package jp.sourceforge.nicoro;

import static jp.sourceforge.nicoro.Log.LOG_TAG;

import android.os.AsyncTask;
import android.os.Handler;

import java.lang.ref.WeakReference;

import jp.gr.java_conf.shiseissi.commonlib.APILevelWrapper;

public abstract class DestroyTask extends AsyncTask<Void, Void, Void> {
    private static final boolean DEBUG_LOGV = Release.IS_DEBUG & true;
    private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;
    private static final long DEFAULT_FINISH_TIMEOUT_MS = 1000L * 10L;

    public interface Callback {
        /**
         * onDestroyで行うべき終了処理の本体<BR>
         * onPauseよりも先に実行される可能性がある<BR>
         * onDestroyImplよりも前に実行される<BR>
         * Mainスレッドで実行される
         */
        void onDestroyImplPre();
        /**
         * onDestroyで行うべき終了処理の本体<BR>
         * onPauseよりも先に実行される可能性がある<BR>
         * 外部スレッドとMainスレッドのどちらででも実行される可能性がある
         */
        void onDestroyImpl();
        /**
         * onDestroyで行うべき終了処理の本体<BR>
         * onDestroy上またはその後に実行される<BR>
         * onDestroyImplの後に実行される<BR>
         * Mainスレッドで実行される<BR>
         * 二重実行される可能性がある
         */
        void onDestroyImplPost();
        /**
         * 真の{@link android.app.Activity#finish}を呼ぶこと
         */
        void finishReally();
        /**
         *
         * @return onDestroyを終えていればtrue, そうでなければfalse
         */
        boolean wasDestroyed();
    }

    private final WeakReference<Callback> mCallback;
    private final long mFinishTimeoutMs;
    private final Object mSync = new Object();
    private boolean mTaskStart = false;
    private boolean mTaskEnd = false;

    protected DestroyTask(DestroyTask.Callback callback) {
        this(callback, DEFAULT_FINISH_TIMEOUT_MS);
    }
    protected DestroyTask(DestroyTask.Callback callback,
            long finishTimeoutMs) {
        mCallback = new WeakReference<DestroyTask.Callback>(callback);
        mFinishTimeoutMs = finishTimeoutMs;
    }

    @Override
    final protected void onPreExecute() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#onPreExecute").toString());
        }
        mTaskStart = true;
        onPreExecuteImpl();
        Callback callback = mCallback.get();
        if (callback != null) {
            callback.onDestroyImplPre();
        }
    }
    protected abstract void onPreExecuteImpl();

    @Override
    final protected Void doInBackground(Void... params) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#doInBackground").toString());
        }

        Callback callback = mCallback.get();
        if (callback != null) {
            callback.onDestroyImpl();
        }
        synchronized (mSync) {
            mTaskEnd = true;
            mSync.notifyAll();
        }
        return null;
    }

    @Override
    final protected void onPostExecute(Void result) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#onPostExecute").toString());
        }

        Callback callback = mCallback.get();
        if (callback != null && callback.wasDestroyed()) {
            callback.onDestroyImplPost();
        }
        onPostExecuteImpl();
        if (callback != null) {
            callback.finishReally();
        }
    }
    protected abstract void onPostExecuteImpl();

    final public void timeout() {
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#timeout").toString());
        }

        timeoutImpl();
    }
    protected void timeoutImpl() {
    }

    /**
     * overrideした{@link android.app.Activity#finish}から呼ぶこと
     */
    public void finishActivity() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#finishActivity").toString());
        }

        // XXX onPause等の前に呼ばれることになるので色々と気をつけるべき
        synchronized (mSync) {
            if (!mTaskStart) {
                if (DEBUG_LOGV) {
                    Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                            .append(": start destroy task").toString());
                }

                APILevelWrapper api = APILevelWrapper.createInstance();
                api.executeOnThreadPoolExecutor(this);
                // 専用のHandler使用
                Handler handler = new Handler();
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (!mTaskEnd) {
                            timeout();
                            Callback callback = mCallback.get();
                            if (callback != null) {
                                callback.finishReally();
                            }
                        }
                    }
                }, mFinishTimeoutMs);
            }
        }
    }

    /**
     * overrideした{@link android.app.Activity#onDestroy}から呼ぶこと
     * @return タスクが完了していればtrue, まだならfalse
     */
    public boolean onDestroy() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#onDestroy").toString());
        }

        synchronized (mSync) {
            if (!mTaskStart) {
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, Log.buf().append(getClass().getName())
                            .append(": start destroy task after onDestroy").toString());
                }
                // XXX 終了後も動作し続けるのでメモリの問題等発生の恐れ有り
                APILevelWrapper api = APILevelWrapper.createInstance();
                api.executeOnThreadPoolExecutor(this);
                return false;
            } else if (mTaskEnd) {
                Callback callback = mCallback.get();
                if (callback != null) {
                    callback.onDestroyImplPost();
                }
                return true;
            } else {
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, Log.buf().append(getClass().getName())
                            .append(": running destroy task during onDestroy").toString());
                }
                // タスクが終わるのは待たない
                return false;
            }
        }
    }

    public boolean wasTaskStarted() {
        return mTaskStart;
    }

    public boolean wasTaskEnded() {
        return mTaskEnd;
    }
}
