package mara.mybox.controller;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import mara.mybox.data.StringTable;
import mara.mybox.fxml.FxmlControl;
import static mara.mybox.fxml.FxmlControl.badStyle;
import mara.mybox.tools.DateTools;
import mara.mybox.tools.FileTools;
import mara.mybox.tools.HtmlTools;
import mara.mybox.tools.TextTools;
import mara.mybox.value.AppVariables;
import static mara.mybox.value.AppVariables.logger;
import static mara.mybox.value.AppVariables.message;
import mara.mybox.value.CommonValues;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZMethod;
import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
import org.apache.commons.compress.compressors.CompressorOutputStream;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.apache.commons.compress.utils.IOUtils;

/**
 * @Author Mara
 * @CreateDate 2019-11-2
 * @License Apache License Version 2.0
 */
// http://commons.apache.org/proper/commons-compress/examples.html
public class FilesArchiveCompressController extends FilesBatchController {

    protected String archiver, compressor, rootName, extension;
    protected ArchiveOutputStream archiveOut;
    protected SevenZOutputFile sevenZOutput;
    protected SevenZMethod sevenCompress;
    protected File archiveFile;
    protected List<ArchiveEntry> archive;
    protected long totalSize;

    @FXML
    protected ToggleGroup archiverGroup, compressGroup, sevenCompressGroup;
    @FXML
    protected TextField rootInput;
    @FXML
    protected ComboBox<String> encodeBox;
    @FXML
    protected VBox archiveVBox, compressVBox;
    @FXML
    protected Label archiverLabel;
    @FXML
    protected FlowPane sevenZCompressPane, commonCompressPane;
    @FXML
    protected RadioButton pack200Radio, gzRadio;
    @FXML
    protected CheckBox resultCheck;

    public FilesArchiveCompressController() {
        baseTitle = AppVariables.message("FilesArchiveCompress");
    }

    @Override
    public void initOptionsSection() {
        try {
            archiverGroup.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
                @Override
                public void changed(ObservableValue ov, Toggle oldv, Toggle newv) {
                    checkArchiver();
                }
            });
            checkArchiver();

