import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.2
import QtQuick.Layouts 1.1
import Nymea 1.0
import NymeaApp.Utils 1.0
import "../components"
import "../customviews"
import QtCharts 2.2

Item {
    id: root
    implicitHeight: width * .6
    implicitWidth: 400

    property Thing thing: null
    property StateType stateType: null
    property int roundTo: 2
    property color color: Style.accentColor
    property string iconSource: ""
    property alias title: titleLabel.text
    property bool titleVisible: true
    property bool inverted: false

    readonly property State valueState: thing && stateType ? thing.states.getState(stateType.id) : null
    readonly property StateType connectedStateType: hasConnectable ? thing.thingClass.stateTypes.findByName("connected") : null
    readonly property bool hasConnectable: connectedStateType != null

    QtObject {
        id: d
        property date now: new Date()

        readonly property int range: selectionTabs.currentValue.range
        readonly property int sampleRate: root.stateType == null || root.stateType.type.toLowerCase() == "bool" ? NewLogsModel.SampleRateAny : selectionTabs.currentValue.sampleRate

        readonly property int visibleValues: range / sampleRate

        readonly property var startTime: {
            var date = new Date(fixTime(now));
            date.setTime(date.getTime() - range * 60000 + 2000);
            return date;
        }

        readonly property var endTime: {
            var date = new Date(fixTime(now));
            date.setTime(date.getTime() + 2000)
            return date;
        }

        function fixTime(timestamp) {
            return timestamp
        }
    }

    NewLogsModel {
        id: logsModel
        engine: _engine
        source: root.thing ? "state-" + thing.id + "-" + root.stateType.name : ""
        startTime: new Date(d.startTime.getTime() - d.range * 1.1 * 60000)
        endTime: new Date(d.endTime.getTime() + d.range * 1.1 * 60000)
        sampleRate: d.sampleRate
        sortOrder: Qt.DescendingOrder

        Component.onCompleted: {
//            print("****** completed")
            ready = true
            update()
        }
        property bool ready: false
        onSourceChanged: {
//            print("***** source changed")
            update()
        }

        function update() {
//            print("*********+ source", source, "start", startTime, "end", endTime, ready)
            if (ready && source != "") {
                fetchLogs()
            }
        }

        property double minValue
        property double maxValue

        onEntriesAddedIdx: {
//            print("**** entries added", index, count, "entries in series:", valueSeries.count, "in model", logsModel.count)
            for (var i = 0; i < count; i++) {
                var entry = logsModel.get(i)
//                                print("entry", entry.timestamp, entry.source, JSON.stringify(entry.values))
                zeroSeries.ensureValue(entry.timestamp)

                if (root.stateType.type.toLowerCase() == "bool") {
                    var value = entry.values[root.stateType.name]
                    if (value == null) {
                        value = false;
                    }
                    value *= root.inverted ? -1 : 1

                    // for booleans, we'll insert the opposite value right before the new one so the position is doubled
                    var insertIdx = (index + i) * 2
                    valueSeries.insert(insertIdx+1, entry.timestamp.getTime() - 500, !value)
                    valueSeries.insert(insertIdx, entry.timestamp, value)

                } else {
                    var value = entry.values[root.stateType.name]
                    if (value == null) {
                        value = 0;
                    }
                    value *= root.inverted ? -1 : 1

                    minValue = minValue == undefined ? value : Math.min(minValue, value)
                    maxValue = maxValue == undefined ? value : Math.max(maxValue, value)

                    var insertIdx = index + i
                    valueSeries.insert(insertIdx, entry.timestamp, value)
                }
            }

            if (root.stateType.type.toLowerCase() == "bool") {
                var last = valueSeries.at(0);
                if (last.x < d.endTime) {
                    valueSeries.insert(0, d.endTime, last.y)
                    zeroSeries.ensureValue(d.endTime)
                }
            }

            print("added entries. now in series:", valueSeries.count)
        }
        onEntriesRemoved: {
            print("removing:", index, count, valueSeries.count)
            if (root.stateType.type.toLowerCase() == "bool") {
                valueSeries.removePoints(index * 2, count * 2)
                if (valueSeries.count == 1) {
                    valueSeries.removePoints(0, 1);
                }
            } else {
                valueSeries.removePoints(index, count)
            }

            zeroSeries.shrink()
        }
    }

    ColumnLayout {
        anchors.fill: parent
        spacing: 0

        Label {
            id: titleLabel
            Layout.fillWidth: true
            Layout.margins: Style.smallMargins
            horizontalAlignment: Text.AlignHCenter
            text: root.stateType.displayName
            visible: root.titleVisible
            //            MouseArea {
            //                anchors.fill: parent
            //                onClicked: {
            //                    pageStack.push(Qt.resolvedUrl("PowerBalanceHistoryPage.qml"))
            //                }
            //            }
        }

        SelectionTabs {
            id: selectionTabs
            Layout.fillWidth: true
            Layout.leftMargin: Style.smallMargins
            Layout.rightMargin: Style.smallMargins
            currentIndex: 1
            model: ListModel {
                ListElement {
                    modelData: qsTr("Hours")
                    sampleRate: NewLogsModel.SampleRate1Min
                    range: 180 // 3 Hours: 3 * 60
                }
                ListElement {
                    modelData: qsTr("Days")
                    sampleRate: NewLogsModel.SampleRate15Mins
                    range: 1440 // 1 Day: 24 * 60
                }
                ListElement {
                    modelData: qsTr("Weeks")
                    sampleRate: NewLogsModel.SampleRate1Hour
                    range: 10080 // 7 Days: 7 * 24 * 60
                }
                ListElement {
                    modelData: qsTr("Months")
                    sampleRate: NewLogsModel.SampleRate3Hours
                    range: 43200 // 30 Days: 30 * 24 * 60
                }
            }
            onTabSelected: {
                d.now = new Date()
                logsModel.clear()
                print("*** tab selected")
                logsModel.fetchLogs()
            }
        }

        Item {
            Layout.fillWidth: true
            Layout.fillHeight: true


            ChartView {
                id: chartView
                anchors.fill: parent
                //                backgroundColor: "transparent"
                margins.left: 0
                margins.right: 0
                margins.bottom: Style.smallMargins //Style.smallIconSize + Style.margins
                margins.top: 0

                backgroundColor: Style.tileBackgroundColor
                backgroundRoundness: Style.cornerRadius

                legend.alignment: Qt.AlignBottom
                legend.labelColor: Style.foregroundColor
                legend.font: Style.extraSmallFont
                legend.visible: false

                ActivityIndicator {
                    anchors.centerIn: parent
                    visible: logsModel.busy
                    opacity: .5
                }

                Label {
                    anchors.centerIn: parent
                    visible: !logsModel.busy && logsModel.count == 0
                    text: qsTr("No data")
                    font: Style.smallFont
                    opacity: .5
                }

                Label {
                    x: chartView.x + chartView.plotArea.x + (chartView.plotArea.width - width) / 2
                    y: chartView.y + chartView.plotArea.y + Style.smallMargins
                    text: {
                        switch (d.sampleRate) {
                        case NewLogsModel.SampleRate1Min:
                            return d.startTime.toLocaleDateString(Qt.locale(), Locale.LongFormat)
                        case NewLogsModel.SampleRate15Mins:
                        case NewLogsModel.SampleRate1Hour:
                        case NewLogsModel.SampleRate3Hours:
                        case NewLogsModel.SampleRate1Day:
                        case NewLogsModel.SampleRate1Week:
                        case NewLogsModel.SampleRate1Month:
                        case NewLogsModel.SampleRate1Year:
                            return d.startTime.toLocaleDateString(Qt.locale(), Locale.ShortFormat) + " - " + d.endTime.toLocaleDateString(Qt.locale(), Locale.ShortFormat)
                        }
                    }
                    font: Style.smallFont
                    opacity: ((new Date().getTime() - d.now.getTime()) / d.sampleRate / 60000) > d.visibleValues ? .5 : 0
                    Behavior on opacity { NumberAnimation {} }
                }
                ValueAxis {
                    id: valueAxis
                    min: logsModel.minValue == undefined || logsModel.minValue == 0
                         ? 0
                         : root.stateType.minValue ? Math.max(logsModel.minValue - 5, root.stateType.minValue) : logsModel.minValue - 5
                    max: logsModel.maxValue == undefined || logsModel.maxValue == 0
                         ? 0
                         : root.stateType.maxValue ? Math.min(logsModel.maxValue + 5, root.stateType.maxValue) : logsModel.maxValue + 5

                    labelFormat: "%0." + labelsLayout.precision + "f " + Types.toUiUnit(root.stateType.unit)
                    gridLineColor: Style.tileOverlayColor
                    labelsVisible: false
                    lineVisible: false
                    titleVisible: false
                    shadesVisible: false
                    labelsFont: Style.extraSmallFont
                    labelsColor: Style.foregroundColor

                }
                // Overriding the labels with our own as printf struggles with special chars
                Item {
                    id: labelsLayout
                    x: Style.smallMargins
                    y: chartView.plotArea.y
                    height: chartView.plotArea.height
                    width: chartView.plotArea.x - x
                    visible: root.stateType.type.toLowerCase() != "bool" && logsModel.minValue != logsModel.maxValue
                    property double range: Math.abs(valueAxis.max - valueAxis.min)
                    property double stepSize: range / (valueAxis.tickCount - 1)
                    property int precision: valueAxis.max - valueAxis.min < 5 ? 2 : 0

                    Repeater {
                        model: valueAxis.tickCount
                        delegate: Label {
                            y: parent.height / (valueAxis.tickCount - 1) * index - font.pixelSize / 2
                            width: parent.width - Style.smallMargins
                            horizontalAlignment: Text.AlignRight
                            property double offset: (valueAxis.tickCount - index - 1) * labelsLayout.stepSize
                            property double value: valueAxis.min + offset
                            text: root.stateType ? Types.toUiValue(value, root.stateType.unit).toFixed(labelsLayout.precision) + " " + Types.toUiUnit(root.stateType.unit) : ""
                            verticalAlignment: Text.AlignTop
                            font: Style.extraSmallFont
                        }
                    }
                }

                DateTimeAxis {
                    id: dateTimeAxis

                    min: d.startTime
                    max: d.endTime
                    format: {
                        switch (selectionTabs.currentValue.sampleRate) {
                        case NewLogsModel.SampleRate1Min:
                        case NewLogsModel.SampleRate15Mins:
                            return "hh:mm"
                        case NewLogsModel.SampleRate1Hour:
                        case NewLogsModel.SampleRate3Hours:
                        case NewLogsModel.SampleRate1Day:
                            return "dd.MM."
                        }
                    }
                    tickCount: {
                        switch (selectionTabs.currentValue.sampleRate) {
                        case NewLogsModel.SampleRate1Min:
                        case NewLogsModel.SampleRate15Mins:
                            return root.width > 500 ? 13 : 7
                        case NewLogsModel.SampleRate1Hour:
                            return 7
                        case NewLogsModel.SampleRate3Hours:
                        case NewLogsModel.SampleRate1Day:
                            return root.width > 500 ? 12 : 6
                        }
                    }
                    labelsFont: Style.extraSmallFont
                    gridVisible: false
                    minorGridVisible: false
                    lineVisible: false
                    shadesVisible: false
                    labelsColor: Style.foregroundColor
                }

                AreaSeries {
                    id: mainSeries
                    axisX: dateTimeAxis
                    axisY: valueAxis
                    name: root.stateType ? root.stateType.displayName : ""
                    color: Qt.rgba(root.color.r, root.color.g, root.color.b, .5)
                    borderColor: root.color
                    borderWidth: 2
                    lowerSeries: LineSeries {
                        id: zeroSeries
                        XYPoint { x: dateTimeAxis.max.getTime(); y: 0 }
                        XYPoint { x: dateTimeAxis.min.getTime(); y: 0 }
                        function ensureValue(timestamp) {
                            if (count == 0) {
                                append(timestamp, 0)
                            } else if (count == 1) {
                                if (timestamp.getTime() < at(0).x) {
                                    append(timestamp, 0)
                                } else {
                                    insert(0, timestamp, 0)
                                }
                            } else {
                                if (timestamp.getTime() > at(0).x) {
                                    remove(0)
                                    insert(0, timestamp, 0)
                                } else if (timestamp.getTime() < at(1).x) {
                                    remove(1)
                                    append(timestamp, 0)
                                }
                            }
                        }
                        function shrink() {
                            clear();
                            if (logsModel.count > 0) {
                                ensureValue(logsModel.get(0).timestamp)
                                ensureValue(logsModel.get(logsModel.count-1).timestamp)
                            }
                        }
                    }

                    upperSeries: LineSeries {
                        id: valueSeries
                    }
                }
            }

            MouseArea {
                id: mouseArea
                anchors.fill: parent
                anchors.leftMargin: chartView.plotArea.x
                anchors.topMargin: chartView.plotArea.y
                anchors.rightMargin: chartView.width - chartView.plotArea.width - chartView.plotArea.x
                anchors.bottomMargin: chartView.height - chartView.plotArea.height - chartView.plotArea.y

                hoverEnabled: true
                preventStealing: tooltipping || dragging
                propagateComposedEvents: true

                property int startMouseX: 0
                property bool dragging: false
                property bool tooltipping: false

                property var startDatetime: null

                Timer {
                    interval: 300
                    running: mouseArea.pressed
                    onTriggered: {
                        if (!mouseArea.dragging) {
                            mouseArea.tooltipping = true
                        }
                    }
                }
                onReleased: {
                    if (mouseArea.dragging) {
                        logsModel.fetchLogs()
                        mouseArea.dragging = false;
                    }

                    mouseArea.tooltipping = false;
                }

                onPressed: {
                    startMouseX = mouseX
                    startDatetime = d.now
                }

                onDoubleClicked: {
                    if (selectionTabs.currentIndex == 0) {
                        return;
                    }

                    var idx = Math.ceil(mouseArea.mouseX * d.visibleValues / mouseArea.width)
                    var timestamp = new Date(d.startTime.getTime() + (idx * d.sampleRate * 60000))
                    selectionTabs.currentIndex--
                    d.now = new Date(Math.min(new Date().getTime(), timestamp.getTime() + (d.visibleValues / 2) * d.sampleRate * 60000))
                    powerBalanceLogs.fetchLogs()
                }

                onMouseXChanged: {
                    if (!pressed || mouseArea.tooltipping) {
                        return;
                    }
                    if (Math.abs(startMouseX - mouseX) < 10) {
                        return;
                    }
                    dragging = true

                    var dragDelta = startMouseX - mouseX
                    var totalTime = d.endTime.getTime() - d.startTime.getTime()
                    // dragDelta : timeDelta = width : totalTime
                    var timeDelta = dragDelta * totalTime / mouseArea.width
//                    print("dragging", dragDelta, totalTime, mouseArea.width)
                    d.now = new Date(Math.min(new Date(), new Date(startDatetime.getTime() + timeDelta)))
                }

                onWheel: {
                    startDatetime = d.now
                    var totalTime = d.endTime.getTime() - d.startTime.getTime()
                    // pixelDelta : timeDelta = width : totalTime
                    var timeDelta = wheel.pixelDelta.x * totalTime / mouseArea.width
//                    print("wheeling", wheel.pixelDelta.x, totalTime, mouseArea.width)
                    d.now = new Date(Math.min(new Date(), new Date(startDatetime.getTime() - timeDelta)))
                    wheelStopTimer.restart()
                }
                Timer {
                    id: wheelStopTimer
                    interval: 300
                    repeat: false
                    onTriggered: logsModel.fetchLogs()
                }

                Rectangle {
                    height: parent.height
                    width: 1
                    color: Style.foregroundColor
                    x: Math.min(mouseArea.width, Math.max(0, toolTip.entryX))
                    visible: toolTip.visible
                }

                NymeaToolTip {
                    id: toolTip
                    visible: (mouseArea.containsMouse || mouseArea.tooltipping) && !mouseArea.dragging && logsModel.count > 0

                    backgroundItem: chartView
                    backgroundRect: Qt.rect(mouseArea.x + toolTip.x, mouseArea.y + toolTip.y, toolTip.width, toolTip.height)

                    property var timestamp: new Date(d.startTime.getTime() + (mouseArea.mouseX * (d.endTime.getTime() - d.startTime.getTime()) / mouseArea.width) )
                    property NewLogEntry entry: logsModel.count > 0 ? logsModel.find(timestamp) : null

                    // eX : eT = w : duration
                    property int entryX: entry ? (entry.timestamp.getTime() - d.startTime.getTime()) * mouseArea.width / (d.endTime.getTime() - d.startTime.getTime()) : 0
                    property int xOnRight: Math.max(0, entryX) + Style.smallMargins
                    property int xOnLeft: Math.min(entryX, mouseArea.width) - Style.smallMargins - width
                    x: xOnRight + width < mouseArea.width ? xOnRight : xOnLeft
                    property var value: entry ? entry.values[root.stateType.name] * (root.inverted ? -1 : 1) : null
                    y: Math.min(Math.max(mouseArea.height - (value * mouseArea.height / valueAxis.max) - height - Style.margins, 0), mouseArea.height - height)

                    width: tooltipLayout.implicitWidth + Style.smallMargins * 2
                    height: tooltipLayout.implicitHeight + Style.smallMargins * 2

                    ColumnLayout {
                        id: tooltipLayout
                        width: parent.width
                        anchors {
                            left: parent.left
                            top: parent.top
                            margins: Style.smallMargins
                        }
                        Label {
                            text: toolTip.entry ? toolTip.entry.timestamp.toLocaleString(Qt.locale(), Locale.ShortFormat) : ""
                            font: Style.smallFont
                        }

                        RowLayout {

                            ColorIcon {
                                id: icon
                                size: Style.smallIconSize
                                name: root.iconSource
                                color: root.color
                                visible: name != ""
                            }

                            Rectangle {
                                width: Style.extraSmallFont.pixelSize
                                height: width
                                color: root.color
                                visible: !icon.visible
                            }

                            Label {
                                Layout.fillWidth: true
                                elide: Text.ElideRight
                                text: toolTip.value === null
                                      ? qsTr("No data")
                                      : root.stateType.type.toLowerCase() == "bool"
                                        ? root.stateType.displayName + ": " + (toolTip.value ? qsTr("Yes") : qsTr("No"))
                                        : Types.toUiValue(toolTip.value, root.stateType.unit).toFixed(root.roundTo) + Types.toUiUnit(root.stateType.unit)
                                font: Style.extraSmallFont
                            }
                        }

                    }
                }
            }
        }
    }

}
