package jp.sourceforge.nicoro;

import android.os.SystemClock;
import android.test.InstrumentationTestCase;

public class DestroyTaskTest extends InstrumentationTestCase {
    private static class FakeCallback implements DestroyTask.Callback {
        boolean mOnDestroyImplPre;
        boolean mOnDestroyImpl;
        boolean mOnDestroyImplPost;
        boolean mFinishReally;
        boolean mWasDestroyed;

        Object mSync;
        Exception mSyncException;

        @Override
        public void onDestroyImplPre() {
            mOnDestroyImplPre = true;
        }
        @Override
        public void onDestroyImpl() {
            mOnDestroyImpl = true;
            if (mSync != null) {
                synchronized (mSync) {
                    try {
                        mSync.wait(60000);
                    } catch (Exception e) {
                        mSyncException = e;
                    }
                }
            }
        }
        @Override
        public void onDestroyImplPost() {
            mOnDestroyImplPost = true;
        }
        @Override
        public void finishReally() {
            mFinishReally = true;
        }
        @Override
        public boolean wasDestroyed() {
            return mWasDestroyed;
        }

        public void clear() {
            mOnDestroyImplPre = false;
            mOnDestroyImpl = false;
            mOnDestroyImplPost = false;
            mFinishReally = false;
            mWasDestroyed = false;
            mSync = null;
            mSyncException = null;
        }
    }

    private static class FakeDestroyTask extends DestroyTask {
        boolean mOnPreExecuteImpl;
        boolean mOnPostExecuteImpl;
        boolean mTimeoutImpl;

        FakeDestroyTask(DestroyTask.Callback callback) {
            super(callback);
        }
        FakeDestroyTask(DestroyTask.Callback callback,
                long finishTimeoutMs) {
            super(callback, finishTimeoutMs);
        }

        @Override
        protected void onPreExecuteImpl() {
            mOnPreExecuteImpl = true;
        }
        @Override
        protected void onPostExecuteImpl() {
            mOnPostExecuteImpl = true;
        }
        @Override
        protected void timeoutImpl() {
            mTimeoutImpl = true;
        }

        public void clear() {
            mOnPreExecuteImpl = false;
            mOnPostExecuteImpl = false;
            mTimeoutImpl = false;
        }
    }

