package de.sebse.fuplanner.services.kvv;

import android.content.Context;
import android.os.Build;
import android.os.Environment;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.FileOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;

import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.services.kvv.types.Resource;
import de.sebse.fuplanner.tools.Regex;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;

public class ModulesResources extends PartModules<ArrayList<Resource>> {

    ModulesResources(Login login, ModulesList list, Context context) {
        super(login, list, context);
    }

    @Override
    protected ArrayList<Resource> getPart(Modules.Module module) {
        return module.resources;
    }

    @Override
    protected boolean setPart(Modules.Module module, ArrayList<Resource> part) {
        boolean changed = module.resources == null || module.resources.hashCode() != part.hashCode();
        module.resources = part;
        return changed;
    }

    @Override
    protected void upgrade(final String ID, final NetworkCallback<ArrayList<Resource>> callback, final NetworkErrorCallback errorCallback) {
        if (!mLogin.isInOnlineMode() || mLogin.getLoginToken() == null) {
            errorCallback.onError(new NetworkError(101604, 500, "Currently running in offline mode!"));
            return;
        }
        get(String.format("https://kvv.imp.fu-berlin.de/direct/content/site/%s.json", ID), mLogin.getLoginToken().getCookies(), response -> {
            String body = response.getParsed();
            if (body == null) {
                errorCallback.onError(new NetworkError(101601, 403, "No resources retrieved!"));
                return;
            }
            ArrayList<Resource> resources = new ArrayList<>();
            JSONArray sites;
            try {
                JSONObject json = new JSONObject(body);
                sites = json.getJSONArray("content_collection");
            } catch (JSONException e) {
                e.printStackTrace();
                errorCallback.onError(new NetworkError(101602, 403, "Cannot parse resources!"));
                return;
            }

            for (int i = 0; i < sites.length(); i++) {
                try {
                    JSONObject site = sites.getJSONObject(i);
                    String author = site.getString("author");
                    String title = site.getString("title");
                    //long modifiedDate = site.getLong("modifiedDate");"20181018155405439"
                    long modifiedDate = UtilsDate.stringToMillis(site.getString("modifiedDate"), "yyyyMMddHHmmssSSS");
                    String url = site.getString("url");
                    boolean visible = site.getBoolean("visible");
                    String type = site.getString("type");
                    String container = site.getString("container");
                    if (type.equals("collection")){
                        resources.add(new Resource.Folder(author, title, modifiedDate, url, visible, container));
                    }
                    else {
                        resources.add(new Resource.File(author, title, modifiedDate, url, visible, container, type));
                    }
                } catch (JSONException e) {
                    log.e(new NetworkError(101605, 403, "Cannot parse resources!"));
                    e.printStackTrace();
                    log.e("ID:", i, "JSON:", sites);
                    return;
                }
            }

            ArrayList<Resource> root = new ArrayList<>();
            // Generate folder structure
            for (Resource res: resources) {
                if (!res.getContainer().equals("/content/group/")) {
                    if (res.getContainer().equals("/content/group/"+ID+"/")){
                        // if file in root folder
                        root.add(res);
                    } else {
                        // in sub folder
                        for (Resource res2: resources) {
                            try {
                                String utf8Name;
                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
                                    utf8Name = StandardCharsets.UTF_8.name();
                                else utf8Name = "UTF-8";
                                if (URLDecoder.decode(res2.getUrl(), utf8Name).endsWith(res.getContainer()) && res2 instanceof Resource.Folder) {
                                    // Append File/Folder to list
                                    ((Resource.Folder) res2).add(res);
                                }
                            } catch (UnsupportedEncodingException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }

            // Empty resources *may be* because token is invalid -> check
            if (resources.size() == 0)
                mLogin.testLoginToken(token -> callback.onResponse(root), errorCallback);
            else
                callback.onResponse(root);
        }, error -> errorCallback.onError(new NetworkError(101603, error.networkResponse.statusCode, "Cannot get resources!")));
    }











    public void file(final String filename, final String url, final String modulename, final NetworkCallback<String> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
        file(filename, url, modulename, callback, errorCallback, forceRefresh, RETRY_COUNT);
    }

    private void file(final String filename, final String url, final String modulename, final NetworkCallback<String> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh, int retries) {
        if (isExternalStorageReadable()){
            File f = new File(Environment.getExternalStoragePublicDirectory(
                    Environment.DIRECTORY_DOWNLOADS)+"/"+modulename+"/"+filename);
            // check if file already downloaded -> do not download again
            if (f.exists() && !forceRefresh) {
                callback.onResponse(f.getPath());
                return;
            }
        }
        fileUpgrade(filename, url , modulename, callback, error -> {
            if (retries >= 0 && (error.getHttpStatus() == 401 || error.getHttpStatus() == 403)) {
                mLogin.refreshLogin(success -> {
                    file(filename, url, modulename, callback, errorCallback, forceRefresh, retries-1);
                }, errorCallback);
                return;
            }
            errorCallback.onError(error);
        });
    }

    private void fileUpgrade(String filename, String url, String modulename, final NetworkCallback<String> callback, final NetworkErrorCallback errorCallback) {
        if (!mLogin.isInOnlineMode() || mLogin.getLoginToken() == null) {
            errorCallback.onError(new NetworkError(101604, 500, "Currently running in offline mode!"));
            return;
        }
        get(url, mLogin.getLoginToken().getCookies(), response -> {
            if (Regex.has("\\.[Uu][Rr][Ll]$", url)){
                // Return redirected URL
                String path = response.getHeaders().get("Location");
                if (path == null){
                    path = "";
                }
                callback.onResponse(path);
            } else if (response.getBytes() == null) {
                errorCallback.onError(new NetworkError(101705, 403, "Cannot get file!"));
            } else if (isExternalStorageWritable()) {
                String path = saveFileInDownloads(filename, response.getBytes(), modulename);
                callback.onResponse(path);
            } else {
                errorCallback.onError(new NetworkError(101704, 403, "External storage not writable!"));
            }
        }, error -> errorCallback.onError(new NetworkError(101702, error.networkResponse.statusCode, "Cannot get file!")));


    }







    /* Checks if external storage is available for read and write */
    private boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        log.w("File system: Writing not possible!");
        return false;
    }

    /* Checks if external storage is available to at least read */
    private boolean isExternalStorageReadable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) ||
                Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        log.w("File system: Reading not possible!");
        return false;
    }
    private String saveFileInDownloads(String filename, byte[] data, String moduleName) {
        // Saves file in folder: DOWNLOADS/moduleName
        File folder = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_DOWNLOADS), moduleName);
        if (!folder.mkdir()) {
            log.w( "Directory not created");
        }
        String path = "";
        try {
            // TODO check if enough storage space is available
            FileOutputStream out = new FileOutputStream(folder.getPath()+"/"+filename);
            out.write(data);
            out.close();
            path = folder.getPath()+"/"+filename;
        } catch (Exception e) {
            log.w("File not saved!");
            e.printStackTrace();
        }
        return path;
    }
}
