package site.hanschen.pretty.service;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.support.annotation.WorkerThread;
import android.util.Log;
import android.util.SparseArray;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;

import site.hanschen.pretty.db.bean.Question;
import site.hanschen.pretty.db.repository.PrettyRepository;
import site.hanschen.pretty.zhihu.ZhiHuApi;

/**
 * @author HansChen
 */
public class Dispatcher {

    private static final int MSG_ENQUEUE_TASK    = 0;
    private static final int MSG_DEQUEUE_TASK    = 1;
    private static final int MSG_HUNTER_COMPLETE = 2;
    private static final int MSG_HUNTER_FAILED   = 3;
    private static final int MSG_NEXT_BATCH      = 4;

    private Handler            mWorkerHandler;
    private Handler            mMainHandler;
    private HandlerThread      mWorkerThread;
    private PrettyRepository   mPrettyRepository;
    private ZhiHuApi           mApi;
    private ThreadPoolExecutor mExecutor;

    private volatile List<UrlHunter>           mTaskArray;
    private          SparseArray<List<String>> mBatch;

    public Dispatcher(PrettyRepository repository, ZhiHuApi api, ThreadPoolExecutor executor, Handler mainHandler) {
        this.mPrettyRepository = repository;
        this.mApi = api;
        this.mExecutor = executor;
        this.mMainHandler = mainHandler;

        mWorkerThread = new HandlerThread("worker thread");
        mWorkerThread.start();
        mWorkerHandler = new Handler(mWorkerThread.getLooper(), mWorkerCallback);
        mTaskArray = new ArrayList<>();
        mBatch = new SparseArray<>();
    }

    void shutdown() {
        mWorkerHandler.removeCallbacksAndMessages(null);
        mWorkerThread.quit();
        mTaskArray.clear();
    }

    public boolean isFetching(int questionId) {
        for (UrlHunter h : mTaskArray) {
            if (h.getQuestionId() == questionId) {
                return true;
            }
        }
        return false;
    }

    public void dispatchAddTask(int questionId) {
        Message message = mWorkerHandler.obtainMessage(MSG_ENQUEUE_TASK);
        message.arg1 = questionId;
        mWorkerHandler.sendMessage(message);
    }

    public void dispatchRemoveTask(int questionId) {
        Message message = mWorkerHandler.obtainMessage(MSG_DEQUEUE_TASK);
        message.arg1 = questionId;
        mWorkerHandler.sendMessage(message);
    }

    public void dispatchHuntComplete(UrlHunter hunter) {
        Message message = mWorkerHandler.obtainMessage(MSG_HUNTER_COMPLETE);
        message.obj = hunter;
        mWorkerHandler.sendMessage(message);
    }

    public void dispatchHuntFailed(UrlHunter hunter) {
        Message message = mWorkerHandler.obtainMessage(MSG_HUNTER_FAILED);
        message.obj = hunter;
        mWorkerHandler.sendMessage(message);
    }

    @WorkerThread
    private void performAddTask(int questionId) {
        Question question = mPrettyRepository.getQuestion(questionId);
        if (question != null) {
            for (int offset = 0; offset < question.getAnswerCount(); offset += 10) {
                UrlHunter hunter = new UrlHunter(questionId, 10, offset, mApi, this);
                mTaskArray.add(hunter);
                hunter.setFuture(mExecutor.submit(hunter));
                Log.d("Hans", "submit: " + hunter);
            }
        }
    }

    @WorkerThread
    private void performRemoveTask(int questionId) {
        Iterator<UrlHunter> iterator = mTaskArray.iterator();
        while (iterator.hasNext()) {
            UrlHunter h = iterator.next();
            if (h.getQuestionId() == questionId && h.cancel()) {
                iterator.remove();
            }
        }
    }

    @WorkerThread
    private void performHuntComplete(UrlHunter hunter) {
        mTaskArray.remove(hunter);
        batch(hunter);
    }

    @WorkerThread
    private void batch(UrlHunter hunter) {
        if (hunter.isCancelled()) {
            return;
        }
        List<String> urls = mBatch.get(hunter.getQuestionId());
        if (urls == null) {
            mBatch.put(hunter.getQuestionId(), hunter.getResult());
        } else {
            urls.addAll(hunter.getResult());
        }
        if (!mWorkerHandler.hasMessages(MSG_NEXT_BATCH)) {
            mWorkerHandler.sendEmptyMessageDelayed(MSG_NEXT_BATCH, 1000);
        }
    }

    @WorkerThread
    private void performHuntFailed(UrlHunter hunter) {
        mTaskArray.remove(hunter);
    }

    @WorkerThread
    private void performBatch() {
        SparseArray copy = mBatch.clone();
        mBatch.clear();
        mMainHandler.sendMessage(mMainHandler.obtainMessage(TaskService.MSG_HUNT_COMPLETE, copy));
    }

    @SuppressWarnings("FieldCanBeLocal")
    private Handler.Callback mWorkerCallback = new Handler.Callback() {

        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_ENQUEUE_TASK:
                    performAddTask(msg.arg1);
                    break;
                case MSG_DEQUEUE_TASK:
                    performRemoveTask(msg.arg1);
                    break;
                case MSG_HUNTER_COMPLETE:
                    performHuntComplete((UrlHunter) msg.obj);
                    break;
                case MSG_HUNTER_FAILED:
                    performHuntFailed((UrlHunter) msg.obj);
                    break;
                case MSG_NEXT_BATCH:
                    performBatch();
                    break;
            }
            return true;
        }
    };
}