package com.sobot.network.http.download;

import android.content.ContentValues;
import android.text.TextUtils;
import android.util.Log;

import com.sobot.network.http.SobotOkHttpUtils;
import com.sobot.network.http.db.SobotDownloadManager;
import com.sobot.network.http.exception.StException;
import com.sobot.network.http.exception.StHttpException;
import com.sobot.network.http.exception.StStorageException;
import com.sobot.network.http.model.SobotProgress;
import com.sobot.network.http.request.RequestCall;
import com.sobot.network.http.task.PriorityRunnable;
import com.sobot.network.http.utils.IOUtils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;

import okhttp3.Response;
import okhttp3.ResponseBody;

/**
 * 文件的下载任务类
 */
public class SobotDownloadTask implements Runnable {

    private static final int BUFFER_SIZE = 1024 * 8;

    public SobotProgress progress;
    public Map<Object, SobotDownloadListener> listeners;
    private ThreadPoolExecutor executor;
    private PriorityRunnable priorityRunnable;

    public SobotDownloadTask(String tag, RequestCall request) {
//        HttpUtils.checkNotNull(tag, "tag == null");
        progress = new SobotProgress();
        progress.tag = tag;
        progress.isUpload = false;
        progress.folder = SobotDownload.getInstance().getFolder();
        progress.url = request.getOkHttpRequest().getBaseUrl();
        progress.status = SobotProgress.NONE;
        progress.totalSize = -1;
        progress.request = request;

        executor = SobotDownload.getInstance().getThreadPool().getExecutor();
        listeners = new HashMap<>();
    }

    public SobotDownloadTask(SobotProgress progress) {
//        HttpUtils.checkNotNull(progress, "progress == null");
        this.progress = progress;
        executor = SobotDownload.getInstance().getThreadPool().getExecutor();
        listeners = new HashMap<>();
    }

    public SobotDownloadTask folder(String folder) {
        if (folder != null && !TextUtils.isEmpty(folder.trim())) {
            progress.folder = folder;
        } else {
//            OkLogger.w("folder is null, ignored!");
        }
        return this;
    }

    public SobotDownloadTask fileName(String fileName) {
        if (fileName != null && !TextUtils.isEmpty(fileName.trim())) {
            progress.fileName = fileName;
        } else {
//            OkLogger.w("fileName is null, ignored!");
        }
        return this;
    }

    public SobotDownloadTask priority(int priority) {
        progress.priority = priority;
        return this;
    }

    public SobotDownloadTask save() {
        if (!TextUtils.isEmpty(progress.folder) && !TextUtils.isEmpty(progress.fileName)) {
            progress.filePath = new File(progress.folder, progress.fileName).getAbsolutePath();
        }
        SobotDownloadManager.getInstance().replace(progress);
        return this;
    }

    public SobotDownloadTask register(SobotDownloadListener listener) {
        if (listener != null) {
            listeners.put(listener.tag, listener);
        }
        return this;
    }

    public void unRegister(SobotDownloadListener listener) {
//        HttpUtils.checkNotNull(listener, "listener == null");
        listeners.remove(listener.tag);
    }

    public void unRegister(String tag) {
//        HttpUtils.checkNotNull(tag, "tag == null");
        listeners.remove(tag);
    }

    public void start() {
        if (SobotDownload.getInstance().getTask(progress.tag) == null || SobotDownloadManager.getInstance().get(progress.tag) == null) {
//            throw new IllegalStateException("you must call SobotDownloadTask#save() before SobotDownloadTask#start()！");
            Log.i("SobotDownloadTask", "you must call SobotDownloadTask#save() before SobotDownloadTask#start()！");
            return;
        }
        if (progress.status == SobotProgress.NONE || progress.status == SobotProgress.PAUSE || progress.status == SobotProgress.ERROR) {
            postOnStart(progress);
            postWaiting(progress);
            priorityRunnable = new PriorityRunnable(progress.priority, this);
            executor.execute(priorityRunnable);
        } else if (progress.status == SobotProgress.FINISH) {
            if (progress.filePath == null) {
                postOnError(progress, new StStorageException("the file of the task with tag:" + progress.tag + " may be invalid or damaged, please call the method restart() to download again！"));
            } else {
                File file = new File(progress.filePath);
                if (file.exists() && file.length() == progress.totalSize) {
                    postOnFinish(progress, new File(progress.filePath));
                } else {
                    postOnError(progress, new StStorageException("the file " + progress.filePath + " may be invalid or damaged, please call the method restart() to download again！"));
                }
            }
        } else {
//            OkLogger.w("the task with tag " + progress.tag + " is already in the download queue, current task status is " + progress.status);
        }
    }

