package leigh.elements.javafx.type1;

import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.TableView;
import javafx.scene.control.*;
import javafx.scene.control.cell.ProgressBarTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import leigh.elements.ElementBase;
import leigh.elements.sdk.AppScreenInstance;
import leigh.eo.EO;
import leigh.mecha.file.FileStat;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;
import org.apache.commons.io.FileUtils;

import java.awt.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

/**
 * This class provides an implementation of a drag&drop file upload/download panel.
 * <p>
 * This control maintains a list of the files in the <code>files</code> loop. Each file is presented
 * as a file entry EO.
 *
 * @author C. Alexader Leigh
 */
public class FilePanelJFX extends ElementBase implements ElementJFX {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(FilePanelJFX.class);
    private final ObservableList<FilePanelEntry> data = FXCollections.observableArrayList();
    private final int parallelUploads = 2;
    private VBox root = new VBox();
    private ToolBar tb = new ToolBar();
    private TableView<FilePanelEntry> fileTable;

    public FilePanelJFX(EO cfg, AppScreenInstance screen) {
        super(cfg);

        fileTable = new TableView<FilePanelEntry>();
        // To avoid bugs in TableView, set this right away.
        fileTable.setItems(data);
        TableColumn<FilePanelEntry, String> filenameCol = new TableColumn("Filename");
        filenameCol.setCellValueFactory(new PropertyValueFactory<FilePanelEntry, String>("filename"));
        filenameCol.setPrefWidth(300);
        fileTable.setMinHeight(200);
        fileTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

        Button galleryBtn = new Button("Gallery");
        tb.getItems().add(galleryBtn);
        Button deleteBtn = new Button("Delete");
        tb.getItems().add(deleteBtn);
        deleteBtn.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                ObservableList<FilePanelEntry> selectedRows = fileTable.getSelectionModel().getSelectedItems();
                // we don't want to iterate on same collection on with we remove items
                ArrayList<FilePanelEntry> rows = new ArrayList<>(selectedRows);
                rows.forEach(row -> fileTable.getItems().remove(row));
            }
        });

        TableColumn<FilePanelEntry, String> kindCol = new TableColumn("Kind");
        kindCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<FilePanelEntry, String>,
                ObservableValue<String>>() {
            @Override
            public ObservableValue<String> call(TableColumn.CellDataFeatures<FilePanelEntry, String> param) {
                return new ReadOnlyStringWrapper(param.getValue().getFileKind());
            }
        });
        kindCol.setPrefWidth(250);

        TableColumn<FilePanelEntry, Double> progressCol = new TableColumn("Size");
        progressCol.setCellValueFactory(new PropertyValueFactory<FilePanelEntry, Double>("progress"));
        progressCol.setCellFactory(forTableColumn());

        fileTable.setRowFactory(new Callback<TableView<FilePanelEntry>, TableRow<FilePanelEntry>>() {
            @Override
            public TableRow<FilePanelEntry> call(TableView<FilePanelEntry> param) {
                TableRow<FilePanelEntry> row = new TableRow<>();
                row.setOnMouseClicked(new EventHandler<MouseEvent>() {
                    @Override
                    public void handle(MouseEvent event) {
                        if (event.getClickCount() == 2 && (!row.isEmpty())) {
                            FilePanelEntry rowData = row.getItem();
                            try {
                                Desktop.getDesktop().open(rowData.getFile());
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            //    System.out.println(rowData);
                        }
                    }
                });
                return row;
            }
        });

        fileTable.getColumns().addAll(filenameCol, kindCol, progressCol);
        root.getChildren().addAll(tb, fileTable);
        final ObservableList<FilePanelEntry> data = FXCollections.observableArrayList();

        ExecutorService executor = Executors.newFixedThreadPool(parallelUploads, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setDaemon(true);
                return t;
            }
        });

        for (FilePanelEntry task : fileTable.getItems()) {
            executor.execute(task);
        }

        root.setOnDragOver(new EventHandler<DragEvent>() {
            @Override
            public void handle(DragEvent event) {
                if (event.getGestureSource() != root
                        && event.getDragboard().hasFiles()) {
                    /* we only allow for copying into the app */
                    event.acceptTransferModes(TransferMode.COPY);
                }
                event.consume();
            }
        });

        root.setOnDragDropped(new EventHandler<DragEvent>() {
            @Override
            public void handle(DragEvent event) {
                Dragboard db = event.getDragboard();
                boolean success = false;
                if (db.hasFiles()) {
                    for (File uploadFile : db.getFiles()) {
                        // Is the file a directory?
                        if (uploadFile.isDirectory()) {
                            try {
                                Files.walkFileTree(uploadFile.toPath(), new SimpleFileVisitor<Path>() {
                                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                                            throws IOException {
                                        if (dir.equals(root)) return FileVisitResult.CONTINUE;
                                        return FileVisitResult.CONTINUE;
                                    }

                                    @Override
                                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                                            throws IOException {
                                        // Note: This block is only called if we were iterating a directory
                                        logger.info("visitFile() called...");

                                        FilePanelEntry entry = new FilePanelEntry();
                                        File ul = file.toFile();

                                        UUID fileId = screen.getAppInstance().putBlob(new FileInputStream(uploadFile),
                                                new FileStat(uploadFile.getName(), null, uploadFile.length()));
                                        logger.info("Uploaded file. [id: {}]", fileId);

                                        entry.setId(fileId);
                                        entry.setFileSize(ul.length());
                                        entry.setFileType(Files.probeContentType(file));
                                        entry.setFilename(ul.getName());
                                        entry.setFile(ul);
                                        fileTable.getItems().add(entry);
                                        executor.execute(entry);

                                        return FileVisitResult.CONTINUE;
                                    }
                                });
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        } else {
                            // Note: This block is called for single file entries
                            FilePanelEntry entry = new FilePanelEntry();
                            UUID fileId = null;
                            try {
                                fileId = screen.getAppInstance().putBlob(new FileInputStream(uploadFile),
                                        new FileStat(uploadFile.getName(), null, uploadFile.length()));
                                logger.info("Uploaded file. [id: {}]", fileId);
                                entry.setId(fileId);
                                entry.setFileSize(uploadFile.length());
                                entry.setFile(uploadFile);
                                try {
                                    entry.setFileType(Files.probeContentType(uploadFile.toPath()));
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                                entry.setFilename(uploadFile.getName());
                                fileTable.getItems().add(entry);
                                executor.execute(entry);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                    success = true;
                }
                /* let the source know whether the string was successfully
                 * transferred and used */
                event.setDropCompleted(success);

                event.consume();
            }
        });
    }

    public Callback<TableColumn<FilePanelEntry, Double>,
            TableCell<FilePanelEntry, Double>> forTableColumn() {
        return new Callback<TableColumn<FilePanelEntry, Double>, TableCell<FilePanelEntry, Double>>() {
            @Override
            public TableCell<FilePanelEntry, Double> call(TableColumn<FilePanelEntry, Double> param) {
                logger.info("callback called");
                return new ProgressBarTableCell<FilePanelEntry>() {
                    public void updateItem(Double val, boolean empty) {
                        TableRow<FilePanelEntry> row = getTableRow();
                        FilePanelEntry entry = row.getItem();

                        if (entry == null) {
                            logger.warn("Event handler called, but row entry was null.");
                            return;
                        }

                        if (val != null && val == 1) {
                            EO fileEntry = new EO();
                            fileEntry.setValue("filename", entry.getFile());
                            fileEntry.setValue("kind", entry.getFileKind());
                            fileEntry.setValue("size", entry.getFileSize());
                            fileEntry.setValue("type", entry.getFileType());
                            logger.info("Cell is done. [cfg: {}] [entry: {}]", entry);
                            getCfg().getValueLoop("files").add(fileEntry);
                            logger.info("CFG: {}", getCfg());

                            // TODO Update this to the uploaded size
                            super.setGraphic(null);
                            setText(FileUtils.byteCountToDisplaySize(entry.getFileSize()));
                        } else {
                            super.updateItem(val, empty);
                        }
                    }
                };
            }
        };
    }

    @Override
    public Node getJavaFXNode() {
        return root;
    }
}