/*
 * Copyright (c) 2018 LingoChamp Inc.
 *
 * 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 com.liulishuo.filedownloader.connection;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.liulishuo.filedownloader.util.FileDownloadHelper;
import com.liulishuo.okdownload.IRedirectHandler;
import com.liulishuo.okdownload.OkDownload;
import com.liulishuo.okdownload.RedirectUtil;
import com.liulishuo.okdownload.core.Util;
import com.liulishuo.okdownload.core.connection.DownloadConnection;

import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.net.ProtocolException;
import java.util.List;
import java.util.Map;

public class DownloadConnectionAdapter implements DownloadConnection, DownloadConnection.Connected {

    @NonNull
    private final FileDownloadConnection fileDownloadConnection;
    @NonNull
    private final IRedirectHandler redirectHandler;
    @Nullable
    private DownloadConnectionAdapter redirectConnectAdapter;
    @NonNull
    private String requestMethod = "GET";

    private static final String TAG = "DownloadConnectionAdapter";

    public DownloadConnectionAdapter(
            @NonNull FileDownloadConnection fileDownloadConnection,
            @NonNull IRedirectHandler redirectHandler) {
        this.fileDownloadConnection = fileDownloadConnection;
        this.redirectHandler = redirectHandler;
    }

    @Override
    public void addHeader(String name, String value) {
        fileDownloadConnection.addHeader(name, value);
    }

    @Override
    public boolean setRequestMethod(@NonNull String method) throws ProtocolException {
        requestMethod = method;
        return fileDownloadConnection.setRequestMethod(method);
    }

    @Override
    public Connected execute() throws IOException {
        final Map<String, List<String>> headerProperties = getRequestProperties();
        fileDownloadConnection.execute();
        redirectHandler.handleRedirect(this, this, headerProperties);
        return this;
    }

    @Override
    public void release() {
        if (redirectConnectAdapter != null) {
            redirectConnectAdapter.release();
        } else {
            fileDownloadConnection.ending();
        }
    }

    @Override
    public Map<String, List<String>> getRequestProperties() {
        return fileDownloadConnection.getRequestHeaderFields();
    }

    /**
     * {@link FileDownloadConnection} don't support this method.
     *
     * @param key property key
     */
    @Override
    public String getRequestProperty(String key) {
        return "unknown";
    }

    @Override
    public int getResponseCode() throws IOException {
        if (redirectConnectAdapter != null) {
            return redirectConnectAdapter.getResponseCode();
        }
        return fileDownloadConnection.getResponseCode();
    }

    @Override
    public InputStream getInputStream() throws IOException {
        if (redirectConnectAdapter != null) {
            return redirectConnectAdapter.getInputStream();
        }
        return fileDownloadConnection.getInputStream();
    }

    @Nullable
    @Override
    public Map<String, List<String>> getResponseHeaderFields() {
        if (redirectConnectAdapter != null) {
            return redirectConnectAdapter.getResponseHeaderFields();
        }
        return fileDownloadConnection.getResponseHeaderFields();
    }

    @Nullable
    @Override
    public String getResponseHeaderField(String name) {
        if (redirectConnectAdapter != null) {
            return redirectConnectAdapter.getResponseHeaderField(name);
        }
        return fileDownloadConnection.getResponseHeaderField(name);
    }

    @Override
    public String getRedirectLocation() {
        return redirectHandler.getRedirectLocation();
    }

    static final class RedirectHandler implements IRedirectHandler {

        String redirectLocation;

        @Override
        public void handleRedirect(
                DownloadConnection originalConnection,
                Connected originalConnected,
                Map<String, List<String>> headerProperties) throws IOException {
            if (!(originalConnection instanceof DownloadConnectionAdapter)) return;
            final String requestMethod =
                    ((DownloadConnectionAdapter) originalConnection).requestMethod;
            int responseCode = originalConnected.getResponseCode();
            int redirectCount = 0;
            DownloadConnectionAdapter redirectConnectAdapter = null;
            DownloadConnection checkingDownloadConnection = originalConnection;
            while (RedirectUtil.isRedirect(responseCode)) {
                checkingDownloadConnection.release();
                if (++redirectCount > RedirectUtil.MAX_REDIRECT_TIMES) {
                    throw new ProtocolException("Too many redirect requests: " + redirectCount);
                }

                redirectLocation = RedirectUtil.getRedirectedUrl(originalConnected, responseCode);
                final DownloadConnection redirectConnection = OkDownload.with()
                        .connectionFactory().create(redirectLocation);
                if (redirectConnection instanceof DownloadConnectionAdapter) {
                    Util.addRequestHeaderFields(headerProperties, redirectConnection);
                    redirectConnection.setRequestMethod(requestMethod);
                    redirectConnectAdapter = (DownloadConnectionAdapter) redirectConnection;
                    Util.d(TAG, "connect redirect location with method: " + requestMethod);
                    redirectConnectAdapter.fileDownloadConnection.execute();
                    responseCode = redirectConnectAdapter.getResponseCode();
                    checkingDownloadConnection = redirectConnection;
                } else {
                    redirectLocation = null;
                    throw new InvalidClassException("The connection factory is customized, "
                            + "but now the factory creates a inconsistent connection: "
                            + redirectConnection.getClass().getCanonicalName());
                }
            }
            if (redirectConnectAdapter != null && redirectLocation != null) {
                DownloadConnectionAdapter originalAdapter =
                        ((DownloadConnectionAdapter) originalConnection);
                originalAdapter.redirectConnectAdapter = redirectConnectAdapter;
            }
        }

        @Nullable
        @Override
        public String getRedirectLocation() {
            return redirectLocation;
        }
    }

    public static class Factory implements DownloadConnection.Factory {

        @NonNull
        private final FileDownloadHelper.ConnectionCreator creator;

        public Factory(@NonNull FileDownloadHelper.ConnectionCreator creator) {
            this.creator = creator;
        }

        @Override
        public DownloadConnection create(String url) throws IOException {
            final FileDownloadConnection fileDownloadConnection = creator.create(url);
            return new DownloadConnectionAdapter(fileDownloadConnection, new RedirectHandler());
        }
    }
}