    public void restart() {
        pause();
        IOUtils.delFileOrFolder(progress.filePath);
        progress.status = SobotProgress.NONE;
        progress.currentSize = 0;
        progress.fraction = 0;
        progress.speed = 0;
        SobotDownloadManager.getInstance().replace(progress);
        start();
    }

    /** 暂停的方法 */
    public void pause() {
        executor.remove(priorityRunnable);
        if (progress.status == SobotProgress.WAITING) {
            postPause(progress);
        } else if (progress.status == SobotProgress.LOADING) {
            progress.speed = 0;
            progress.status = SobotProgress.PAUSE;
        } else {
//            OkLogger.w("only the task with status WAITING(1) or LOADING(2) can pause, current status is " + progress.status);
        }
    }

    /** 删除一个任务,不会删除下载文件 */
    public void remove() {
        remove(false);
    }

    /** 删除一个任务,会删除下载文件 */
    public SobotDownloadTask remove(boolean isDeleteFile) {
        pause();
        if (isDeleteFile) IOUtils.delFileOrFolder(progress.filePath);
        SobotDownloadManager.getInstance().delete(progress.tag);
        SobotDownloadTask task = SobotDownload.getInstance().removeTask(progress.tag);
        postOnRemove(progress);
        return task;
    }

    @Override
    public void run() {
        //check breakpoint
        long startPosition = progress.currentSize;
        if (startPosition < 0) {
            postOnError(progress, StException.BREAKPOINT_EXPIRED());
            return;
        }
        if (startPosition > 0) {
            if (!TextUtils.isEmpty(progress.filePath)) {
                File file = new File(progress.filePath);
                if (!file.exists()) {
                    startPosition = 0;
                    progress.currentSize = 0;
                }
            }
        }

        //request network from startPosition
        Response response;
        try {
            RequestCall request = progress.request;
            request.getOkHttpRequest().addHeader("Range", "bytes=" + startPosition + "-");
            response = request.execute();
        } catch (Exception e) {
            postOnError(progress, e);
            return;
        }

        //check network data
        int code = response.code();
        if (code == 404 || code >= 500) {
            postOnError(progress, StHttpException.NET_ERROR());
            return;
        }
        ResponseBody body = response.body();
        if (body == null) {
            postOnError(progress, new StHttpException("response body is null"));
            return;
        }
        if (progress.totalSize == -1) {
            progress.totalSize = body.contentLength();
        }

        //create filename
        String fileName = progress.fileName;
        if (TextUtils.isEmpty(fileName)) {
            fileName = IOUtils.getNetFileName(response, progress.url);
            progress.fileName = fileName;
        }
        if (!IOUtils.createFolder(progress.folder)) {
            postOnError(progress, StStorageException.NOT_AVAILABLE());
            return;
        }

        //create and check file
        File file;
        if (TextUtils.isEmpty(progress.filePath)) {
            file = new File(progress.folder, fileName);
            progress.filePath = file.getAbsolutePath();
        } else {
            file = new File(progress.filePath);
        }
        if (startPosition > 0 && !file.exists()) {
            remove();
            postOnError(progress, StException.BREAKPOINT_EXPIRED());
            return;
        }
        if (startPosition > progress.totalSize) {
            remove(true);
            postOnError(progress, StException.BREAKPOINT_EXPIRED());
            return;
        }
        if (startPosition == 0 && file.exists()) {
            IOUtils.delFileOrFolder(file);
        }
        if (startPosition == progress.totalSize && startPosition > 0) {
            if (file.exists() && startPosition == file.length()) {
                postOnFinish(progress, file);
                return;
            } else {
                remove(true);
                postOnError(progress, StException.BREAKPOINT_EXPIRED());
                return;
            }
        }

        //start downloading
        RandomAccessFile randomAccessFile;
        try {
            randomAccessFile = new RandomAccessFile(file, "rw");
            randomAccessFile.seek(startPosition);
            progress.currentSize = startPosition;
        } catch (Exception e) {
            postOnError(progress, e);
            return;
        }
        try {
            SobotDownloadManager.getInstance().replace(progress);
            download(body.byteStream(), randomAccessFile, progress);
        } catch (Exception e) {
            postOnError(progress, e);
            return;
        }

        //check finish status
        if (progress.status == SobotProgress.PAUSE) {
            postPause(progress);
        } else if (progress.status == SobotProgress.LOADING) {
            if (file.length() == progress.totalSize) {
                postOnFinish(progress, file);
            } else {
                postOnError(progress, StException.BREAKPOINT_EXPIRED());
            }
        } else {
            postOnError(progress, StException.UNKNOWN());
        }
    }

