//
//  ScriptAPI.qml
//
//  Created by Luis Cuenca on 12/18/2017
//  Copyright 2017 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.5
import QtQuick.Controls 1.4
import "styles-uit"
import "controls-uit" as HifiControls

Item {
    id: root
    width: parent.width
    height: parent.height

    property var hideQtMethods: true
    property var maxUpdateValues: 20
    property var maxReloadValues: 200
    property var apiMembers: []
    property var membersWithValues: []
    property var isReloading: false
    property var evaluatingIdx: -1
    property Component scrollSlider
    property Component keyboard
    
    Rectangle {
        color: hifi.colors.baseGray
        width: parent.width
        height: parent.height
    }
    
    FontLoader { id: ralewayRegular; source: pathToFonts + "fonts/Raleway-Regular.ttf"; }

    Timer {
        id: updateList
        interval: 200
        repeat: false
        running: false
        onTriggered: {
            scrollSlider.y = 0;
            list.contentY = 0;
        }
    }

    Row {
        id: topBar
        anchors.left: parent.left
        anchors.leftMargin: 30
        anchors.top: parent.top
        anchors.topMargin: 30
        width: parent.width
        height: 40

        HifiControls.GlyphButton {
            id: back;
            enabled: true;
            color: hifi.buttons.black
            glyph: hifi.glyphs.backward
            size: 40
            width: 40
            height: 40
            anchors.left: search.right
            anchors.leftMargin: 12
            onClicked: {
                var text = searchBar.text;
                var chain = text.split(".");
                if (chain.length > 2) {
                    var result = chain[0]+".";
                    for (var i = 1; i < chain.length-2; i++) {
                        result += chain[i] + ".";
                    }
                    result += chain[chain.length-2];
                    searchBar.text = result;
                } else {
                    searchBar.text = (chain.length > 1) ? chain[0] : "";
                }
                if (chain.length > 1) {
                    addListElements(searchBar.text);
                } else {
                    addListElements();
                }
                focus = true;
            }
        }

        HifiControls.TextField {
            id: searchBar
            focus: true
            isSearchField: true
            width: parent.width - 112
            height: 40
            colorScheme: hifi.colorSchemes.dark
            anchors.left: back.right
            anchors.leftMargin: 10
            font.family: firaSansSemiBold.name
            placeholderText: "Search"
            onAccepted: {
                console.log("Enter Pressed");
                addListElements(searchBar.text);
            }
            onActiveFocusChanged: {
                if (activeFocus && HMD.mounted) {
                    keyboard.raised = true;
                } else {
                    keyboard.raised = false;
                }
            }

        }
    }

    Row {
        id: topBar2
        anchors.left: parent.left
        anchors.leftMargin: 30
        anchors.top: topBar.bottom
        anchors.topMargin: 30
        width: parent.width -60
        height: 40

        HifiControls.GlyphButton {
            id: addMember;
            enabled: true;
            color: hifi.buttons.black
            glyph: hifi.glyphs.maximize
            width: 40
            height: 40
            anchors.top: parent.top
            anchors.left: parent.left
            onClicked: {
                addNewMember();
                updateList.start();
                focus = true;
            } 
        }

        HifiControls.Button {
            id: evaluate;
            enabled: true;
            color: hifi.buttons.black
            text: "Eval"
            width: 40
            height: 40
            anchors.left: addMember.right
            anchors.leftMargin: 12
            onClicked: {
                evaluateMember();
                focus = true;
            } 
        }

        HifiControls.TextField {
            id: valueBar
            isSearchField: false
            font.pixelSize: 16
            width: parent.width - 208
            height: 40
            colorScheme: hifi.colorSchemes.dark
            font.family: firaSansSemiBold.name
            placeholderText: "Value"
            anchors.left: evaluate.right
            anchors.leftMargin: 12
            onActiveFocusChanged: {
                if (activeFocus && HMD.mounted) {
                    keyboard.raised = true;
                } else {
                    keyboard.raised = false;
                }
            }
        }

        HifiControls.GlyphButton {
            id: reload;
            enabled: false;
            color: hifi.buttons.black
            glyph: hifi.glyphs.reload
            size: 40
            width: 40
            height: 40
            anchors.right: update.left
            anchors.rightMargin: 12
            onClicked: {
                reloadListValues();
                focus = true;
            } 
        }
        
        HifiControls.GlyphButton {
            id: update;
            enabled: false;
            color: hifi.buttons.black
            glyph: hifi.glyphs.playback_play
            size: 40
            width: 40
            height: 40
            anchors.right: parent.right
            onClicked: {
                if (isReloading) {
                    update.glyph = hifi.glyphs.playback_play
                    isReloading = false;
                    stopReload();
                } else {
                    update.glyph = hifi.glyphs.stop_square
                    isReloading = true;
                    startReload();
                }
                focus = true;
            } 
        }
    }

    Rectangle {
        id: membersBackground
        anchors {
            left: parent.left; right: parent.right; top: topBar2.bottom; bottom: bottomBar.top;
            margins: 30
        }
        color: hifi.colors.tableBackgroundDark
        border.color: hifi.colors.lightGray
        border.width: 2
        radius: 5

        ListModel {
            id: memberModel
        }

        Component {
            id: memberDelegate
            Item {
                id: item
                width: parent.width
                anchors.left: parent.left
                height: 26
                clip: true

                Rectangle {
                    width: parent.width
                    height: parent.height
                    color: index % 2 == 0 ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd
                    anchors.verticalCenter: parent.verticalCenter

                    Row {
                        id: memberRow
                        anchors.bottom: parent.bottom
                        anchors.verticalCenter: parent.verticalCenter
                        spacing: 10

                        FiraSansSemiBold {
                            property var isMainKey: apiType === "class";
                            text: apiMember
                            size: isMainKey ? 17 : 15
                            font.bold: true
                            anchors.verticalCenter: parent.verticalCenter
                            color: isMainKey ? hifi.colors.faintGray : hifi.colors.lightGrayText
                            MouseArea {
                                width: list.width
                                height: parent.height
                                onClicked: {
                                    searchBar.text = apiType=="function()" ? apiMember + "()" : apiMember;
                                    valueBar.text = !apiValue ? "" : apiValue;
                                    list.currentIndex = index;
                                    evaluatingIdx = index;
                                }
                                onDoubleClicked: {
                                    if (apiType === "class") {
                                        addListElements(apiMember+".");
                                    } else {
                                        isolateElement(evaluatingIdx);
                                    }
                                }
                            }
                        }

                        FiraSansRegular {
                            text: apiType
                            anchors.left: apiMember.right
                            anchors.verticalCenter: parent.verticalCenter
                            size: 13
                            color: hifi.colors.lightGrayText
                        }

                        FiraSansRegular {
                            text: !apiValue ? "" : apiValue;
                            anchors.left: apiType.right
                            anchors.verticalCenter: parent.verticalCenter
                            size: 14
                            color: hifi.colors.primaryHighlight
                        }
                    }
                }
            }
        }

        Component {
            id: highlight
            Rectangle {
                anchors {
                    left: list.left
                    right: scrollBar.left
                    leftMargin: 2
                    rightMargin: 2
                }
                color: hifi.colors.primaryHighlight
                radius: 4
                z: 10
                opacity: 0.5
            }
        }

        ListView {
            id: list
            anchors {
                top: parent.top
                left: parent.left
                right: scrollBar.left
                bottom: parent.bottom
                topMargin: 2
                leftMargin: 2
                bottomMargin: 2
            }
            clip: true
            cacheBuffer: 4000
            model: memberModel
            delegate: memberDelegate
            highlightMoveDuration: 0
            highlight: highlight
            onMovementStarted: {
                scrollSlider.manual = true;
            }
            onMovementEnded: {
                if (list.contentHeight > list.height) {
                    var range = list.contentY/(list.contentHeight-list.height);
                    range = range > 1 ? 1 : range;
                    var idx = Math.round((list.count-1)*range);
                    scrollSlider.positionSlider(idx);
                }
                scrollSlider.manual = false;
                returnToBounds()
            }
        }

        Rectangle {
            id: scrollBar

            property bool scrolling: list.contentHeight > list.height

            anchors {
                top: parent.top
                right: parent.right
                bottom: parent.bottom
                margins: 2
            }
            width: 22
            height: parent.height - 4
            color: hifi.colors.tableScrollBackgroundDark

            MouseArea {
                anchors.fill: parent

                onClicked: {
                    var index = scrollSlider.y * (list.count - 1) / (scrollBar.height - scrollSlider.height);
                    index = Math.round(index);
                    var scrollAmount = Math.round(list.count/10);
                    index = index + (mouse.y <= scrollSlider.y ? -scrollAmount : scrollAmount);
                    if (index < 0) {
                        index = 0;
                    }
                    if (index > list.count - 1) {
                        index = list.count - 1;
                    }
                    scrollSlider.positionSlider(index);
                }
            }

            Rectangle {
                id: scrollSlider

                property var manual: false

                function positionSlider(index){
                    y = index*(scrollBar.height - scrollSlider.height)/(list.count - 1);
                }

                anchors.right: parent.right
                anchors.margins: 2
                width: 18
                height: ((list.height / list.contentHeight) * list.height) < 15 ? 15 : (list.height / list.contentHeight) * list.height
                radius: 5
                color: hifi.colors.tableScrollHandleDark

                visible: scrollBar.scrolling;

                onYChanged: {
                    var index = y * (list.count - 1) / (scrollBar.height - scrollSlider.height);
                    index = Math.round(index);
                    if (!manual) {
                        list.positionViewAtIndex(index, ListView.Visible);
                    } 
                }

                MouseArea {
                    anchors.fill: parent
                    drag.target: scrollSlider
                    drag.axis: Drag.YAxis
                    drag.minimumY: 0
                    drag.maximumY: scrollBar.height - scrollSlider.height
                }
            }
        }
    }

    Row {
        id: bottomBar
        anchors.left: parent.left
        anchors.leftMargin: 30
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 30
        width: parent.width
        height: 40

        HifiControls.GlyphButton {
            id: clipboard;
            enabled: true;
            color: hifi.buttons.black
            glyph: hifi.glyphs.scriptNew
            size: 25
            width: 40
            height: 40
            anchors.left: parent.left
            onClicked: {
                var buffer = "";
                for (var i = 0; i < memberModel.count; i++) {
                    var datarow = memberModel.get(i);
                    buffer += "\n" + datarow.apiMember + "    " + datarow.apiType + "    " + datarow.apiValue;
                }
                Window.copyToClipboard(buffer);
                focus = true;
            } 
        }

        HifiControls.CheckBox {
            id: hideQt
            colorScheme: hifi.checkbox.dark
            boxSize: 25
            boxRadius: 3
            checked: true
            anchors.left: clipboard.right
            anchors.leftMargin: 10
            anchors.verticalCenter: clipboard.verticalCenter
            onClicked: {
                hideQtMethods = checked;
                addListElements();
            }
        }

        HifiControls.Label {
            id: hideLabel
            anchors.left: hideQt.right
            anchors.verticalCenter: clipboard.verticalCenter
            anchors.margins: 2
            font.pixelSize: 15
            text: "Hide Qt Methods"
        }
    
        HifiControls.Button {
            id: debug;
            enabled: true;
            color: hifi.buttons.black
            text: "Debug Script"
            width: 120
            height: 40
            anchors.right: parent.right
            anchors.rightMargin: 60
            anchors.bottom: parent.bottom
        
            onClicked: {
                sendToScript({type: "selectScript"});
            } 
        } 
    }

    HifiControls.Keyboard {
        id: keyboard;
        raised: false;
        anchors {
            bottom: parent.bottom;
            left: parent.left;
            right: parent.right;
        }

        Keys.onPressed: {
            console.log(event.nativeScanCode);
            if (event.key == Qt.Key_Left) {
                keyboard.raised = false;
            }
        }
    }

    function addNewMember() {
        apiMembers.push({member: searchBar.text, type: "user", value: valueBar.text, hasValue: true});
        var data = {'memberIndex': apiMembers.length-1, 'apiMember': searchBar.text, 'apiType':"user", 'apiValue': valueBar.text};
        memberModel.insert(0, data);
        computeMembersWithValues();
    }

    function evaluateMember() {
        sendToScript({type: "evaluateMember", data:{member: searchBar.text, index: evaluatingIdx}});
    }

    function getValuesToRefresh() {
        var valuesToRefresh = [];
        for (var i = 0; i < membersWithValues.length; i++) {
            var index = membersWithValues[i];
            var row = memberModel.get(index);
            valuesToRefresh.push({index: index, member: (row.apiType == "function()")  ? row.apiMember+"()" : row.apiMember, value: row.apiValue});
        }
        return valuesToRefresh;
    }


    function reloadListValues(){
        var valuesToRefresh = getValuesToRefresh();
        sendToScript({type: "refreshValues", data:valuesToRefresh});
    }

    function startReload(){
        var valuesToRefresh = getValuesToRefresh();
        sendToScript({type: "startRefreshValues", data:valuesToRefresh});
    }

    function stopReload(){
        sendToScript({type: "stopRefreshValues"});
    }

    function refreshValues(data) {
        var buffer = "";
        for (var i = 0; i < data.length; i++) {
            var row = memberModel.get(data[i].index);
            row.apiValue = data[i].value;
            apiMembers[row.memberIndex].value = data[i].value;
            memberModel.set(data[i].index, row);
            buffer += "\n" + apiMembers[row.memberIndex].member + " : " + data[i].value;
        }
        print(buffer);
    }
    

    function fromScript(message) {
        if (message.type === "methods") {
            apiMembers = message.data;
            if (ScriptDiscoveryService.debugScriptUrl != "") {
                addListElements("GlobalDebugger");
                if (memberModel.count == 0) {
                    addListElements();
                }
            } else {
                addListElements();
            }
            
        } else if (message.type === "debugMethods") {
            addListElements("GlobalDebugger");
        } else if (message.type === "refreshValues") {
            refreshValues(message.data);
        } else if (message.type === "evaluateMember") {
            valueBar.text = message.data.value;
            var selrow = memberModel.get(message.data.index);
            if (selrow.apiMember === searchBar.text || selrow.apiMember === searchBar.text + "()") {
                selrow.apiValue = message.data.value;
                apiMembers[selrow.memberIndex].value = message.data.value;
                apiMembers[selrow.memberIndex].hasValue = true;
                memberModel.set(message.data.index, selrow);
            }
        } else if (message.type === "selectScript") {
            if (message.data.length > 0) {
                ScriptDiscoveryService.debugScriptUrl = message.data;
            }
        }
    }


    function getFilterPairs(filter){
        var filteredArray = [];
        var filterChain;
        filterChain = filter.split(" ");
        for (var i = 0; i < filterChain.length; i++) {
            filterChain[i] = filterChain[i].toUpperCase();
        }
        var matchPairs = [];
        
        for (var i = 0; i < apiMembers.length; i++) {
            if (filterChain != undefined) {
                var found = 0;
                var memberComp = apiMembers[i].member.toUpperCase();
                for (var j = 0; j < filterChain.length; j++) {
                    found += memberComp.indexOf(filterChain[j]) >= 0 ? 1 : 0;
                }
                if (found === 0) {
                    continue;
                }
                matchPairs.push({index: i, found: found, member: apiMembers[i].member});
            }
        }
        
        matchPairs.sort(function(a, b){
            if(a.found > b.found) return -1;
		    if(a.found < b.found) return 1;
            if(a.member > b.member) return 1;
		    if(a.member < b.member) return -1;
		    return 0;
        });

        return matchPairs;
    }

    function isolateElement(index) {
        var oldElement = memberModel.get(index);
        var newElement = {memberIndex: oldElement.memberIndex, apiMember: oldElement.apiMember, apiType: oldElement.apiType, apiValue: oldElement.apiValue};
        membersWithValues = apiMembers[oldElement.memberIndex].hasValue ? [0] : [];
        memberModel.remove(0, memberModel.count);
        memberModel.append(newElement);
    }

    function computeMembersWithValues() {
        membersWithValues = [];
        for (var i = 0; i < memberModel.count; i++) {
            var idx = memberModel.get(i).memberIndex;
            if (apiMembers[idx].hasValue) {
                membersWithValues.push(i);
            }
        }
        update.enabled = membersWithValues.length <= maxUpdateValues;
        reload.enabled = membersWithValues.length <= maxReloadValues;
    }

    function addListElements(filter) {
        valueBar.text = "";
        memberModel.remove(0, memberModel.count);
        
        var filteredArray = (filter != undefined) ? [] : apiMembers;
        var matchPairs;
        if (filter != undefined) {
            matchPairs = getFilterPairs(filter);
            for (var i = 0; i < matchPairs.length; i++) {
                if (matchPairs[i].found < matchPairs[0].found) {
                    break;
                }
                var idx = matchPairs[i].index;
                filteredArray.push(apiMembers[idx]);
            }
        }

        for (var i = 0; i < filteredArray.length; i++) {
            var data = {'memberIndex':  matchPairs ? matchPairs[i].index : i, 
                        'apiMember': filteredArray[i].member, 
                        'apiType': filteredArray[i].type, 
                        'apiValue': filteredArray[i].value};

            if (hideQtMethods) {
                var chain = data.apiMember.split("."); 
                var method = chain[chain.length-1];
                if (method != "destroyed" &&
                    method != "objectName" &&
                    method != "objectNameChanged") {
                    memberModel.append(data);
                } 
            } else {
                memberModel.append(data);
            }
        }

        computeMembersWithValues();

        if (isReloading) {
            update.glyph = hifi.glyphs.playback_play
            isReloading = false;
            stopReload();
        }

        if (memberModel.count > 0) {
            scrollSlider.y = 0;
            list.contentY = 0;
        }
    }

    signal sendToScript(var message);    
}