/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "FutureTaskTest.h"

#include <decaf/util/LinkedList.h>
#include <decaf/util/concurrent/FutureTask.h>
#include <decaf/util/concurrent/Callable.h>
#include <decaf/lang/Integer.h>
#include <decaf/lang/Thread.h>
#include <decaf/lang/exceptions/IllegalArgumentException.h>

#include <typeinfo>

using namespace std;
using namespace decaf;
using namespace decaf::lang;
using namespace decaf::lang::exceptions;
using namespace decaf::util;
using namespace decaf::util::concurrent;

////////////////////////////////////////////////////////////////////////////////
namespace {

    class PublicFutureTask : public FutureTask<std::string> {
    public:

        PublicFutureTask(Callable<std::string>* r) : FutureTask<std::string>(r) {}

        virtual ~PublicFutureTask() {}

        virtual bool runAndReset() {
            return FutureTask<std::string>::runAndReset();
        }

        virtual void set(const std::string& x) {
            FutureTask<std::string>::set(x);
        }

        virtual void setException(const Exception& ex) {
            FutureTask<std::string>::setException(ex);
        }
    };

    class FutureRunnable : public decaf::lang::Runnable {
    public:

        FutureRunnable() {
        }

        virtual ~FutureRunnable() {
        }

        virtual void run() {
        }
    };

    template<typename E>
    class FutureCallable : public Callable<E> {
    public:

        FutureCallable() {
        }

        virtual ~FutureCallable() {
        }

        virtual E call() {
            return E();
        }
    };

    template<typename E>
    class MediumSleepCallable : public Callable<E> {
    private:

        FutureTaskTest* parent;

    private:

        MediumSleepCallable(const MediumSleepCallable&);
        MediumSleepCallable operator= (const MediumSleepCallable&);

    public:

        MediumSleepCallable(FutureTaskTest* parent) : Callable<E>(), parent(parent) {
        }

        virtual ~MediumSleepCallable() {
        }

        virtual E call() {
            try {
                Thread::sleep(FutureTaskTest::MEDIUM_DELAY_MS);
                this->parent->threadShouldThrow();
            } catch (InterruptedException& success) {
            }

            return E();
        }
    };
}

////////////////////////////////////////////////////////////////////////////////
FutureTaskTest::FutureTaskTest() {
}