    private FakeCallback mCallback;
    private FakeDestroyTask mTask;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        mCallback = new FakeCallback();
    }

    @Override
    protected void tearDown() throws Exception {
        mCallback = null;
        mTask = null;
        super.tearDown();
    }

    public void testBeforeOnDestroy() throws Throwable {
        createTestDestroyTask();

        assertFalse(mTask.mOnPreExecuteImpl);
        assertFalse(mTask.mOnPostExecuteImpl);
        assertFalse(mTask.mTimeoutImpl);
        assertFalse(mCallback.mOnDestroyImplPre);
        assertFalse(mCallback.mOnDestroyImpl);
        assertFalse(mCallback.mOnDestroyImplPost);
        assertFalse(mCallback.mFinishReally);
        assertNull(mCallback.mSync);
        assertNull(mCallback.mSyncException);

        mCallback.mSync = new Object();

        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                mTask.finishActivity();
                assertTrue(mTask.mOnPreExecuteImpl);
                assertFalse(mTask.mOnPostExecuteImpl);
                assertFalse(mTask.mTimeoutImpl);
                assertTrue(mCallback.mOnDestroyImplPre);
                assertFalse(mCallback.mOnDestroyImplPost);
                assertFalse(mCallback.mFinishReally);
            }
        });

        waitUntilOnDestroyImplTrue();
        assertFalse(mTask.mOnPostExecuteImpl);
        assertFalse(mTask.mTimeoutImpl);
        assertFalse(mCallback.mOnDestroyImplPost);
        assertFalse(mCallback.mFinishReally);

        synchronized (mCallback.mSync) {
            mCallback.mSync.notifyAll();
        }

        SystemClock.sleep(100);
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                assertTrue(mTask.mOnPostExecuteImpl);
                assertFalse(mTask.mTimeoutImpl);
                assertFalse(mCallback.mOnDestroyImplPost);
                assertTrue(mCallback.mFinishReally);

                assertTrue(mTask.onDestroy());
                assertTrue(mCallback.mOnDestroyImplPost);
            }
        });
    }

    public void testDuringOnDestroy() throws Throwable {
        createTestDestroyTask();

        assertFalse(mTask.mOnPreExecuteImpl);
        assertFalse(mTask.mOnPostExecuteImpl);
        assertFalse(mTask.mTimeoutImpl);
        assertFalse(mCallback.mOnDestroyImplPre);
        assertFalse(mCallback.mOnDestroyImpl);
        assertFalse(mCallback.mOnDestroyImplPost);
        assertFalse(mCallback.mFinishReally);
        assertNull(mCallback.mSync);
        assertNull(mCallback.mSyncException);

        mCallback.mSync = new Object();

        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                mTask.finishActivity();
                assertTrue(mTask.mOnPreExecuteImpl);
                assertFalse(mTask.mOnPostExecuteImpl);
                assertFalse(mTask.mTimeoutImpl);
                assertTrue(mCallback.mOnDestroyImplPre);
                assertFalse(mCallback.mOnDestroyImplPost);
                assertFalse(mCallback.mFinishReally);
            }
        });

        waitUntilOnDestroyImplTrue();
        assertFalse(mTask.mOnPostExecuteImpl);
        assertFalse(mTask.mTimeoutImpl);
        assertFalse(mCallback.mOnDestroyImplPost);
        assertFalse(mCallback.mFinishReally);

        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                assertFalse(mTask.onDestroy());
                assertFalse(mTask.mOnPostExecuteImpl);
                assertFalse(mTask.mTimeoutImpl);
                assertFalse(mCallback.mOnDestroyImplPost);
                assertFalse(mCallback.mFinishReally);
                mCallback.mWasDestroyed = true;
            }
        });

        synchronized (mCallback.mSync) {
            mCallback.mSync.notifyAll();
        }

        SystemClock.sleep(100);
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                assertTrue(mTask.mOnPostExecuteImpl);
                assertFalse(mTask.mTimeoutImpl);
                assertTrue(mCallback.mOnDestroyImplPost);
                assertTrue(mCallback.mFinishReally);
            }
        });
    }

    public void testAfterOnDestroy() throws Throwable {
        createTestDestroyTask();

        assertFalse(mTask.mOnPreExecuteImpl);
        assertFalse(mTask.mOnPostExecuteImpl);
        assertFalse(mTask.mTimeoutImpl);
        assertFalse(mCallback.mOnDestroyImplPre);
        assertFalse(mCallback.mOnDestroyImpl);
        assertFalse(mCallback.mOnDestroyImplPost);
        assertFalse(mCallback.mFinishReally);
        assertNull(mCallback.mSync);
        assertNull(mCallback.mSyncException);

        mCallback.mSync = new Object();

        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                assertFalse(mTask.onDestroy());
                assertTrue(mTask.mOnPreExecuteImpl);
                assertFalse(mTask.mOnPostExecuteImpl);
                assertFalse(mTask.mTimeoutImpl);
                assertTrue(mCallback.mOnDestroyImplPre);
                assertFalse(mCallback.mOnDestroyImplPost);
                assertFalse(mCallback.mFinishReally);
                mCallback.mWasDestroyed = true;

                mTask.finishActivity();
                assertFalse(mTask.mOnPostExecuteImpl);
                assertFalse(mTask.mTimeoutImpl);
                assertFalse(mCallback.mOnDestroyImplPost);
                assertFalse(mCallback.mFinishReally);
            }
        });

        waitUntilOnDestroyImplTrue();
        assertFalse(mTask.mOnPostExecuteImpl);
        assertFalse(mTask.mTimeoutImpl);
        assertFalse(mCallback.mOnDestroyImplPost);
        assertFalse(mCallback.mFinishReally);

        synchronized (mCallback.mSync) {
            mCallback.mSync.notifyAll();
        }

        SystemClock.sleep(100);
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                assertTrue(mTask.mOnPostExecuteImpl);
                assertFalse(mTask.mTimeoutImpl);
                assertTrue(mCallback.mOnDestroyImplPost);
                assertTrue(mCallback.mFinishReally);
            }
        });
    }

    public void testTimeout() throws Throwable {
        final long timeout = 500;
        createTestDestroyTask(timeout);

        assertFalse(mTask.mOnPreExecuteImpl);
        assertFalse(mTask.mOnPostExecuteImpl);
        assertFalse(mTask.mTimeoutImpl);
        assertFalse(mCallback.mOnDestroyImplPre);
        assertFalse(mCallback.mOnDestroyImpl);
        assertFalse(mCallback.mOnDestroyImplPost);
        assertFalse(mCallback.mFinishReally);
        assertNull(mCallback.mSync);
        assertNull(mCallback.mSyncException);

        mCallback.mSync = new Object();

        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                mTask.finishActivity();
                assertTrue(mTask.mOnPreExecuteImpl);
                assertFalse(mTask.mOnPostExecuteImpl);
                assertFalse(mTask.mTimeoutImpl);
                assertTrue(mCallback.mOnDestroyImplPre);
                assertFalse(mCallback.mOnDestroyImplPost);
                assertFalse(mCallback.mFinishReally);
            }
        });

        waitUntilOnDestroyImplTrue();
        assertFalse(mTask.mOnPostExecuteImpl);
        assertFalse(mTask.mTimeoutImpl);
        assertFalse(mCallback.mOnDestroyImplPost);
        assertFalse(mCallback.mFinishReally);

        SystemClock.sleep(timeout + 100);
        assertFalse(mTask.mOnPostExecuteImpl);
        assertTrue(mTask.mTimeoutImpl);
        assertFalse(mCallback.mOnDestroyImplPost);
        assertTrue(mCallback.mFinishReally);

        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                assertFalse(mTask.onDestroy());
                assertFalse(mTask.mOnPostExecuteImpl);
                assertFalse(mCallback.mOnDestroyImplPost);
                mCallback.mWasDestroyed = true;
            }
        });

        synchronized (mCallback.mSync) {
            mCallback.mSync.notifyAll();
        }

        SystemClock.sleep(100);
        assertTrue(mTask.mOnPostExecuteImpl);
        assertTrue(mCallback.mOnDestroyImplPost);
    }

    private void createTestDestroyTask() throws Throwable {
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                mTask = new FakeDestroyTask(mCallback);
            }
        });
    }
    private void createTestDestroyTask(final long finishTimeoutMs) throws Throwable {
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                mTask = new FakeDestroyTask(mCallback, finishTimeoutMs);
            }
        });
    }

    private void waitUntilOnDestroyImplTrue() {
        final long timeout = 1000L;
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < timeout) {
            if (mCallback.mOnDestroyImpl) {
                return;
            }
            SystemClock.sleep(50);
        }
        fail("mCallback.mOnDestroyImpl is not true, timeout=" + timeout + "ms");
    }
}
