package com.quaap.bookymcbookface;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Environment;
import android.util.Log;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * Copyright (C) 2017   Tom Kliethermes
 *
 * This file is part of BookyMcBookface and is 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.
 */

public class FsTools {

    private final Context mContext;

    public FsTools(Context context) {
        mContext = context;
    }

    private Map<File,String> getDrives() {
        List<File> roots = new ArrayList<>();
        roots.add(new File("/storage"));
        //roots.add(new File("/mnt"));

        try {
            roots.addAll(Arrays.asList(mContext.getExternalFilesDirs(null)));
        } catch (Throwable t) {
            Log.e("storage", t.getMessage(), t);
        }


        Map<File,String> files = new LinkedHashMap<>();

        for(File r: roots) {
            try {
                int sd = 1;
                if (r!=null) {
                    for (File e : r.listFiles()) {
                        try {
                            Log.d("storage", e.getPath() + " " + e.isDirectory());
                            if (e.isDirectory()) { // && !e.getName().equals("emulated") && !e.getName().equals("self")) {
                                boolean removable = false;
                                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                                    removable = Environment.isExternalStorageRemovable(e);
                                }
                                String name = "SD";
                                if (sd++ > 1) name += sd;
                                files.put(e, removable ? name : e.getName());
                                //Log.d("storage", name + " " + e.getPath());
                            }
                        } catch (IllegalArgumentException ex) {
                            Log.d("storage", e.getPath() + " is no good");
                        } catch (Throwable t) {
                            Log.e("storage", t.getMessage(), t);
                        }

                    }
                }
            } catch (Exception e) {
                Log.e("storage", e.getMessage(), e);
            }
        }


        File ext = Environment.getExternalStorageDirectory();
        if (Environment.isExternalStorageEmulated()) {
            files.put(ext, "Internal");
        } else if (Environment.isExternalStorageRemovable()) {
            files.put(ext, "SD");
        }

