package alexclin.httplite.okhttp3;

import java.io.IOException;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.CookieStore;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import alexclin.httplite.Handle;
import alexclin.httplite.HttpLiteBuilder;
import alexclin.httplite.LiteClient;
import alexclin.httplite.Request;
import alexclin.httplite.exception.CanceledException;
import alexclin.httplite.exception.IllegalOperationException;
import alexclin.httplite.listener.Callback;
import alexclin.httplite.listener.MediaType;
import alexclin.httplite.listener.Response;
import alexclin.httplite.util.ClientSettings;
import alexclin.httplite.util.Util;
import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.Call;
import okhttp3.CookieJar;
import okhttp3.Dispatcher;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;

/**
 * Ok3Lite
 *
 * @author alexclin 16/2/16 20:15
 */
public class Ok3Lite implements LiteClient {
    private final OkHttpClient mClient;
    private final Ok3Factory mFactory;

    private Ok3Lite(OkHttpClient client){
        mClient = client;
        mFactory = new Ok3Factory();
    }

    @Override
    public Response execute(Request request) throws Exception {
        Call call = mClient.newCall(makeRequest(request));
        request.handle().setHandle(new CallHandle(call));
        return new OkResponse(call.execute(),request);
    }

    @Override
    public void enqueue(final Request request,final Callback<Response> callback) {
        okhttp3.Callback okCallback = new okhttp3.Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Exception throwable;
                if("Canceled".equals(e.getMessage())){
                    throwable = new CanceledException(e);
                }else{
                    throwable = e;
                }
                callback.onFailed(request,throwable);
            }

            @Override
            public void onResponse(Call call, okhttp3.Response response) throws IOException {
                callback.onSuccess(request,response.headers().toMultimap(),new OkResponse(response,request));
            }
        };
        Call call = mClient.newCall(makeRequest(request));
        request.handle().setHandle(new CallHandle(call));
        call.enqueue(okCallback);
    }

    @Override
    public void cancel(Object tag) {
        List<Call> list = mClient.dispatcher().runningCalls();
        for(Call call:list){
            if(Util.equal(tag,call.request().tag())){
                call.cancel();
            }
        }
        list = mClient.dispatcher().queuedCalls();
        for(Call call:list){
            if(Util.equal(tag,call.request().tag())){
                call.cancel();
            }
        }
    }

    @Override
    public void cancelAll() {
        mClient.dispatcher().cancelAll();
    }

    @Override
    public void shutDown() {
        cancelAll();
        mClient.dispatcher().executorService().shutdown();
    }

    @Override
    public MediaType mediaType(String mediaType) {
        return OkMediaType.create(mediaType);
    }

    private okhttp3.Request makeRequest(Request real){
        okhttp3.Request.Builder rb = new okhttp3.Request.Builder().url(real.getUrl()).tag(real.getTag());
        Headers okHeader = createHeader(real.getHeaders());
        if(okHeader!=null) rb.headers(okHeader);
        alexclin.httplite.RequestBody liteBody = real.getRequestBody();
        RequestBody requestBody = null;
        if(liteBody!=null){
            requestBody = mFactory.convertBody(liteBody,real.getWrapListener());
        }
        switch (real.getMethod()){
            case GET:
                rb = rb.get();
                break;
            case POST:
                rb = rb.post(requestBody);
                break;
            case PUT:
                rb = rb.put(requestBody);
                break;
            case PATCH:
                rb = rb.patch(requestBody);
                break;
            case HEAD:
                rb = rb.head();
                break;
            case DELETE:
                if(real.getRequestBody()==null){
                    rb = rb.delete();
                }else{
                    rb = rb.delete(requestBody);
                }
                break;
        }
        if(real.getCacheExpiredTime()>0){
            rb.cacheControl(new CacheControl.Builder().maxAge(real.getCacheExpiredTime(), TimeUnit.SECONDS).build());
        }else if(real.getCacheExpiredTime()== alexclin.httplite.Request.FORCE_CACHE){
            rb.cacheControl(CacheControl.FORCE_CACHE);
        }else if(real.getCacheExpiredTime()== alexclin.httplite.Request.NO_CACHE){
            rb.cacheControl(CacheControl.FORCE_NETWORK);
        }
        return rb.build();
    }

    static Headers createHeader(Map<String, List<String>> headers){
        if(headers!=null&&!headers.isEmpty()){
            Headers.Builder hb = new Headers.Builder();
            for(String key:headers.keySet()){
                List<String> values = headers.get(key);
                for(String value:values){
                    hb.add(key,value);
                }
            }
            return hb.build();
        }
        return null;
    }

    private static class CallHandle implements Handle{
        private Call call;

        private CallHandle(Call call) {
            this.call = call;
        }

        @Override
        public void cancel() {
            call.cancel();
        }

        @Override
        public boolean isCanceled() {
            return call.isCanceled();
        }

        @Override
        public boolean isExecuted() {
            return call.isExecuted();
        }

        @Override
        public void setHandle(Handle handle) {
            throw new IllegalOperationException("not support method");
        }
    }

    public static final class Builder extends HttpLiteBuilder{
        private OkHttpClient client;
        private CookieJar cookieJar;

        public Builder(OkHttpClient client) {
            this.client = client;
        }

        public Builder() {
        }

        public Builder setCookieJar(CookieJar cookieJar) {
            this.cookieJar = cookieJar;
            return this;
        }

        public Builder setCookieStore(CookieStore cookieStore){
            if(cookieStore!=null)
                this.cookieJar = new CookieJarImpl(new CookieManager(cookieStore, CookiePolicy.ACCEPT_ALL));
            return this;
        }

        public Builder setCookieStore(CookieStore cookieStore, CookiePolicy policy){
            if(cookieStore!=null)
                this.cookieJar = new CookieJarImpl(new CookieManager(cookieStore, policy));
            return this;
        }

        @Override
        protected LiteClient initClient(ClientSettings settings) {
            OkHttpClient.Builder builder = client==null?new OkHttpClient.Builder():client.newBuilder();
            builder.followSslRedirects(settings.isFollowSslRedirects())
                    .followRedirects(settings.isFollowRedirects());
            if(settings.getSocketFactory() != null) builder.socketFactory(settings.getSocketFactory());
            if(settings.getSslSocketFactory() != null) builder.sslSocketFactory(settings.getSslSocketFactory());
            if(settings.getHostnameVerifier() != null) builder.hostnameVerifier(settings.getHostnameVerifier());
            if(settings.getProxySelector() != null) builder.proxySelector(settings.getProxySelector());
            if(settings.getProxy() != null) builder.proxy(settings.getProxy());
            if(settings.getRequestExecutor()!=null) builder.dispatcher(new Dispatcher(settings.getRequestExecutor()));
            builder.retryOnConnectionFailure(settings.getMaxRetryCount() > 0);
            builder.connectTimeout(settings.getConnectTimeout(), TimeUnit.MILLISECONDS);
            builder.readTimeout(settings.getReadTimeout(), TimeUnit.MILLISECONDS);
            builder.writeTimeout(settings.getWriteTimeout(), TimeUnit.MILLISECONDS);
            if(cookieJar!=null)
                builder.cookieJar(cookieJar);
            if (settings.getCacheDir() != null) {
                builder.cache(new Cache(settings.getCacheDir(), settings.getCacheMaxSize()));
            }
            client = builder.build();
            return new Ok3Lite(client);
        }
    }
}