    /** 执行文件下载 */
    private void download(InputStream input, RandomAccessFile out, SobotProgress progress) throws IOException {
        if (input == null || out == null) return;

        progress.status = SobotProgress.LOADING;
        byte[] buffer = new byte[BUFFER_SIZE];
        BufferedInputStream in = new BufferedInputStream(input, BUFFER_SIZE);
        int len;
        try {
            while ((len = in.read(buffer, 0, BUFFER_SIZE)) != -1 && progress.status == SobotProgress.LOADING) {
                out.write(buffer, 0, len);

                SobotProgress.changeProgress(progress, len, progress.totalSize, new SobotProgress.Action() {
                    @Override
                    public void call(SobotProgress progress) {
                        postLoading(progress);
                    }
                });
            }
        } finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(input);
        }
    }

    private void postOnStart(final SobotProgress progress) {
        progress.speed = 0;
        progress.status = SobotProgress.NONE;
        updateDatabase(progress);
        SobotOkHttpUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                for (SobotDownloadListener listener : listeners.values()) {
                    listener.onStart(progress);
                }
            }
        });
    }

    private void postWaiting(final SobotProgress progress) {
        progress.speed = 0;
        progress.status = SobotProgress.WAITING;
        updateDatabase(progress);
        SobotOkHttpUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                for (SobotDownloadListener listener : listeners.values()) {
                    listener.onProgress(progress);
                }
            }
        });
    }

    private void postPause(final SobotProgress progress) {
        progress.speed = 0;
        progress.status = SobotProgress.PAUSE;
        updateDatabase(progress);
        SobotOkHttpUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                for (SobotDownloadListener listener : listeners.values()) {
                    listener.onProgress(progress);
                }
            }
        });
    }

    private void postLoading(final SobotProgress progress) {
        updateDatabase(progress);
        SobotOkHttpUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                for (SobotDownloadListener listener : listeners.values()) {
                    listener.onProgress(progress);
                }
            }
        });
    }

    private void postOnError(final SobotProgress progress, final Throwable throwable) {
        progress.speed = 0;
        progress.status = SobotProgress.ERROR;
        progress.exception = throwable;
        updateDatabase(progress);
        SobotOkHttpUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                for (SobotDownloadListener listener : listeners.values()) {
                    listener.onProgress(progress);
                    listener.onError(progress);
                }
            }
        });
    }

    private void postOnFinish(final SobotProgress progress, final File file) {
        progress.speed = 0;
        progress.fraction = 1.0f;
        progress.status = SobotProgress.FINISH;
        updateDatabase(progress);
        SobotOkHttpUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                for (SobotDownloadListener listener : listeners.values()) {
                    listener.onProgress(progress);
                    listener.onFinish(file, progress);
                }
                //删除任务
                SobotDownload.getInstance().removeTask(progress.tag);
            }
        });
    }

    private void postOnRemove(final SobotProgress progress) {
        updateDatabase(progress);
        SobotOkHttpUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                for (SobotDownloadListener listener : listeners.values()) {
                    listener.onRemove(progress);
                }
                listeners.clear();
            }
        });
    }

    private void updateDatabase(SobotProgress progress) {
        ContentValues contentValues = SobotProgress.buildUpdateContentValues(progress);
        SobotDownloadManager.getInstance().update(contentValues, progress.tag);
    }
}