        return files;
    }

    private Map<File,String> listDirsInDir(File extdir, final String matchRE, final boolean onlyDirs) {
        Map<File,String> allfiles = new LinkedHashMap<>();

        Map<File,String> drives = getDrives();

        if (extdir==null) {
            return drives;
        }

        List<File> dirs;
        if (!extdir.exists() || !extdir.isDirectory()) {
            dirs = new ArrayList<>();
        } else {
            FilenameFilter filterdirs = new FilenameFilter() {
                @Override
                public boolean accept(File dir, String filename) {
                    File sel = new File(dir, filename);
                    return sel.isDirectory();
                }

            };

            try {
                dirs = new ArrayList<>(Arrays.asList(extdir.listFiles(filterdirs)));
            } catch (Exception e) {
                Log.d("FsTools", e.getMessage(), e);
                dirs = new ArrayList<>();
            }
        }

        Collections.sort(dirs, new Comparator<File>() {
            @Override
            public int compare(File s, File t1) {
                return s.getName().compareToIgnoreCase(t1.getName());
            }
        });

        if (extdir.getParent()!=null) {
            if (drives.keySet().contains(extdir)) {
                allfiles.put(null, "← ..");
            } else {
                allfiles.put(extdir.getParentFile(), "← ..");
            }
        }

        for (File dir: dirs) {
            allfiles.put(dir, "\uD83D\uDCC1 " + dir.getName());
        }


        if (!onlyDirs) {
            FilenameFilter filterfiles = new FilenameFilter() {

                @Override
                public boolean accept(File dir, String filename) {
                    File sel = new File(dir, filename);
                    return sel.isFile() && (matchRE == null || filename.matches(matchRE));
                }

            };

            List<File> files;
            if (!extdir.exists() || !extdir.isDirectory()) {
                files = new ArrayList<>();
            } else {
                try {
                    files = new ArrayList<>(Arrays.asList(extdir.listFiles(filterfiles)));
                } catch (Exception e) {
                    Log.d("FsTools", e.getMessage(), e);
                    files = new ArrayList<>();
                }
            }

            Collections.sort(files, new Comparator<File>() {
                @Override
                public int compare(File s, File t1) {
                    return s.getName().compareToIgnoreCase(t1.getName());
                }
            });

            for(File file: files) {
                allfiles.put(file, "\uD83D\uDCC4 " + file.getName());
            }
        }

        return allfiles;

    }

    public void selectExternalLocation(final SelectionMadeListener listener, String title, boolean chooseDir) {
        selectExternalLocation(listener, title, null, chooseDir, null);
    }

    public void selectExternalLocation(final SelectionMadeListener listener, String title, boolean chooseDir, String matchRE) {
        selectExternalLocation(listener, title, null, chooseDir, matchRE);
    }


    private void selectExternalLocation(final SelectionMadeListener listener, final String title, final String startdir, final boolean chooseDir, final String matchRE) {

        String dname = startdir==null ? "" : startdir;

        Map<File,String> listDirsInDir = listDirsInDir(startdir==null?null:new File(startdir), matchRE, chooseDir);

        final File [] fileItems = new File[listDirsInDir.size()];
        final String [] showItems  = new String[listDirsInDir.size()];

        int i=0;
        for (Map.Entry<File,String> entry: listDirsInDir.entrySet()) {
            fileItems[i] = entry.getKey();
            showItems[i] = entry.getValue();

            //Log.d("storage2", fileItems[i] + " " + showItems[i]);
            i++;
        }

        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);

        builder.setTitle(title + "\n" + dname);

        builder.setItems(showItems, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                dialogInterface.dismiss();

                try {
                    File selFile = fileItems[i];
                    if (selFile==null) {
                        selectExternalLocation(listener, title, null, chooseDir, matchRE);
                    } else if (selFile.isDirectory()) {
                        selectExternalLocation(listener, title, selFile.getPath(), chooseDir, matchRE);
                    } else {
                        listener.selected(selFile.getCanonicalFile());
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        });
        if (chooseDir && startdir!=null) {
            builder.setPositiveButton(R.string.select_thisfolder, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {

                    listener.selected(new File(startdir));
                }
            });
        }
        builder.setNegativeButton(R.string.cancel, null);

        builder.show();
        //Log.d("storage3", "Here!");
    }


    public interface SelectionMadeListener {
        void selected(File selection);
    }


    public static boolean deleteDir(File dir) {

        if (dir.exists()) {
            File[] files = dir.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        deleteDir(file);
                    } else {
                        file.delete();
                    }
                }
            }
        }
        return dir.delete();
    }

    public static File copyFileToDir(File srcFile, File destDir){
        if (!destDir.isDirectory()) throw new IllegalArgumentException("Destination must be a directory");

        return copyFile(srcFile, new File(destDir,srcFile.getName()));
    }

    private static File copyFile(File srcFile, File destFile) {
        return copyFile(srcFile, destFile, false, false);
    }

    public static File compressFile(File srcFile, File destFile) {
        return copyFile(srcFile, destFile, true, false);
    }


    public static File decompressFile(File srcFile, File destFile) {
        return copyFile(srcFile, destFile, false, true);
    }



    private static File copyFile(File srcFile, File destFile, boolean compress, boolean decompress){
        if (destFile.exists() && !destFile.isFile()) throw new IllegalArgumentException("Destination must be a normal file");
        try {

            InputStream fis = new FileInputStream(srcFile);

            try {
                if (decompress) {
                    fis = new GZIPInputStream(fis);
                }

                OutputStream output = new FileOutputStream(destFile);

                try {
                    if (compress) {
                        output = new GZIPOutputStream(output);
                    }

                    // Transfer bytes from the inputfile to the outputfile
                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = fis.read(buffer)) > 0) {
                        output.write(buffer, 0, length);
                    }

                    output.flush();
                    return destFile;
                } finally {
                    try {output.close();} catch (Exception e) {Log.e("Fs",e.getMessage(),e);}
                }
            } finally {
                try {fis.close();} catch (Exception e) {Log.e("Fs",e.getMessage(),e);}
            }

        } catch (IOException e) {
            Log.e("DB", "Copy failed", e);
        }
        return null;
    }

    /**
     *
     * @param files first files are source, last file is destination file.
     * @return
     */
    public static File compressFiles(File destFile, File ... files) {

        if (files.length<1) throw new IllegalArgumentException("Need at least one source file and exactly one destination file.");
        if (destFile.exists() && !destFile.isFile()) throw new IllegalArgumentException("Destination must be a normal file");

        try {
            ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(destFile));
            try {

                for (int i=0; i< files.length; i++) {
                    InputStream fis = new FileInputStream(files[i]);
                    try {

                        zout.putNextEntry(new ZipEntry(files[i].getName()));

                        byte[] buffer = new byte[1024];
                        int length;
                        while ((length = fis.read(buffer)) > 0) {
                            zout.write(buffer, 0, length);
                        }


                        zout.closeEntry();

                    } finally {
                        try {fis.close();} catch (Exception e) {Log.e("Fs",e.getMessage(),e);}
                    }

                }
            } finally {
                try {zout.close();} catch (Exception e) {Log.e("Fs",e.getMessage(),e);}
            }


        } catch (IOException e) {
            Log.e("DB", "Copy failed", e);
        }
        return destFile;
    }


    public static List<File> uncompressFiles(File srcFile, File destDir) {

        List<File> files = new ArrayList<>();


        try {
            ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(srcFile));
            try {
                ZipEntry zipEntry;
                while ((zipEntry = zipInputStream.getNextEntry()) !=null) {
                    File destFile = new File(destDir, zipEntry.getName());

                    if (!destFile.getParentFile().exists()) {
                        if (!destFile.getParentFile().mkdirs()) continue;
                    }
                    if (zipEntry.isDirectory()) {
                        continue;
                    }

                    files.add(destFile);

                    OutputStream zout = new FileOutputStream(destFile);
                    try {

                        byte[] buffer = new byte[1024];
                        int length;
                        while ((length = zipInputStream.read(buffer)) > 0) {
                            zout.write(buffer, 0, length);
                        }

                    } finally {
                        try {zout.close();} catch (Exception e) {Log.e("Fs",e.getMessage(),e);}
                    }

                }
            } finally {
                try {zipInputStream.close();} catch (Exception e) {Log.e("Fs",e.getMessage(),e);}
            }


        } catch (IOException e) {
            Log.e("DB", "Copy failed", e);
        }
        return files;
    }

    /*
    public static Map<String,CharSequence> extractFilesAsString(File srcFile, String ... searchFiles) {

        Map<String,CharSequence> files = new HashMap<>();

        List<String> searchFilesList = Arrays.asList(searchFiles);

        try {
            ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(srcFile));
            try {
                ZipEntry zipEntry;
                while ((zipEntry = zipInputStream.getNextEntry()) !=null) {
                    String fname = zipEntry.getName();
                    Log.d("FsTools", "zip entry " + fname);

                    if (zipEntry.isDirectory()) {
                        continue;
                    }

                    if (searchFilesList.contains(fname)) {
                        StringBuilder buff = new StringBuilder(4096);

                        BufferedReader reader = new BufferedReader(new InputStreamReader(zipInputStream));

                        String line;
                        while ( (line =reader.readLine())!=null) {
                            buff.append(line);
                            buff.append(System.lineSeparator());
                        }
                        files.put(fname, buff);
                    }

                }
            } finally {
                try {zipInputStream.close();} catch (Exception e) {Log.e("Fs",e.getMessage(),e);}
            }


        } catch (IOException e) {
            Log.e("FsTools", "extract failed", e);
        }
        return null;
    }


    public static File saveSharedPreferencesToFile(SharedPreferences pref, File destFile) {

        ObjectOutputStream output = null;
        try {
            output = new ObjectOutputStream(new FileOutputStream(destFile));

            output.writeObject(pref.getAll());

        } catch (IOException e) {
            Log.e("FsTools", e.getMessage(), e);
        } finally {
            try {
                if (output != null) {
                    output.flush();
                    output.close();
                }
            } catch (IOException e) {
                Log.e("FsTools", e.getMessage(), e);
            }
        }
        return destFile;
    }


    @SuppressWarnings({"unchecked"})
    public static boolean loadSharedPreferencesFromFile(SharedPreferences pref, File src) {
        boolean res = false;
        ObjectInputStream input = null;
        try {
            input = new ObjectInputStream(new FileInputStream(src));
            SharedPreferences.Editor prefEdit = pref.edit();
            prefEdit.clear();

            Map<String, ?> entries = (Map<String, ?>) input.readObject();
            for (Map.Entry<String, ?> entry : entries.entrySet()) {
                Object v = entry.getValue();
                String key = entry.getKey();

                if (v instanceof Boolean) {
                    prefEdit.putBoolean(key, (Boolean) v);
                } else if (v instanceof Float) {
                    prefEdit.putFloat(key, (Float) v);
                } else if (v instanceof Integer) {
                    prefEdit.putInt(key, (Integer) v);
                } else if (v instanceof Long) {
                    prefEdit.putLong(key, (Long) v);
                } else if (v instanceof String) {
                    prefEdit.putString(key, ((String) v));
                } else if (v instanceof Set) {
                    prefEdit.putStringSet(key, ((Set<String>) v));
                } else {
                    Log.d("FsTools", "unknown type for '" + key + "': " + v.getClass());
                }

            }
            if (prefEdit.commit()) {
                Log.d("FsTools", "prefs updated");
            }
            res = true;
        } catch (IOException | ClassNotFoundException e) {
            Log.e("FsTools", e.getMessage(), e);
        } finally {
            try {
                if (input != null) {
                    input.close();
                }
            } catch (IOException e) {
                Log.e("FsTools", e.getMessage(), e);
            }
        }
        return res;
    }
*/
}