////////////////////////////////////////////////////////////////////////////////
FutureTaskTest::~FutureTaskTest() {
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testConstructor1() {

    CPPUNIT_ASSERT_THROW_MESSAGE(
        "Should have thrown a NullPointerException",
        new FutureTask<std::string>(NULL),
        NullPointerException);
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testConstructor2() {

    CPPUNIT_ASSERT_THROW_MESSAGE(
        "Should have thrown a NullPointerException",
        new FutureTask<std::string>(NULL, std::string("Test")),
        NullPointerException);
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testConstructor3() {
    FutureTask<int> task(new FutureRunnable(), 10);
    CPPUNIT_ASSERT(!task.isCancelled());
    CPPUNIT_ASSERT(!task.isDone());

    task.run();

    CPPUNIT_ASSERT(!task.isCancelled());
    CPPUNIT_ASSERT(task.isDone());
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testConstructor4() {

    FutureTask<std::string> task(new FutureCallable<std::string>());
    CPPUNIT_ASSERT(!task.isCancelled());
    CPPUNIT_ASSERT(!task.isDone());

    task.run();

    CPPUNIT_ASSERT(!task.isCancelled());
    CPPUNIT_ASSERT(task.isDone());
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testIsDone() {

    FutureTask<int> task(new NoOpCallable<int>());
    task.run();
    CPPUNIT_ASSERT(task.isDone());
    CPPUNIT_ASSERT(!task.isCancelled());
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testRunAndReset() {
    PublicFutureTask task(new NoOpCallable<std::string>());
    CPPUNIT_ASSERT(task.runAndReset());
    CPPUNIT_ASSERT(!task.isDone());
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testResetAfterCancel() {
    PublicFutureTask task(new NoOpCallable<std::string>());
    CPPUNIT_ASSERT(task.cancel(false));
    CPPUNIT_ASSERT(!task.runAndReset());
    CPPUNIT_ASSERT(task.isDone());
    CPPUNIT_ASSERT(task.isCancelled());
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testSet() {
    PublicFutureTask task(new NoOpCallable<std::string>());
    task.set("one");
    try {
        CPPUNIT_ASSERT_EQUAL(task.get(), std::string("one"));
    } catch(Exception& e) {
        unexpectedException();
    }
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testSetException() {
    NoSuchElementException nse;
    PublicFutureTask task(new NoOpCallable<std::string>());
    task.setException(nse);
    try {
        std::string x = task.get();
        shouldThrow();
    } catch(ExecutionException& ee) {
        const NoSuchElementException* cause =
            dynamic_cast<const NoSuchElementException*>(ee.getCause());
        CPPUNIT_ASSERT(cause != NULL);
    } catch(Exception& e) {
        unexpectedException();
    }
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testCancelBeforeRun() {
    PublicFutureTask task(new NoOpCallable<std::string>());
    CPPUNIT_ASSERT(task.cancel(false));
    task.run();
    CPPUNIT_ASSERT(task.isDone());
    CPPUNIT_ASSERT(task.isCancelled());
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testCancelBeforeRun2() {
    PublicFutureTask task(new NoOpCallable<std::string>());
    CPPUNIT_ASSERT(task.cancel(true));
    task.run();
    CPPUNIT_ASSERT(task.isDone());
    CPPUNIT_ASSERT(task.isCancelled());
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testCancelAfterRun() {
    PublicFutureTask task(new NoOpCallable<std::string>());
    task.run();
    CPPUNIT_ASSERT(!task.cancel(false));
    CPPUNIT_ASSERT(task.isDone());
    CPPUNIT_ASSERT(!task.isCancelled());
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testCancelInterrupt() {

    FutureTask<std::string> task(new MediumSleepCallable<std::string>(this));
    Thread t(&task);
    t.start();

    try {
        Thread::sleep(SHORT_DELAY_MS);
        CPPUNIT_ASSERT(task.cancel(true));
        t.join();
        CPPUNIT_ASSERT(task.isDone());
        CPPUNIT_ASSERT(task.isCancelled());
    } catch(InterruptedException& e) {
        unexpectedException();
    }
}

////////////////////////////////////////////////////////////////////////////////
namespace {

    template<typename E>
    class MediumNoInterruptsSleepCallable : public Callable<E> {
    private:

        FutureTaskTest* parent;

    private:

        MediumNoInterruptsSleepCallable(const MediumNoInterruptsSleepCallable&);
        MediumNoInterruptsSleepCallable operator= (const MediumNoInterruptsSleepCallable&);

    public:

        MediumNoInterruptsSleepCallable(FutureTaskTest* parent) : Callable<E>(), parent(parent) {
        }

        virtual ~MediumNoInterruptsSleepCallable() {
        }

        virtual E call() {
            try {
                Thread::sleep(FutureTaskTest::MEDIUM_DELAY_MS);
            } catch (InterruptedException& success) {
                this->parent->threadFail("Should not interrupt");
            }

            return E();
        }
    };
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testCancelNoInterrupt() {

    FutureTask<std::string> task(new MediumNoInterruptsSleepCallable<std::string>(this));
    Thread t(&task);
    t.start();

    try {
        Thread::sleep(SHORT_DELAY_MS);
        CPPUNIT_ASSERT(task.cancel(false));
        t.join();
        CPPUNIT_ASSERT(task.isDone());
        CPPUNIT_ASSERT(task.isCancelled());
    } catch(InterruptedException& e) {
        unexpectedException();
    }
}

////////////////////////////////////////////////////////////////////////////////
namespace {

    class MediumNoInterruptsSleepRunnable : public Runnable {
    private:

        FutureTaskTest* parent;
        FutureTask<std::string>* task;

    private:

        MediumNoInterruptsSleepRunnable(const MediumNoInterruptsSleepRunnable&);
        MediumNoInterruptsSleepRunnable operator= (const MediumNoInterruptsSleepRunnable&);

    public:

        MediumNoInterruptsSleepRunnable(FutureTaskTest* parent, FutureTask<std::string>* task) :
            Runnable(), parent(parent), task(task) {
        }

        virtual ~MediumNoInterruptsSleepRunnable() {
        }

        virtual void run() {
            try {
                task->get();
            } catch(Exception& e) {
                this->parent->threadUnexpectedException();
            }
        }
    };
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testGet1() {

    FutureTask<std::string> ft(new MediumNoInterruptsSleepCallable<std::string>(this));
    MediumNoInterruptsSleepRunnable runner(this, &ft);
    Thread t(&runner);

    try {
        CPPUNIT_ASSERT(!ft.isDone());
        CPPUNIT_ASSERT(!ft.isCancelled());
        t.start();
        Thread::sleep(SHORT_DELAY_MS);
        ft.run();
        t.join();
        CPPUNIT_ASSERT(ft.isDone());
        CPPUNIT_ASSERT(!ft.isCancelled());
    } catch(InterruptedException& e) {
        unexpectedException();
    }
}

////////////////////////////////////////////////////////////////////////////////
namespace {

    class GetDelayedFutureTaskRunnable : public Runnable {
    private:

        FutureTaskTest* parent;
        FutureTask<std::string>* task;

    private:

        GetDelayedFutureTaskRunnable(const GetDelayedFutureTaskRunnable&);
        GetDelayedFutureTaskRunnable operator= (const GetDelayedFutureTaskRunnable&);

    public:

        GetDelayedFutureTaskRunnable(FutureTaskTest* parent, FutureTask<std::string>* task) :
            Runnable(), parent(parent), task(task) {
        }

        virtual ~GetDelayedFutureTaskRunnable() {
        }

        virtual void run() {
            try {
                task->get(FutureTaskTest::SHORT_DELAY_MS, TimeUnit::MILLISECONDS);
            } catch(TimeoutException& success) {
            } catch(Exception& e) {
                this->parent->threadUnexpectedException();
            }
        }
    };
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testTimedGet1() {

    FutureTask<std::string> ft(new MediumNoInterruptsSleepCallable<std::string>(this));
    GetDelayedFutureTaskRunnable runner(this, &ft);
    Thread t(&runner);

    try {
        CPPUNIT_ASSERT(!ft.isDone());
        CPPUNIT_ASSERT(!ft.isCancelled());
        t.start();
        ft.run();
        t.join();
        CPPUNIT_ASSERT(ft.isDone());
        CPPUNIT_ASSERT(!ft.isCancelled());
    } catch(InterruptedException& e) {
        unexpectedException();
    }
}

////////////////////////////////////////////////////////////////////////////////
namespace {

    template<typename E>
    class ShortSleepCallable : public Callable<E> {
    private:

        FutureTaskTest* parent;

    private:

        ShortSleepCallable(const ShortSleepCallable&);
        ShortSleepCallable operator= (const ShortSleepCallable&);

    public:

        ShortSleepCallable(FutureTaskTest* parent) : Callable<E>(), parent(parent) {
        }

        virtual ~ShortSleepCallable() {
        }

        virtual E call() {
            try {
                Thread::sleep(FutureTaskTest::MEDIUM_DELAY_MS);
                this->parent->threadShouldThrow();
            } catch (InterruptedException& success) {
            }

            return E();
        }
    };

    class InterruptedGetDelayedFutureTaskRunnable : public Runnable {
    private:

        FutureTaskTest* parent;
        FutureTask<std::string>* task;

    private:

        InterruptedGetDelayedFutureTaskRunnable(const InterruptedGetDelayedFutureTaskRunnable&);
        InterruptedGetDelayedFutureTaskRunnable operator= (const InterruptedGetDelayedFutureTaskRunnable&);

    public:

        InterruptedGetDelayedFutureTaskRunnable(FutureTaskTest* parent, FutureTask<std::string>* task) :
            Runnable(), parent(parent), task(task) {
        }

        virtual ~InterruptedGetDelayedFutureTaskRunnable() {
        }

        virtual void run() {
            try {
                task->get(FutureTaskTest::MEDIUM_DELAY_MS, TimeUnit::MILLISECONDS);
                this->parent->threadShouldThrow();
            } catch(CancellationException& success) {
            } catch(Exception& e) {
                this->parent->threadUnexpectedException();
            }
        }
    };
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testTimedGetCancellation() {

    FutureTask<std::string> ft(new ShortSleepCallable<std::string>(this));
    InterruptedGetDelayedFutureTaskRunnable runner(this, &ft);

    try {
        Thread t1(&runner);
        Thread t2(&ft);
        t1.start();
        t2.start();
        Thread::sleep(SHORT_DELAY_MS);
        ft.cancel(true);
        t1.join();
        t2.join();
    } catch(InterruptedException& ie) {
        unexpectedException();
    }
}

////////////////////////////////////////////////////////////////////////////////
namespace {

    class CancelledGetFutureTaskRunnable : public Runnable {
    private:

        FutureTaskTest* parent;
        FutureTask<std::string>* task;

    private:

        CancelledGetFutureTaskRunnable(const CancelledGetFutureTaskRunnable&);
        CancelledGetFutureTaskRunnable operator= (const CancelledGetFutureTaskRunnable&);

    public:

        CancelledGetFutureTaskRunnable(FutureTaskTest* parent, FutureTask<std::string>* task) :
            Runnable(), parent(parent), task(task) {
        }

        virtual ~CancelledGetFutureTaskRunnable() {
        }

        virtual void run() {
            try {
                task->get();
                this->parent->threadShouldThrow();
            } catch(CancellationException& success) {
            } catch(Exception& e) {
                this->parent->threadUnexpectedException();
            }
        }
    };
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testGetCancellation() {

    FutureTask<std::string> ft(new MediumSleepCallable<std::string>(this));
    CancelledGetFutureTaskRunnable runner(this, &ft);

    try {
        Thread t1(&runner);
        Thread t2(&ft);
        t1.start();
        t2.start();
        Thread::sleep(SHORT_DELAY_MS);
        ft.cancel(true);
        t1.join();
        t2.join();
    } catch(InterruptedException& success) {
        unexpectedException();
    }
}

////////////////////////////////////////////////////////////////////////////////
namespace {

    template<typename E>
    class ErrorThrowingCallable : public Callable<E> {
    private:

        FutureTaskTest* parent;

    private:

        ErrorThrowingCallable(const ErrorThrowingCallable&);
        ErrorThrowingCallable operator= (const ErrorThrowingCallable&);

    public:

        ErrorThrowingCallable(FutureTaskTest* parent) : Callable<E>(), parent(parent) {
        }

        virtual ~ErrorThrowingCallable() {
        }

        virtual E call() {
            throw Exception(__FILE__, __LINE__, "Something aweful");
            return E();
        }
    };
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testGetExecutionException() {

    FutureTask<std::string> ft(new ErrorThrowingCallable<std::string>(this));

    try {
        ft.run();
        ft.get();
        shouldThrow();
    } catch(ExecutionException& success) {
    } catch(Exception& e) {
        unexpectedException();
    }
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testTimedGetExecutionException2() {

    FutureTask<std::string> ft(new ErrorThrowingCallable<std::string>(this));

    try {
        ft.run();
        ft.get(SHORT_DELAY_MS, TimeUnit::MILLISECONDS);
        shouldThrow();
    } catch(ExecutionException& success) {
    } catch(TimeoutException& success) {
    } catch(Exception& e) {
        unexpectedException();
    }
}

////////////////////////////////////////////////////////////////////////////////
namespace {

    class InterruptableFutureTaskGetRunnable : public Runnable {
    private:

        FutureTaskTest* parent;
        FutureTask<std::string>* task;

    private:

        InterruptableFutureTaskGetRunnable(const InterruptableFutureTaskGetRunnable&);
        InterruptableFutureTaskGetRunnable operator= (const InterruptableFutureTaskGetRunnable&);

    public:

        InterruptableFutureTaskGetRunnable(FutureTaskTest* parent, FutureTask<std::string>* task) :
            Runnable(), parent(parent), task(task) {
        }

        virtual ~InterruptableFutureTaskGetRunnable() {
        }

        virtual void run() {
            try {
                task->get();
                this->parent->threadShouldThrow();
            } catch(InterruptedException& success) {
            } catch(Exception& e) {
                this->parent->threadUnexpectedException();
            }
        }
    };
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testGetInterruptedException() {

    FutureTask<std::string> ft(new NoOpCallable<std::string>());
    Thread t(&ft);

    try {
        t.start();
        Thread::sleep( SHORT_DELAY_MS);
        t.interrupt();
        t.join();
    } catch(Exception& e) {
        unexpectedException();
    }
}

////////////////////////////////////////////////////////////////////////////////
namespace {

    class InterruptableFutureTaskLongTimeoutGetRunnable : public Runnable {
    private:

        FutureTaskTest* parent;
        FutureTask<std::string>* task;

    private:

        InterruptableFutureTaskLongTimeoutGetRunnable(const InterruptableFutureTaskLongTimeoutGetRunnable&);
        InterruptableFutureTaskLongTimeoutGetRunnable operator= (const InterruptableFutureTaskLongTimeoutGetRunnable&);

    public:

        InterruptableFutureTaskLongTimeoutGetRunnable(FutureTaskTest* parent, FutureTask<std::string>* task) :
            Runnable(), parent(parent), task(task) {
        }

        virtual ~InterruptableFutureTaskLongTimeoutGetRunnable() {
        }

        virtual void run() {
            try {
                task->get(FutureTaskTest::LONG_DELAY_MS,TimeUnit::MILLISECONDS);
                this->parent->threadShouldThrow();
            } catch(InterruptedException& success) {
            } catch(Exception& e) {
                this->parent->threadUnexpectedException();
            }
        }
    };
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testTimedGetInterruptedException2() {
    FutureTask<std::string> ft(new NoOpCallable<std::string>());
    InterruptableFutureTaskLongTimeoutGetRunnable runner(this, &ft);
    Thread t(&runner);

    try {
        t.start();
        Thread::sleep(SHORT_DELAY_MS);
        t.interrupt();
        t.join();
    } catch(Exception& e) {
        unexpectedException();
    }
}

////////////////////////////////////////////////////////////////////////////////
void FutureTaskTest::testGetTimeoutException() {
    try {
        FutureTask<std::string> ft(new NoOpCallable<std::string>());
        ft.get(1, TimeUnit::MILLISECONDS);
        shouldThrow();
    } catch(TimeoutException& success) {
    } catch(Exception& success) {
        unexpectedException();
    }
}
