/*
 * Copyright 2014 biylda <[email protected]>
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU
 * General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program. If not,
 * see <http://www.gnu.org/licenses/>.
 */

package menion.android.whereyougo.network;

import android.content.Context;
import android.os.AsyncTask;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;

import menion.android.whereyougo.utils.FileSystem;
import menion.android.whereyougo.utils.Logger;
import menion.android.whereyougo.utils.Utils;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class DownloadCartridgeTask extends
        AsyncTask<String, DownloadCartridgeTask.Progress, Boolean> {
    private static final String TAG = "DownloadCartridgeTask";
    private static final String LOGIN = "https://www.wherigo.com/login/default.aspx";
    private static final String DOWNLOAD = "https://www.wherigo.com/cartridge/download.aspx";

    private final String username;
    private final String password;
    private OkHttpClient httpClient;
    private String errorMessage;

    public DownloadCartridgeTask(Context context, String username, String password) {
        super();
        this.username = username;
        this.password = password;
    }

    @Override
    protected Boolean doInBackground(String... arg0) {
        return init() && ping() && login() && download(arg0) && logout();
    }

    private boolean init() {
        try {
            System.setProperty("http.keepAlive", "false");
            httpClient = new OkHttpClient.Builder()
                    .sslSocketFactory(new TLSSocketFactory())
                    .cookieJar(new NonPersistentCookieJar())
                    .connectTimeout(30, TimeUnit.SECONDS)
                    .readTimeout(30, TimeUnit.SECONDS)
                    .writeTimeout(30, TimeUnit.SECONDS)
                    .build();
        } catch (KeyManagementException | NoSuchAlgorithmException e) {
            Logger.e(TAG, "init()", e);
            errorMessage = e.getMessage();
        }
        if (httpClient == null)
            publishProgress(new Progress(Task.INIT, State.FAIL, errorMessage));
        return httpClient != null;
    }

    private boolean ping() {
        Request request = new Request.Builder()
                .url(LOGIN)
                .build();
        return handleRequest(request, Task.PING) != null;
    }

    private boolean login() {
        RequestBody formBody = new FormBody.Builder()
                .add("__EVENTTARGET", "")
                .add("__EVENTARGUMENT", "")
                .add("ctl00$ContentPlaceHolder1$Login1$Login1$UserName", username)
                .add("ctl00$ContentPlaceHolder1$Login1$Login1$Password", password)
                .add("ctl00$ContentPlaceHolder1$Login1$Login1$LoginButton", "Sign In")
                .build();
        Request request = new Request.Builder()
                .url(LOGIN)
                .post(formBody)
                .build();
        publishProgress(new Progress(Task.LOGIN, State.WORKING));
        Response response = handleRequest(request);
        if (response != null && !LOGIN.equals(response.request().url().toString())) {
            publishProgress(new Progress(Task.LOGIN, State.SUCCESS));
            return true;
        } else {
            publishProgress(new Progress(Task.LOGIN, State.FAIL, errorMessage));
            return false;
        }
    }

    private boolean logout() {
        RequestBody formBody = new FormBody.Builder()
                .add("__EVENTTARGET", "ctl00$ProfileWidget$LoginStatus1$ctl00")
                .add("__EVENTARGUMENT", "")
                .build();
        Request request = new Request.Builder()
                .url(LOGIN)
                .post(formBody)
                .build();
        return handleRequest(request, Task.LOGOUT) != null;
    }

    private boolean download(String[] cguid) {
        publishProgress(new Progress(Task.DOWNLOAD, State.WORKING));
        for (int i = 0; i < cguid.length; ++i) {
            if (download(cguid[i])) {
                publishProgress(new Progress(Task.DOWNLOAD, i, cguid.length));
            } else {
                publishProgress(new Progress(Task.DOWNLOAD, State.FAIL, errorMessage));
                return false;
            }
        }
        return true;
    }

    private boolean download(String cguid) {
        RequestBody formBody = new FormBody.Builder()
                .add("__EVENTTARGET", "")
                .add("__EVENTARGUMENT", "")
                .add("ctl00$ContentPlaceHolder1$uxDeviceList", "4")
                .add("ctl00$ContentPlaceHolder1$btnDownload", "Download Now")
                .build();
        Request request = new Request.Builder()
                .url(DOWNLOAD + "?CGUID=" + cguid)
                .post(formBody)
                .build();
        Response response = handleRequest(request);
        if (response != null) {
            String type = response.body().contentType().toString();
            if ("application/octet-stream".equals(type)) {
                String contentDisposition = response.header("Content-Disposition", "");
                String pattern = "(?i)^ *attachment *; *filename *= *(.*) *$";
                String filename;
                if (contentDisposition.matches(pattern)) {
                    filename = cguid + "_" + contentDisposition.replaceFirst(pattern, "$1");
                } else {
                    filename = cguid + ".gwc";
                }
                long length = Long.parseLong(response.header("Content-Length", "0"));
                return download(filename, response.body().byteStream(), length);
            }
        }
        return false;
    }

    private boolean download(String filename, InputStream input, long total) {
        File file = new File(FileSystem.ROOT + filename);
        long completed = 0;
        int length;
        byte[] buffer = new byte[1024];
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            bis = new BufferedInputStream(input);
            bos = new BufferedOutputStream(new FileOutputStream(file));
            publishProgress(new Progress(Task.DOWNLOAD_SINGLE, completed, total));
            while ((length = bis.read(buffer)) > 0 && !isCancelled()) {
                bos.write(buffer, 0, length);
                completed += length;
                publishProgress(new Progress(Task.DOWNLOAD_SINGLE, completed, total));
            }
        } catch (IOException e) {
            Logger.e(TAG, "download(" + filename + ")", e);
            errorMessage = e.getMessage();
        } finally {
            Utils.closeStream(bis);
            Utils.closeStream(bos);
            if (completed != total) {
                file.delete();
            }
        }
        return completed == total;
    }

    private Response handleRequest(Request request, Task task) {
        publishProgress(new Progress(task, State.WORKING));
        Response response = handleRequest(request);
        if (response != null)
            publishProgress(new Progress(task, State.SUCCESS));
        else
            publishProgress(new Progress(task, State.FAIL, errorMessage));
        return response;
    }

    private Response handleRequest(Request request) {
        if (isCancelled())
            return null;
        Response response;
        try {
            response = httpClient.newCall(request).execute();
            if (!response.isSuccessful()) {
                throw new IOException("Request " + request.toString() + " failed: " + response);
            }
        } catch (Exception e) {
            Logger.e(TAG, "handleRequest(" + request.toString() + ")", e);
            errorMessage = e.getMessage();
            return null;
        }
        return response;
    }

    public enum Task {
        INIT, PING, LOGIN, DOWNLOAD, DOWNLOAD_SINGLE, LOGOUT
    }

    public enum State {
        WORKING, SUCCESS, FAIL
    }

    public static class Progress {
        final Task task;
        final State state;
        long total;
        long completed;
        String message;

        public Progress(Task task, State state) {
            this.task = task;
            this.state = state;
        }

        public Progress(Task task, State state, String message) {
            this.task = task;
            this.state = state;
            this.message = message;
        }

        public Progress(Task task, long completed, long total) {
            this.state = State.WORKING;
            this.task = task;
            this.total = total;
            this.completed = completed;
        }

        public Task getTask() {
            return task;
        }

        public State getState() {
            return state;
        }

        public String getMessage() {
            return message;
        }

        public long getTotal() {
            return total;
        }

        public long getCompleted() {
            return completed;
        }
    }
}