//
//  FileDialog.qml
//
//  Created by Bradley Austin Davis on 14 Jan 2016
//  Copyright 2015 High Fidelity, Inc.
//
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//

import QtQuick 2.7
import Qt.labs.folderlistmodel 2.1
import Qt.labs.settings 1.0
import QtQuick.Dialogs 1.2 as OriginalDialogs
import QtQuick.Controls 1.4 as QQC1
import QtQuick.Controls 2.3

import ".."
import controlsUit 1.0
import stylesUit 1.0
import "../windows"

import "fileDialog"

//FIXME implement shortcuts for favorite location
ModalWindow {
    id: root
    resizable: true
    implicitWidth: 480
    implicitHeight: 360 + (fileDialogItem.keyboardEnabled && fileDialogItem.keyboardRaised ? keyboard.raisedHeight + hifi.dimensions.contentSpacing.y : 0)

    minSize: Qt.vector2d(360, 240)
    draggable: true

    HifiConstants { id: hifi }

    property var filesModel: ListModel { }

    Settings {
        category: "FileDialog"
        property alias width: root.width
        property alias height: root.height
        property alias x: root.x
        property alias y: root.y
    }


    // Set from OffscreenUi::getOpenFile()
    property alias caption: root.title;
    // Set from OffscreenUi::getOpenFile()
    property alias dir: fileTableModel.folder;
    // Set from OffscreenUi::getOpenFile()
    property alias filter: selectionType.filtersString;
    // Set from OffscreenUi::getOpenFile()
    property int options; // <-- FIXME unused

    property string iconText: root.title !== "" ? hifi.glyphs.scriptUpload : ""
    property int iconSize: 40

    property bool selectDirectory: false;
    property bool showHidden: true;
    // FIXME implement
    property bool multiSelect: false;
    property bool saveDialog: false;
    property var helper: fileDialogHelper
    property alias model: fileTableView.model
    property var drives: helper.drives()

    property int titleWidth: 0

    signal selectedFile(var file);
    signal canceled();
    signal selected(int button);
    function click(button) {
        clickedButton = button;
        selected(button);
        destroy();
    }
	
    property int clickedButton: OriginalDialogs.StandardButton.NoButton;
	
    Component.onCompleted: {
        fileDialogItem.keyboardEnabled = HMD.active;

        // HACK: The following lines force the model to initialize properly such that the go-up button
        // works properly from the initial screen.
        var initialFolder = folderListModel.folder;
        fileTableModel.folder = helper.pathToUrl(drives[0]);
        fileTableModel.folder = initialFolder;

        iconText = root.title !== "" ? hifi.glyphs.scriptUpload : "";

        // Clear selection when click on external frame.
        frameClicked.connect(function() { d.clearSelection(); });

        if (selectDirectory) {
            currentSelection.text = d.capitalizeDrive(helper.urlToPath(initialFolder));
            d.currentSelectionIsFolder = true;
            d.currentSelectionUrl = initialFolder;
        }

        helper.contentsChanged.connect(function() {
            if (folderListModel) {
                // Make folderListModel refresh.
                var save = folderListModel.folder;
                folderListModel.folder = "";
                folderListModel.folder = save;
            }
        });

        focusTimer.start();
    }
    
    Timer {
        id: focusTimer
        interval: 10
        running: false
        repeat: false
        onTriggered: {
            fileTableView.contentItem.forceActiveFocus();
        }
    }

    Item {
        id: fileDialogItem
        clip: true
        width: pane.width
        height: pane.height
        anchors.margins: 0

        property bool keyboardEnabled: false
        property bool keyboardRaised: false
        property bool punctuationMode: false

        MouseArea {
            // Clear selection when click on internal unused area.
            anchors.fill: parent
            drag.target: root
            onClicked: {
                d.clearSelection();
                // Defocus text field so that the keyboard gets hidden.
                // Clicking also breaks keyboard navigation apart from backtabbing to cancel 
                frame.forceActiveFocus();
            }
        }

        Row {
            id: navControls
            anchors {
                top: parent.top
                topMargin: hifi.dimensions.contentMargin.y
                left: parent.left
            }
            spacing: hifi.dimensions.contentSpacing.x

            GlyphButton {
                id: upButton
                glyph: hifi.glyphs.levelUp
                width: height
                size: 30
                enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== ""
                onClicked: d.navigateUp();
                Keys.onReturnPressed: { d.navigateUp(); }
                KeyNavigation.tab: homeButton
                KeyNavigation.backtab: upButton
                KeyNavigation.left: upButton
                KeyNavigation.right: homeButton
            }

            GlyphButton {
                id: homeButton
                property var destination: helper.home();
                glyph: hifi.glyphs.home
                size: 28
                width: height
                enabled: d.homeDestination ? true : false
                onClicked: d.navigateHome();
                Keys.onReturnPressed: { d.navigateHome(); }
                KeyNavigation.tab: fileTableView.contentItem
                KeyNavigation.backtab: upButton
                KeyNavigation.left: upButton
            }
        }

        ComboBox {
            id: pathSelector
            anchors {
                top: parent.top
                topMargin: hifi.dimensions.contentMargin.y
                left: navControls.right
                leftMargin: hifi.dimensions.contentSpacing.x
                right: parent.right
            }

            property var lastValidFolder: helper.urlToPath(fileTableModel.folder)

            function calculatePathChoices(folder) {
                var folders = folder.split("/"),
                    choices = [],
                    i, length;

                if (folders[folders.length - 1] === "") {
                    folders.pop();
                }

                choices.push(folders[0]);

                for (i = 1, length = folders.length; i < length; i++) {
                    choices.push(choices[i - 1] + "/" + folders[i]);
                }

                if (folders[0] === "") {
                    // Special handling for OSX root dir.
                    choices[0] = "/";
                }

                choices.reverse();

                if (drives && drives.length > 1) {
                    choices.push("This PC");
                }

                if (choices.length > 0) {
                    pathSelector.model = choices;
                }
            }

            onLastValidFolderChanged: {
                var folder = d.capitalizeDrive(lastValidFolder);
                calculatePathChoices(folder);
            }

            onCurrentTextChanged: {
                var folder = currentText;

                if (/^[a-zA-z]:$/.test(folder)) {
                    folder = "file:///" + folder + "/";
                } else if (folder === "This PC") {
                    folder = "file:///";
                } else {
                    folder = helper.pathToUrl(folder);
                }

                if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) {
                    if (root.selectDirectory) {
                        currentSelection.text = currentText !== "This PC" ? currentText : "";
                        d.currentSelectionUrl = helper.pathToUrl(currentText);
                    }
                    fileTableModel.folder = folder;
                }
            }

            KeyNavigation.up: fileTableView.contentItem
            KeyNavigation.down: fileTableView.contentItem
            KeyNavigation.tab: fileTableView.contentItem
            KeyNavigation.backtab: fileTableView.contentItem
            KeyNavigation.left: fileTableView.contentItem
            KeyNavigation.right: fileTableView.contentItem
        }

        QtObject {
            id: d
            property var currentSelectionUrl;
            readonly property string currentSelectionPath: helper.urlToPath(currentSelectionUrl);
            property bool currentSelectionIsFolder;
            property var backStack: []
            property var tableViewConnection: Connections { target: fileTableView; onCurrentRowChanged: d.update(); }
            property var modelConnection: Connections { target: fileTableModel; onFolderChanged: d.update(); }
            property var homeDestination: helper.home();

            function capitalizeDrive(path) {
                // Consistently capitalize drive letter for Windows.
                if (/[a-zA-Z]:/.test(path)) {
                    return path.charAt(0).toUpperCase() + path.slice(1);
                }
                return path;
            }

            function update() {
                var row = fileTableView.currentRow;

                if (row === -1) {
                    if (!root.selectDirectory) {
                        currentSelection.text = "";
                        currentSelectionIsFolder = false;
                    }
                    return;
                }

                currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath);
                currentSelectionIsFolder = fileTableView.model !== filesModel ?
                            fileTableView.model.isFolder(row) :
                            fileTableModel.isFolder(row);
                if (root.selectDirectory || !currentSelectionIsFolder) {
                    currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl));
                } else {
                    currentSelection.text = "";
                }
            }

            function navigateUp() {
                if (fileTableModel.parentFolder && fileTableModel.parentFolder !== "") {
                    fileTableModel.folder = fileTableModel.parentFolder;
                    return true;
                }
            }

            function navigateHome() {
                fileTableModel.folder = homeDestination;
                return true;
            }

            function clearSelection() {
                fileTableView.selection.clear();
                fileTableView.currentRow = -1;
                update();
            }
        }

        FolderListModel {
            id: folderListModel
            nameFilters: selectionType.currentFilter
            showDirsFirst: true
            showDotAndDotDot: false
            showFiles: !root.selectDirectory
            showHidden: root.showHidden
            Component.onCompleted: {
                showFiles = !root.selectDirectory
                showHidden = root.showHidden
            }

            onFolderChanged: {
                d.clearSelection();
                fileTableModel.update();  // Update once the data from the folder change is available.
            }

            function getItem(index, field) {
                return get(index, field);
            }
        }

        ListModel {
            // Emulates FolderListModel but contains drive data.
            id: driveListModel

            property int count: 1

            Component.onCompleted: initialize();

            function initialize() {
                var drive,
                    i;

                count = drives.length;

                for (i = 0; i < count; i++) {
                    drive = drives[i].slice(0, -1);  // Remove trailing "/".
                    append({
                       fileName: drive,
                       fileModified: new Date(0),
                       fileSize: 0,
                       filePath: drive + "/",
                       fileIsDir: true,
                       fileNameSort: drive.toLowerCase()
                    });
                }
            }

            function getItem(index, field) {
                return get(index)[field];
            }
        }

        Component {
            id: filesModelBuilder
            ListModel { }
        }

        QtObject {
            id: fileTableModel

            // FolderListModel has a couple of problems:
            // 1) Files and directories sort case-sensitively: https://bugreports.qt.io/browse/QTBUG-48757
            // 2) Cannot browse up to the "computer" level to view Windows drives: https://bugreports.qt.io/browse/QTBUG-42901
            //
            // To solve these problems an intermediary ListModel is used that implements proper sorting and can be populated with
            // drive information when viewing at the computer level.

            property var folder
            property int sortOrder: Qt.AscendingOrder
            property int sortColumn: 0
            property var model: folderListModel
            property string parentFolder: calculateParentFolder();

            readonly property string rootFolder: "file:///"

            function calculateParentFolder() {
                if (model === folderListModel) {
                    if (folderListModel.parentFolder.toString() === "" && driveListModel.count > 1) {
                        return rootFolder;
                    } else {
                        return folderListModel.parentFolder;
                    }
                } else {
                    return "";
                }
            }

            onFolderChanged: {
                if (folder === rootFolder) {
                    model = driveListModel;
                    helper.monitorDirectory("");
                    update();
                } else {
                    var needsUpdate = model === driveListModel && folder === folderListModel.folder;

                    model = folderListModel;
                    folderListModel.folder = folder;
                    helper.monitorDirectory(helper.urlToPath(folder));

                    if (needsUpdate) {
                        update();
                    }
                }
            }

            function isFolder(row) {
                if (row === -1) {
                    return false;
                }
                return filesModel.get(row).fileIsDir;
            }

            function get(row) {
                return filesModel.get(row)
            }

            function update() {
                var dataFields = ["fileName", "fileModified", "fileSize"],
                    sortFields = ["fileNameSort", "fileModified", "fileSize"],
                    dataField = dataFields[sortColumn],
                    sortField = sortFields[sortColumn],
                    sortValue,
                    fileName,
                    fileIsDir,
                    comparisonFunction,
                    lower,
                    middle,
                    upper,
                    rows = 0,
                    i;

                filesModel = filesModelBuilder.createObject(root);

                comparisonFunction = sortOrder === Qt.AscendingOrder
                    ? function(a, b) { return a < b; }
                    : function(a, b) { return a > b; }

                for (i = 0; i < model.count; i++) {
                    fileName = model.getItem(i, "fileName");
                    fileIsDir = model.getItem(i, "fileIsDir");

                    sortValue = model.getItem(i, dataField);
                    if (dataField === "fileName") {
                        // Directories first by prefixing a "*".
                        // Case-insensitive.
                        sortValue = (fileIsDir ? "*" : "") + sortValue.toLowerCase();
                    }

                    lower = 0;
                    upper = rows;
                    while (lower < upper) {
                        middle = Math.floor((lower + upper) / 2);
                        var lessThan;
                        if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) {
                            lessThan = true;
                            upper = middle;
                        } else {
                            lessThan = false;
                            lower = middle + 1;
                        }
                    }

                    filesModel.insert(lower, {
                       fileName: fileName,
                       fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
                       fileSize: model.getItem(i, "fileSize"),
                       filePath: model.getItem(i, "filePath"),
                       fileIsDir: fileIsDir,
                       fileNameSort: (fileIsDir ? "*" : "") + fileName.toLowerCase()
                    });

                    rows++;
                }
            }
        }

        Table {
            id: fileTableView
            colorScheme: hifi.colorSchemes.light
            anchors {
                top: navControls.bottom
                topMargin: hifi.dimensions.contentSpacing.y
                left: parent.left
                right: parent.right
                bottom: currentSelection.top
                bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
            }
            headerVisible: !selectDirectory
            onDoubleClicked: navigateToRow(row);
            Keys.onReturnPressed: navigateToCurrentRow();
            Keys.onEnterPressed: navigateToCurrentRow();

            sortIndicatorColumn: 0
            sortIndicatorOrder: Qt.AscendingOrder
            sortIndicatorVisible: true

            model: filesModel

            function updateSort() {
                fileTableModel.sortOrder = sortIndicatorOrder;
                fileTableModel.sortColumn = sortIndicatorColumn;
                fileTableModel.update();
            }

            onSortIndicatorColumnChanged: { updateSort(); }

            onSortIndicatorOrderChanged: { updateSort(); }

            itemDelegate: Item {
                clip: true

                FiraSansSemiBold {
                    text: getText();
                    elide: styleData.elideMode
                    anchors {
                        left: parent.left
                        leftMargin: hifi.dimensions.tablePadding
                        right: parent.right
                        rightMargin: hifi.dimensions.tablePadding
                        verticalCenter: parent.verticalCenter
                    }
                    size: hifi.fontSizes.tableText
                    color: hifi.colors.baseGrayHighlight
                    font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir)
                        ? "Fira Sans SemiBold" : "Fira Sans"

                    function getText() {
                        if (styleData.row === -1) {
                            return styleData.value;
                        }

                        switch (styleData.column) {
                            case 1: return fileTableView.model.get(styleData.row).fileIsDir ? "" : styleData.value;
                            case 2: return fileTableView.model.get(styleData.row).fileIsDir ? "" : formatSize(styleData.value);
                            default: return styleData.value;
                        }
                    }
                    function formatSize(size) {
                        var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
                        var suffixIndex = 0
                        while ((size / 1024.0) > 1.1) {
                            size /= 1024.0;
                            ++suffixIndex;
                        }

                        size = Math.round(size*1000)/1000;
                        size = size.toLocaleString()

                        return size + " " + suffixes[suffixIndex];
                    }
                }
            }

            QQC1.TableViewColumn {
                id: fileNameColumn
                role: "fileName"
                title: "Name"
                width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width
                movable: false
                resizable: true
            }
            QQC1.TableViewColumn {
                id: fileModifiedColumn
                role: "fileModified"
                title: "Date"
                width: 0.3 * fileTableView.width
                movable: false
                resizable: true
                visible: !selectDirectory
            }
            QQC1.TableViewColumn {
                role: "fileSize"
                title: "Size"
                width: fileTableView.width - fileNameColumn.width - fileModifiedColumn.width
                movable: false
                resizable: true
                visible: !selectDirectory
            }

            function navigateToRow(row) {
                currentRow = row;
                navigateToCurrentRow();
            }

            function navigateToCurrentRow() {
                var currentModel = fileTableView.model !== filesModel ? fileTableView.model : fileTableModel
                var row = fileTableView.currentRow
                var isFolder = currentModel.isFolder(row);
                var file = currentModel.get(row).filePath;
                if (isFolder) {
                    currentModel.folder = helper.pathToUrl(file);
                } else {
                    okAction.trigger();
                }
            }

            property string prefix: ""

            function addToPrefix(event) {
                if (!event.text || event.text === "") {
                    return false;
                }
                var newPrefix = prefix + event.text.toLowerCase();
                var matchedIndex = -1;
                for (var i = 0; i < model.count; ++i) {
                    var name = model !== filesModel ? model.get(i).fileName.toLowerCase() :
                                                      filesModel.get(i).fileName.toLowerCase();
                    if (0 === name.indexOf(newPrefix)) {
                        matchedIndex = i;
                        break;
                    }
                }

                if (matchedIndex !== -1) {
                    fileTableView.selection.clear();
                    fileTableView.selection.select(matchedIndex);
                    fileTableView.currentRow = matchedIndex;
                    fileTableView.prefix = newPrefix;
                }
                prefixClearTimer.restart();
                return true;
            }

            Timer {
                id: prefixClearTimer
                interval: 1000
                repeat: false
                running: false
                onTriggered: fileTableView.prefix = "";
            }

            Keys.onPressed: {
                switch (event.key) {
                case Qt.Key_Backspace:
                case Qt.Key_Tab:
                case Qt.Key_Backtab:
                    event.accepted = false;
                    break;
                case Qt.Key_Escape:
                    event.accepted = true;
                    root.click(OriginalDialogs.StandardButton.Cancel);
                    break;
                default:
                    if (addToPrefix(event)) {
                        event.accepted = true
                    } else {
                        event.accepted = false;
                    }
                    break;
                }
            }

            KeyNavigation.tab: root.saveDialog ? currentSelection : openButton
        }

        TextField {
            id: currentSelection
            label: selectDirectory ? "Directory:" : "File name:"
            anchors {
                left: parent.left
                right: selectionType.visible ? selectionType.left: parent.right
                rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0
                bottom: keyboard.top
                bottomMargin: hifi.dimensions.contentSpacing.y
            }
            readOnly: !root.saveDialog
            activeFocusOnTab: !readOnly
            onActiveFocusChanged: if (activeFocus) { selectAll(); }
            onAccepted: okAction.trigger();
            KeyNavigation.up: fileTableView.contentItem
            KeyNavigation.down: openButton
            KeyNavigation.tab: openButton
            KeyNavigation.backtab: fileTableView.contentItem
        }

        FileTypeSelection {
            id: selectionType
            anchors {
                top: currentSelection.top
                left: buttonRow.left
                right: parent.right
            }
            visible: !selectDirectory && filtersCount > 1
        }

        Keyboard {
            id: keyboard
            raised: parent.keyboardEnabled && parent.keyboardRaised
            numeric: parent.punctuationMode
            anchors {
                left: parent.left
                right: parent.right
                bottom: buttonRow.top
                bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0
            }
        }

        Row {
            id: buttonRow
            anchors {
                right: parent.right
                bottom: parent.bottom
            }
            spacing: hifi.dimensions.contentSpacing.y

            Button {
                id: openButton
                color: hifi.buttons.blue
                action: okAction
                Keys.onReturnPressed: okAction.trigger()
                KeyNavigation.right: cancelButton
                KeyNavigation.up: root.saveDialog ? currentSelection : fileTableView.contentItem
                KeyNavigation.tab: cancelButton
            }

            Button {
                id: cancelButton
                action: cancelAction
                Keys.onReturnPressed: { cancelAction.trigger() }
                KeyNavigation.left: openButton
                KeyNavigation.up: root.saveDialog ? currentSelection : fileTableView.contentItem
                KeyNavigation.backtab: openButton
            }
        }

        Action {
            id: okAction
            text: currentSelection.text ? (root.selectDirectory && fileTableView.currentRow === -1 ? "Choose" : (root.saveDialog ? "Save" : "Open")) : "Open"
            enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false
            onTriggered: {
                if (!root.selectDirectory && !d.currentSelectionIsFolder
                        || root.selectDirectory && fileTableView.currentRow === -1) {
                    okActionTimer.start();
                } else {
                    fileTableView.navigateToCurrentRow();
                }
            }
        }

        Timer {
            id: okActionTimer
            interval: 50
            running: false
            repeat: false
            onTriggered: {
                if (!root.saveDialog) {
                    selectedFile(d.currentSelectionUrl);
                    root.destroy()
                    return;
                }

                // Handle the ambiguity between different cases
                // * typed name (with or without extension)
                // * full path vs relative vs filename only
                var selection = helper.saveHelper(currentSelection.text, root.dir, selectionType.currentFilter);

                if (!selection) {
                    desktop.messageBox({ icon: OriginalDialogs.StandardIcon.Warning, text: "Unable to parse selection" })
                    return;
                }

                if (helper.urlIsDir(selection)) {
                    root.dir = selection;
                    currentSelection.text = "";
                    return;
                }

                // Check if the file is a valid target
                if (!helper.urlIsWritable(selection)) {
                    desktop.messageBox({
                                           icon: OriginalDialogs.StandardIcon.Warning,
                                           text: "Unable to write to location " + selection
                                       })
                    return;
                }

                if (helper.urlExists(selection)) {
                    var messageBox = desktop.messageBox({
                                                            icon: OriginalDialogs.StandardIcon.Question,
                                                            buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No,
                                                            text: "Do you wish to overwrite " + selection + "?",
                                                        });
                    var result = messageBox.exec();
                    if (OriginalDialogs.StandardButton.Yes !== result) {
                        return;
                    }
                }

                selectedFile(selection);
                root.destroy();
            }
        }

        Action {
            id: cancelAction
            text: "Cancel"
            onTriggered: { canceled(); root.shown = false; }
        }
    }

    Keys.onPressed: {
        switch (event.key) {
        case Qt.Key_Backspace:
            event.accepted = d.navigateUp();
            break;

        case Qt.Key_Home:
            event.accepted = d.navigateHome();
            break;
		
        case Qt.Key_Escape:
            event.accepted = true;
            root.click(OriginalDialogs.StandardButton.Cancel);
            break;
		}
    }
}