/*
 * Copyright (c) 2014, Android Open Source Project,张涛.
 *
 * Licensed 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.
 */
package org.kymjs.kjframe;

import org.kymjs.kjframe.http.Cache;
import org.kymjs.kjframe.http.CacheDispatcher;
import org.kymjs.kjframe.http.DownloadController;
import org.kymjs.kjframe.http.DownloadTaskQueue;
import org.kymjs.kjframe.http.FileRequest;
import org.kymjs.kjframe.http.FormRequest;
import org.kymjs.kjframe.http.HttpCallBack;
import org.kymjs.kjframe.http.HttpConfig;
import org.kymjs.kjframe.http.HttpParams;
import org.kymjs.kjframe.http.JsonRequest;
import org.kymjs.kjframe.http.NetworkDispatcher;
import org.kymjs.kjframe.http.Request;
import org.kymjs.kjframe.http.Request.HttpMethod;
import org.kymjs.kjframe.utils.KJLoger;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 本类工作流程： 每当发起一次Request，会对这个Request标记一个唯一值。<br>
 * 并加入当前请求的Set中(保证唯一;方便控制)。<br>
 * 同时判断是否启用缓存，若启用则加入缓存队列，否则加入执行队列。<br>
 * Note:<br>
 * 整个KJHttp工作流程：采用责任链设计模式，由三部分组成，类似设计可以类比Handle...Looper...MessageQueue<br>
 * 1、KJHttp负责不停向NetworkQueue(或CacheQueue实际还是NetworkQueue， 具体逻辑请查看
 * {@link CacheDispatcher})添加Request<br>
 * 2、另一边由TaskThread不停从NetworkQueue中取Request并交给Network执行器(逻辑请查看
 * {@link NetworkDispatcher} )，<br>
 * 3、Network执行器将执行成功的NetworkResponse返回给TaskThead，并通过Request的定制方法
 * Request.parseNetworkResponse()封装成Response，最终交给分发器 Delivery
 * 分发到主线程并调用HttpCallback相应的方法
 *
 * @author kymjs (https://www.kymjs.com/)
 */
public class KJHttp {

    public interface ContentType {
        int FORM = 0;
        int JSON = 1;
    }

    // 请求缓冲区
    private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<>();
    // 请求的序列化生成器
    private final AtomicInteger mSequenceGenerator = new AtomicInteger();
    // 当前正在执行请求的线程集合
    private final Set<Request<?>> mCurrentRequests = new HashSet<>();
    // 执行缓存任务的队列.
    private final PriorityBlockingQueue<Request<?>> mCacheQueue = new
            PriorityBlockingQueue<>();
    // 需要执行网络请求的工作队列
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new
            PriorityBlockingQueue<>();
    // 请求任务执行池
    private final NetworkDispatcher[] mTaskThreads;
    // 缓存队列调度器
    private CacheDispatcher mCacheDispatcher;
    // 配置器
    private HttpConfig mConfig;

    public KJHttp() {
        this(null);
    }

    public KJHttp(HttpConfig config) {
        if (config == null) {
            config = new HttpConfig();
        }
        this.mConfig = config;
        mConfig.mController.setRequestQueue(this);
        mTaskThreads = new NetworkDispatcher[HttpConfig.NETWORK_POOL_SIZE];
        start();
    }

    public static class Builder {
        private String url;
        private HttpCallBack callBack;
        private HttpParams params;
        private boolean useCache;
        private int httpMethod;
        private int contentType;
        private HttpConfig httpConfig;

        public Builder config(HttpConfig httpConfig) {
            this.httpConfig = httpConfig;
            return this;
        }

        public Builder url(String url) {
            this.url = url;
            return this;
        }

        public Builder callback(HttpCallBack callBack) {
            this.callBack = callBack;
            return this;
        }

        public Builder params(HttpParams params) {
            this.params = params;
            return this;
        }

        public Builder useCache(boolean useCache) {
            this.useCache = useCache;
            return this;
        }

        public Builder httpMethod(int method) {
            this.httpMethod = method;
            return this;
        }

        public Builder contentType(int contentType) {
            this.contentType = contentType;
            return this;
        }

        public void request() {
            request(new KJHttp(httpConfig));
        }

        public void request(KJHttp kjHttp) {
            switch (httpMethod) {
                case HttpMethod.GET:
                    if (contentType == ContentType.FORM) {
                        kjHttp.get(url, params, useCache, callBack);
                    } else if (contentType == ContentType.JSON) {
                        kjHttp.jsonGet(url, params, useCache, callBack);
                    }
                    break;
                case HttpMethod.POST:
                    if (contentType == ContentType.FORM) {
                        kjHttp.post(url, params, useCache, callBack);
                    } else if (contentType == ContentType.JSON) {
                        kjHttp.jsonPost(url, params, useCache, callBack);
                    }
                    break;
                default:
                    if (contentType == ContentType.FORM) {
                        FormRequest request = new FormRequest(httpMethod, url, params, callBack);
                        request.setShouldCache(useCache);
                        kjHttp.doRequest(request);
                    } else if (contentType == ContentType.JSON) {
                        JsonRequest request = new JsonRequest(httpMethod, url, params, callBack);
                        request.setShouldCache(useCache);
                        kjHttp.doRequest(request);
                    }
                    break;
            }
        }
    }

    /**
     * 发起get请求
     *
     * @param url      地址
     * @param callback 请求中的回调方法
     */
    public Request<byte[]> get(String url, HttpCallBack callback) {
        return get(url, null, callback);
    }

    /**
     * 发起get请求
     *
     * @param url      地址
     * @param params   参数集
     * @param callback 请求中的回调方法
     */
    public Request<byte[]> get(String url, HttpParams params,
                               HttpCallBack callback) {
        if (params == null) params = new HttpParams();
        return get(url, params, true, callback);
    }

    /**
     * 发起get请求
     *
     * @param url      地址
     * @param params   参数集
     * @param callback 请求中的回调方法
     * @param useCache 是否缓存本条请求
     */
    public Request<byte[]> get(String url, HttpParams params, boolean useCache,
                               HttpCallBack callback) {
        if (params != null) {
            url += params.getUrlParams();
        }
        Request<byte[]> request = new FormRequest(HttpMethod.GET, url, params,
                callback);
        request.setShouldCache(useCache);
        doRequest(request);
        return request;
    }

    /**
     * 发起post请求
     *
     * @param url      地址
     * @param params   参数集
     * @param callback 请求中的回调方法
     */
    public Request<byte[]> post(String url, HttpParams params,
                                HttpCallBack callback) {
        return post(url, params, true, callback);
    }

    /**
     * 发起post请求
     *
     * @param url      地址
     * @param params   参数集
     * @param callback 请求中的回调方法
     * @param useCache 是否缓存本条请求
     */
    public Request<byte[]> post(String url, HttpParams params,
                                boolean useCache, HttpCallBack callback) {
        Request<byte[]> request = new FormRequest(HttpMethod.POST, url, params,
                callback);
        request.setShouldCache(useCache);
        doRequest(request);
        return request;
    }

    /**
     * 使用JSON传参的post请求
     *
     * @param url      地址
     * @param params   参数集
     * @param callback 请求中的回调方法
     */
    public Request<byte[]> jsonPost(String url, HttpParams params,
                                    HttpCallBack callback) {
        return jsonPost(url, params, true, callback);
    }

    /**
     * 使用JSON传参的post请求
     *
     * @param url      地址
     * @param params   参数集
     * @param callback 请求中的回调方法
     * @param useCache 是否缓存本条请求
     */
    public Request<byte[]> jsonPost(String url, HttpParams params,
                                    boolean useCache, HttpCallBack callback) {
        Request<byte[]> request = new JsonRequest(HttpMethod.POST, url, params,
                callback);
        request.setShouldCache(useCache);
        doRequest(request);
        return request;
    }

    /**
     * 使用JSON传参的get请求
     *
     * @param url      地址
     * @param params   参数集
     * @param callback 请求中的回调方法
     */
    public Request<byte[]> jsonGet(String url, HttpParams params,
                                   HttpCallBack callback) {
        Request<byte[]> request = new JsonRequest(HttpMethod.GET, url, params,
                callback);
        doRequest(request);
        return request;
    }

    /**
     * 使用JSON传参的get请求
     *
     * @param url      地址
     * @param params   参数集
     * @param callback 请求中的回调方法
     * @param useCache 是否缓存本条请求
     */
    public Request<byte[]> jsonGet(String url, HttpParams params,
                                   boolean useCache, HttpCallBack callback) {
        Request<byte[]> request = new JsonRequest(HttpMethod.GET, url, params,
                callback);
        request.setShouldCache(useCache);
        doRequest(request);
        return request;
    }

    /**
     * 下载
     *
     * @param storeFilePath 文件保存路径。注，必须是一个file路径不能是folder
     * @param url           下载地址
     * @param callback      请求中的回调方法
     */
    public DownloadTaskQueue download(String storeFilePath, String url,
                                      HttpCallBack callback) {
        FileRequest request = new FileRequest(storeFilePath, url, callback);
        request.setConfig(mConfig);
        mConfig.mController.add(request);
        return mConfig.mController;
    }

    /**
     * 尝试唤醒一个处于暂停态的下载任务(不推荐)
     *
     * @param storeFilePath 文件保存路径。注，必须是一个file路径不能是folder
     * @param url           下载地址
     * @deprecated 会造成莫名其妙的问题，建议直接再次调用download方法
     */
    @Deprecated
    public void resumeTask(String storeFilePath, String url) {
        DownloadController controller = mConfig.mController.get(storeFilePath,
                url);
        controller.resume();
    }

    /**
     * 返回下载总控制器
     *
     * @return 下载控制器
     */
    public DownloadController getDownloadController(String storeFilePath,
                                                    String url) {
        return mConfig.mController.get(storeFilePath, url);
    }

    public void cancleAll() {
        mConfig.mController.clearAll();
    }

    /**
     * 执行一个自定义请求
     *
     * @param request 要执行的自定义请求
     */
    public void doRequest(Request<?> request) {
        request.setConfig(mConfig);
        add(request);
    }

    /**
     * 获取缓存数据
     *
     * @param url 哪条url的缓存
     * @return 缓存的二进制数组
     */
    public byte[] getCache(String url) {
        Cache cache = HttpConfig.mCache;
        cache.initialize();
        Cache.Entry entry = cache.get(url);
        if (entry != null) {
            return entry.data;
        } else {
            return new byte[0];
        }
    }

    /**
     * 获取缓存数据
     *
     * @param url    哪条url的缓存
     * @param params http请求中的参数集(KJHttp的缓存会连同请求参数一起作为一个缓存的key)
     * @since 2.234以后有用
     */
    public byte[] getCache(String url, HttpParams params) {
        if (params != null) {
            url += params.getUrlParams();
        }
        return getCache(url);
    }

    /**
     * 只有你确定cache是一个String时才可以使用这个方法，否则还是应该使用getCache(String);
     *
     * @param url    url
     * @param params http请求中的参数集(KJHttp的缓存会连同请求参数一起作为一个缓存的key)
     */
    public String getStringCache(String url, HttpParams params) {
        if (params != null) {
            url += params.getUrlParams();
        }
        return new String(getCache(url));
    }

    /**
     * 只有你确定cache是一个String时才可以使用这个方法，否则还是应该使用getCache(String);
     */
    public String getStringCache(String url) {
        return new String(getCache(url));
    }

    /**
     * 移除一个缓存
     *
     * @param url 哪条url的缓存
     */
    public void removeCache(String url) {
        HttpConfig.mCache.remove(url);
    }

    /**
     * 清空缓存
     */
    public void cleanCache() {
        HttpConfig.mCache.clean();
    }

    public HttpConfig getConfig() {
        return mConfig;
    }


    public void setConfig(HttpConfig config) {
        this.mConfig = config;
    }

    /******************************** core method ****************************************/

    /**
     * 启动队列调度
     */
    private void start() {
        stop();// 首先关闭之前的运行，不管是否存在
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue,
                mConfig);
        mCacheDispatcher.start();
        // 构建线程池
        for (int i = 0; i < mTaskThreads.length; i++) {
            NetworkDispatcher tasker = new NetworkDispatcher(mNetworkQueue,
                    mConfig.mNetwork, HttpConfig.mCache, mConfig.mDelivery);
            mTaskThreads[i] = tasker;
            tasker.start();
        }
    }

    /**
     * 停止队列调度
     */
    private void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (NetworkDispatcher thread : mTaskThreads) {
            if (thread != null) {
                thread.quit();
            }
        }

    }

    public void cancel(String url) {
        synchronized (mCurrentRequests) {
            for (Request<?> request : mCurrentRequests) {
                if (url.equals(request.getTag())) {
                    request.cancel();
                }
            }
        }
    }

    /**
     * 取消全部请求
     */
    public void cancelAll() {
        synchronized (mCurrentRequests) {
            for (Request<?> request : mCurrentRequests) {
                request.cancel();
            }
        }
    }

    /**
     * 向请求队列加入一个请求
     * Note:此处工作模式是这样的：KJHttp可以看做是一个队列类，而本方法不断的向这个队列添加request；另一方面，
     * TaskThread不停的从这个队列中取request并执行。类似的设计可以参考Handle...Looper...MessageQueue的关系
     */
    public <T> Request<T> add(Request<T> request) {
        if (request.getCallback() != null) {
            request.getCallback().onPreStart();
        }

        // 标记该请求属于该队列，并将它添加到该组当前的请求。
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }
        // 设置进程优先序列
        request.setSequence(mSequenceGenerator.incrementAndGet());

        // 如果请求不可缓存，跳过缓存队列，直接进入网络。
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        // 如果已经在mWaitingRequests中有本请求，则替换
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {
                // There is already a request in flight. Queue up.
                Queue<Request<?>> stagedRequests = mWaitingRequests
                        .get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (HttpConfig.DEBUG) {
                    KJLoger.debug("Request for cacheKey=%s is in flight, putting on hold.",
                            cacheKey);
                }
            } else {
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }
    }

    /**
     * 将一个请求标记为已完成
     */
    public void finish(Request<?> request) {
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }

        if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
                if (waitingRequests != null) {
                    if (HttpConfig.DEBUG) {
                        KJLoger.debug("Releasing %d waiting requests for cacheKey=%s.",
                                waitingRequests.size(), cacheKey);
                    }
                    mCacheQueue.addAll(waitingRequests);
                }
            }
        }
    }

    public void destroy() {
        cancelAll();
        stop();
    }
}