            sevenCompressGroup.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
                @Override
                public void changed(ObservableValue ov, Toggle oldv, Toggle newv) {
                    checkSevenCompress();
                }
            });
            checkSevenCompress();

            compressGroup.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
                @Override
                public void changed(ObservableValue ov, Toggle oldv, Toggle newv) {
                    checkCompressor();
                }
            });
            checkCompressor();

            rootName = "";
            rootInput.textProperty().addListener(new ChangeListener<String>() {
                @Override
                public void changed(ObservableValue<? extends String> observable,
                        String oldValue, String newValue) {
                    rootName = rootInput.getText().trim();
                }
            });

            List<String> setNames = TextTools.getCharsetNames();
            encodeBox.getItems().addAll(setNames);
            encodeBox.getSelectionModel().select(
                    AppVariables.getUserConfigValue("FilesUnarchiveEncoding", Charset.defaultCharset().name()));

        } catch (Exception e) {

        }

    }

    protected void checkArchiver() {
        archiver = ((RadioButton) archiverGroup.getSelectedToggle()).getText();
        archiverLabel.setText("");
        sevenZCompressPane.setVisible(archiver.equalsIgnoreCase(ArchiveStreamFactory.SEVEN_Z));
        if (archiver.equalsIgnoreCase(ArchiveStreamFactory.AR)) {
            archiverLabel.setText(message("ARArchivesLimitation"));
        }
        pack200Radio.setDisable(!archiver.equalsIgnoreCase(ArchiveStreamFactory.ZIP)
                && !archiver.equalsIgnoreCase(ArchiveStreamFactory.JAR));
        if (pack200Radio.isDisabled() && pack200Radio.isSelected()) {
            gzRadio.fire();
        }
        checkExtension();
    }

    protected void checkSevenCompress() {
        String selected = ((RadioButton) sevenCompressGroup.getSelectedToggle()).getText();
        switch (selected) {
            case "LZMA2":
                sevenCompress = SevenZMethod.LZMA2;
                break;
            case "COPY":
                sevenCompress = SevenZMethod.COPY;
                break;
            case "DEFLATE":
                sevenCompress = SevenZMethod.DEFLATE;
                break;
            case "BZIP2":
                sevenCompress = SevenZMethod.BZIP2;
                break;
        }
    }

    protected void checkCompressor() {
        compressor = ((RadioButton) compressGroup.getSelectedToggle()).getText();
        checkExtension();
    }

    protected void checkExtension() {
        extension = archiver;
        if (compressor != null && !message("None").equals(compressor)) {
            switch (compressor) {
                case "bzip2":
                    extension = archiver + ".bz2";
                    break;
                case "pack200":
                    extension = archiver + ".pack";
                    break;
                case "lz4-block":
                case "lz4-framed":
                    extension = archiver + ".lz4";
                    break;
                case "snappy-framed":
                    extension = archiver + ".sz";
                    break;
                default:
                    extension = archiver + "." + compressor;
                    break;
            }

        }

        if (targetFile == null) {
            return;
        }
        String name = targetFile.getName();
        int pos = name.indexOf('.');
        if (pos >= 0) {
            name = name.substring(0, pos);
        }
        targetFileInput.setText(targetFile.getParent() + File.separator + name + "." + extension);
    }

    @Override
    public void initTargetSection() {
        super.initTargetSection();

        targetFileInput.textProperty().addListener(
                new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable,
                    String oldValue, String newValue) {
                try {
                    targetFile = new File(newValue);
                    targetFileInput.setStyle(null);
                    recordFileWritten(targetFile.getParent());
                    if (rootInput.getText().trim().isEmpty()) {
                        String name = targetFile.getName();
                        int pos = name.indexOf('.');
                        if (pos >= 0) {
                            name = name.substring(0, pos);
                        }
                        rootInput.setText(name);
                    }
                } catch (Exception e) {
                    targetFile = null;
                    targetFileInput.setStyle(badStyle);
                }
            }
        });

        openTargetButton.disableProperty().unbind();
        openTargetButton.disableProperty().bind(Bindings.
                isEmpty(targetFileInput.textProperty())
                .or(targetFileInput.styleProperty().isEqualTo(badStyle))
        );

        startButton.disableProperty().unbind();
        startButton.disableProperty().bind(Bindings.isEmpty(targetFileInput.
                textProperty())
                .or(targetFileInput.styleProperty().isEqualTo(badStyle))
                .or(Bindings.isEmpty(tableData))
        );

    }

    @Override
    public void selectTargetFileFromPath(File path) {
        try {
            final File file = chooseSaveFile(path, rootName + "." + extension,
                    null, true);
            if (file == null) {
                return;
            }
            selectTargetFile(file);
        } catch (Exception e) {
//            logger.error(e.toString());
        }
    }

    @Override
    public void selectTargetFile(File file) {
        try {
            if (file == null) {
                return;
            }
            targetFile = file;
            recordFileWritten(targetFile);

            if (targetFileInput != null) {
                targetFileInput.setText(targetFile.getAbsolutePath());
            }
        } catch (Exception e) {
//            logger.error(e.toString());
        }
    }

    @Override
    public void disableControls(boolean disable) {
        super.disableControls(disable);
        archiveVBox.setDisable(disable);
        compressVBox.setDisable(disable);
    }

    @Override
    public boolean beforeHandleFiles() {
        try {
            targetFile = makeTargetFile(
                    FileTools.getFilePrefix(targetFile.getName()),
                    "." + FileTools.getFileSuffix(targetFile.getName()),
                    targetFile.getParentFile());
            if (targetFile == null) {
                return false;
            }
            archiveFile = FileTools.getTempFile();
            if (archiver.equalsIgnoreCase(ArchiveStreamFactory.SEVEN_Z)) {
                sevenZOutput = new SevenZOutputFile(archiveFile);
                sevenZOutput.setContentCompression(sevenCompress);
            } else {
                ArchiveStreamFactory f = new ArchiveStreamFactory(
                        encodeBox.getSelectionModel().getSelectedItem());
                archiveOut = f.createArchiveOutputStream(
                        archiver, new FileOutputStream(archiveFile));
            }
            if (resultCheck.isSelected()) {
                archive = new ArrayList();
            } else {
                archive = null;
            }
            totalSize = 0;
            return true;
        } catch (Exception e) {
            logger.debug(e.toString());
            return false;
        }
    }

    @Override
    public String handleFile(File file) {
        try {
            if (!match(file)) {
                return AppVariables.message("Skip");
            }
            return addEntry(file, rootName);
        } catch (Exception e) {
            return AppVariables.message("Failed");
        }
    }

    public String addEntry(File file, String entryPath) {
        try {
            countHandling(file);
            String name;
            if (archiver.equalsIgnoreCase(ArchiveStreamFactory.AR)) {
                name = file.getName();
                if (name.length() > 16) {
                    return AppVariables.message("Skip");
                }
            } else if (entryPath == null || entryPath.trim().isEmpty()) {
                name = file.getName();
            } else {
                name = entryPath + "/" + file.getName();
            }
            if (archiver.equalsIgnoreCase(ArchiveStreamFactory.SEVEN_Z)) {
                SevenZArchiveEntry entry = sevenZOutput.createArchiveEntry(file, name);
                sevenZOutput.putArchiveEntry(entry);
                if (file.isFile()) {
                    try ( BufferedInputStream inputStream
                            = new BufferedInputStream(new FileInputStream(file))) {
                        int len;
                        byte[] buf = new byte[CommonValues.IOBufferLength];
                        while ((len = inputStream.read(buf)) >= 0) {
                            sevenZOutput.write(buf, 0, len);
                        }
                    }
                    totalSize += file.length();
                }
                sevenZOutput.closeArchiveEntry();
                if (archive != null) {
                    archive.add(entry);

                }
            } else {
                ArchiveEntry entry = archiveOut.createArchiveEntry(file, name);
                archiveOut.putArchiveEntry(entry);
                if (file.isFile()) {
                    try ( BufferedInputStream inputStream
                            = new BufferedInputStream(new FileInputStream(file))) {
                        IOUtils.copy(inputStream, archiveOut);
                    }
                    totalSize += file.length();
                }
                archiveOut.closeArchiveEntry();
                if (archive != null) {
                    archive.add(entry);
                }
            }
            return AppVariables.message("Successful");
        } catch (Exception e) {
            logger.debug(e.toString());
            return AppVariables.message("Failed");
        }

    }

    @Override
    public String handleDirectory(File dir) {
        try {
            if (archiver.equalsIgnoreCase(ArchiveStreamFactory.AR)) {
                return AppVariables.message("Skip");
            }
            dirFilesNumber = dirFilesHandled = 0;
            addEntry(dir, rootName);
            if (rootName == null || rootName.trim().isEmpty()) {
                handleDirectory(dir, dir.getName());
            } else {
                handleDirectory(dir, rootName + "/" + dir.getName());
            }
            return MessageFormat.format(AppVariables.message("DirHandledSummary"),
                    dirFilesNumber, dirFilesHandled);
        } catch (Exception e) {
            logger.debug(e.toString());
            return AppVariables.message("Failed");
        }
    }

    protected void handleDirectory(File sourcePath, String entryPath) {
        if (sourcePath == null || !sourcePath.exists() || !sourcePath.isDirectory()
                || (isPreview && dirFilesHandled > 0)) {
            return;
        }
        try {
            File[] files = sourcePath.listFiles();
            if (files == null) {
                return;
            }
            for (File srcFile : files) {
                if (task == null || task.isCancelled()) {
                    return;
                }
                if (srcFile.isFile()) {
                    dirFilesNumber++;
                    if (isPreview && dirFilesHandled > 0) {
                        return;
                    }
                    if (!match(srcFile)) {
                        continue;
                    }
                    String result = addEntry(srcFile, entryPath);
                    if (!AppVariables.message("Failed").equals(result)
                            && !AppVariables.message("Skip").equals(result)) {
                        dirFilesHandled++;
                    }
                } else if (srcFile.isDirectory() && sourceCheckSubdir) {
                    handleDirectory(srcFile, entryPath + "/" + srcFile.getName());
                }
            }
        } catch (Exception e) {
            logger.error(e.toString());
        }
    }

    @Override
    public void afterHandleFiles() {
        try {
            if (archiver.equalsIgnoreCase(ArchiveStreamFactory.SEVEN_Z)) {
                sevenZOutput.finish();
                sevenZOutput.close();
            } else {
                archiveOut.finish();
                archiveOut.close();
            }
            if (targetFile.exists()) {
                targetFile.delete();
            }
            if (!message("None").equals(compressor)) {
                File tmpFile = FileTools.getTempFile();
                try ( BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(archiveFile));
                      BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(tmpFile));
                      CompressorOutputStream compressOut = new CompressorStreamFactory().
                             createCompressorOutputStream(compressor, out)) {
                    IOUtils.copy(inputStream, compressOut);
                }
                tmpFile.renameTo(targetFile);
            } else {
                archiveFile.renameTo(targetFile);
            }
        } catch (Exception e) {
            logger.debug(e.toString());
        }
    }

    @Override
    public void donePost() {
        tableView.refresh();
        if (miaoCheck.isSelected()) {
            FxmlControl.miao3();
        }
        if (openCheck.isSelected()) {
            File path = targetFile.getParentFile();
            browseURI(path.toURI());
            recordFileOpened(path);
        }

        if (archive == null) {
            return;
        }

        StringBuilder s = new StringBuilder();
        s.append("<h1  class=\"center\">").append(targetFile).append("</h1>\n");
        s.append("<hr>\n");
        int ratio;
        if (totalSize > 0) {
            ratio = (int) (100 - targetFile.length() * 100 / totalSize);
        } else {
            ratio = 0;
        }
        String compressInfo = message("TotalSize") + ":"
                + FileTools.showFileSize(totalSize) + "&nbsp;&nbsp;&nbsp;"
                + message("SizeAfterArchivedCompressed") + ":"
                + FileTools.showFileSize(targetFile.length()) + "&nbsp;&nbsp;&nbsp;"
                + message("CompressedRatio") + ":" + ratio + "%";
        s.append("<P>").append(compressInfo).append("</P>\n");

        List<String> names = new ArrayList<>();
        names.addAll(Arrays.asList(message("ID"),
                message("Directory"), message("File"),
                message("Size"), message("ModifiedTime")
        ));
        StringTable table = new StringTable(names, message("ArchiveContents"));
        int id = 1;
        String dir, file, size;
        for (ArchiveEntry entry : archive) {
            List<String> row = new ArrayList<>();
            if (entry.isDirectory()) {
                dir = entry.getName();
                file = "";
            } else {
                int pos = entry.getName().lastIndexOf('/');
                if (pos < 0) {
                    dir = "";
                    file = entry.getName();
                } else {
                    dir = entry.getName().substring(0, pos);
                    file = entry.getName().substring(pos + 1, entry.getName().length());
                }
            }
            if (entry.getSize() > 0) {
                size = FileTools.showFileSize(entry.getSize());
            } else {
                size = "";
            }
            row.addAll(Arrays.asList((id++) + "", dir, file, size,
                    DateTools.datetimeToString(entry.getLastModifiedDate())
            ));
            table.add(row);
        }
        s.append(StringTable.tableDiv(table));

        HtmlTools.editHtml(message("ArchiveContents"), s.toString());
    }

}