import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Window
import 'ext/' as Ext
import 'ext/dialogs' as Dlg

StackView {
  id: main
  objectName: "main"
  width: 800  // alternatively: Screen.desktopAvailableWidth
  height: 600 // alternatively: Screen.desktopAvailableHeight
  initialItem: mainRect

  property bool small: (Math.max(width, height) < 1000)
  property bool skipEnsureVisible: false
  property double editorHeight: 0.5 // preferred initial height (50%)
  property string cursorColor: "blue"

  function mainHeight() {
    var h = Math.round(Qt.inputMethod.keyboardRectangle.y /
                       ((Qt.platform.os === "android") ? Screen.devicePixelRatio : 1))
    return (h === 0) ? main.height : h
  }

  function divideHeight(factor) { return (mainHeight() - rectCommand.height) * factor }
  function isLandscape()        { return (Screen.primaryOrientation === Qt.LandscapeOrientation) }
  function keyboardVisible()    { return Qt.inputMethod.visible }
  function showKeyboard(show)   { show ? Qt.inputMethod.show() : Qt.inputMethod.hide() }

  // show/hide dialogs

  function pushDialog(name)  {
    switch (name) {
      case "query": dialogQuery.open();     break
      case "file":  main.push(dialogFile);  break
      case "debug": main.push(dialogDebug); break
      case "help":  main.push(dialogHelp);  break
    }
  }

  function popDialog() { main.pop() }

  Screen.onOrientationChanged: {
    Lisp.call("editor:orientation-changed", Screen.orientation)
  }

  Keys.onPressed: (event) => {
    if (event.key === Qt.Key_Back) {
      event.accepted = true
      Lisp.call("editor:back-pressed")
    }
  }

  // custom transition animations

  pushEnter: Transition {
    ParallelAnimation {
      OpacityAnimator {
        from: 0
        to: 1
        easing.type: Easing.OutQuart
        duration: 300
      }
      XAnimator {
        from: width / 3
        to: 0
        easing.type: Easing.OutQuart
        duration: 300
      }
    }
  }

  pushExit: Transition {
    OpacityAnimator {
      from: 1
      to: 0
      duration: 300
    }
  }

  popEnter: Transition {
    OpacityAnimator {
      from: 0
      to: 1
      duration: 300
    }
  }

  popExit: Transition {
    ParallelAnimation {
      OpacityAnimator {
        from: 1
        to: 0
        easing.type: Easing.InQuart
        duration: 300
      }
      XAnimator {
        from: 0
        to: width / 3
        easing.type: Easing.InQuart
        duration: 300
      }
    }
  }

  // delay timer

  Timer {
    id: timer
  }

  function delay(milliseconds, callback) {
    timer.interval = milliseconds
    timer.triggered.connect(callback)
    timer.start()
  }

  function later(callback) {
    delay(50, callback)
  }

  // fonts (must stay here, before using them below)

  FontLoader { id: fontHack;     source: "fonts/Hack-Regular.ttf" }        // code
  FontLoader { id: fontHackBold; source: "fonts/Hack-Bold.ttf" }
  FontLoader { id: fontAwesome;  source: "fonts/fontawesome-webfont.ttf" } // icons

  // items

  Rectangle {
    id: mainRect

    SplitView {
      id: splitView
      anchors.fill: parent
      orientation: Qt.Vertical

      property double handleHeight: 10

      handle: Rectangle {
        implicitHeight: splitView.handleHeight
        color: SplitHandle.pressed ? Qt.darker(rectOutput.color) : rectOutput.color
      }

      Rectangle {
        id: rectEdit
        objectName: "rect_edit"
        width: main.width
        SplitView.preferredHeight: divideHeight(editorHeight)

        Ext.Flickable {
          id: flickEdit
          objectName: "flick_edit"
          anchors.fill: parent
          contentWidth: edit.paintedWidth
          contentHeight: edit.paintedHeight

          TextEdit {
            id: edit
            objectName: "edit"
            width: flickEdit.width
            height: flickEdit.height
            leftPadding: 2
            font.family: "Hack"
            font.pixelSize: 18
            selectionColor: "firebrick"
            inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhNoTextHandles | Qt.ImhNoEditMenu
            cursorDelegate: cursor

            Keys.onTabPressed: command.forceActiveFocus()

            onCursorRectangleChanged: flickEdit.ensureVisible(cursorRectangle)

            Component.onCompleted: later(function() {
              Lisp.call("editor:set-text-document", objectName, textDocument)
            })

            // for external keyboard
            Shortcut {
              sequence: "Ctrl+E" // E for Expression
              onActivated: Lisp.call("editor:select-expression")
            }
            Shortcut {
              sequence: "Ctrl+L" // L for Lambda
              onActivated: Lisp.call("editor:eval-single-expression")
            }

            MouseArea {
              width: Math.max(rectEdit.width, edit.paintedWidth)
              height: Math.max(rectEdit.height, edit.paintedHeight)

              onPressed: (mouse) => {
                // seems necessary to consistently move cursor by tapping
                edit.forceActiveFocus()
                edit.cursorPosition = edit.positionAt(mouse.x, mouse.y)
                Qt.inputMethod.show() // needed for edge case (since we have 2 input fields)
                Lisp.call("editor:set-focus-editor", edit.objectName)
              }

              onPressAndHold: Lisp.call("editor:copy-paste", edit.cursorPosition)
            }
          }
        }
      }

      Column {
        width: parent.width
        height: rectCommand.height + rectOutput.height
        SplitView.fillHeight: false // see comment in rectOutput

        Rectangle {
          id: rectCommand
          objectName: "rect_command"
          width: parent.width
          height: command.font.pixelSize + 11
          border.width: 2
          border.color: command.focus ? "#0066ff" : "lightgray"

          Ext.Flickable {
            id: flickCommand
            objectName: "flick_command"
            anchors.fill: parent
            contentWidth: command.paintedWidth
            contentHeight: command.paintedHeight

            TextEdit {
              id: command
              objectName: "command"
              width: flickCommand.width
              height: flickCommand.height
              padding: 4
              font.family: "Hack"
              font.pixelSize: 18
              selectionColor: "firebrick"
              inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhNoTextHandles | Qt.ImhNoEditMenu
              cursorDelegate: cursor
              focus: true

              Keys.onUpPressed:   Lisp.call("editor:history-move", "back")
              Keys.onDownPressed: Lisp.call("editor:history-move", "forward")
              Keys.onTabPressed:  edit.forceActiveFocus()

              onCursorRectangleChanged: flickCommand.ensureVisible(cursorRectangle)

              Component.onCompleted: later(function() {
                Lisp.call("editor:set-text-document", objectName, textDocument)
              })

              MouseArea {
                width: Math.max(rectCommand.width, command.paintedWidth)
                height: Math.max(rectCommand.height, command.paintedHeight)

                onPressed: {
                  // seems necessary to consistently move cursor by tapping
                  command.forceActiveFocus()
                  command.cursorPosition = command.positionAt(mouse.x, mouse.y)
                  Qt.inputMethod.show() // needed for edge case (since we have 2 input fields)
                  Lisp.call("editor:set-focus-editor", command.objectName)
                  Lisp.call("editor:ensure-output-visible")
                }

                onPressAndHold: Lisp.call("editor:copy-paste", command.cursorPosition)
              }
            }
          }
        }

        Rectangle {
          id: rectOutput
          objectName: "rect_output"
          width: main.width
          // calculate manually (for virtual keyboard)
          height: main.mainHeight() - rectEdit.height - rectCommand.height - splitView.handleHeight

          ListView {
            id: output
            objectName: "output"
            anchors.fill: parent
            contentWidth: parent.width * 5
            clip: true
            model: outputModel
            flickableDirection: Flickable.HorizontalAndVerticalFlick

            property string fontFamily: "Hack"
            property int fontSize: 18

            delegate: Column {
              Rectangle {
                width: output.contentWidth
                height: model.line ? 2 : 0
                color: "#c0c0ff"
              }

              Text {
                x: 2
                padding: 2
                textFormat: Text.PlainText
                font.family: output.fontFamily
                font.pixelSize: output.fontSize
                text: model.richText ? "" : model.text
                color: model.color
                font.bold: model.bold
                visible: !model.richText
              }

              Text {
                x: 2
                padding: 2
                textFormat: Text.RichText
                font.family: output.fontFamily
                font.pixelSize: output.fontSize
                text: model.richText ? model.text : ""
                color: model.color
                font.bold: model.bold
                visible: model.richText

                MouseArea {
                  width: parent.paintedWidth
                  height: parent.paintedHeight

                  onPressed: {
                    // custom link handling, since 'onLinkActivated' does not work within a Flickable
                    var link = parent.linkAt(mouse.x, mouse.y)
                    if (link.length) {
                      Qt.openUrlExternally(link)
                    }
                  }
                }
              }
            }

            onFlickStarted: forceActiveFocus()

            Component.onCompleted: later(function () {
              Lisp.call("editor:delayed-ini")
            })
          }

          ListModel {
            id: outputModel
            objectName: "output_model"

            function appendOutput(text) {
              append(text)
              output.contentX = 0
              output.positionViewAtEnd()
            }
          }

          ProgressBar {
            objectName: "progress"
            width: main.width
            z: 1
            indeterminate: true
            enabled: visible
            visible: false
          }

          // move history buttons

          Rectangle {
            id: buttonsBottom
            width: rowButtonsBottom.width
            height: rowButtonsBottom.height
            anchors.horizontalCenter: parent.horizontalCenter
            opacity: 0.7
            visible: command.activeFocus

            Row {
              id: rowButtonsBottom
              padding: 4
              spacing: 6

              Ext.MenuButton {
                objectName: "history_back"
                text: "\uf100"
              }
              Ext.MenuButton {
                objectName: "history_forward"
                text: "\uf101"
              }
            }
          }

          // paren buttons (above keyboard)

          Rectangle {
            objectName: "rect_paren_buttons"
            width: rowParens.width
            height: rowParens.height
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.bottom: parent.bottom
            color: "transparent"
            visible: Qt.inputMethod.visible

            Row {
              id: rowParens
              padding: -parenOpen.width / 30
              spacing: -parenOpen.width / 6

              Ext.ParenButton {
                id: parenOpen
                objectName: "paren_open"
                icon.source: "img/paren-open.png"
                onClicked: Lisp.call("editor:insert", "(")
              }
              Ext.ParenButton {
                objectName: "paren_close"
                icon.source: "img/paren-close.png"
                onClicked:      Lisp.call("editor:insert", ")")
                onPressAndHold: Lisp.call("editor:close-all-parens")
              }
            }
          }

          // arrow buttons (cursor movement)

          Rectangle {
            id: rectArrows
            objectName: "rect_arrows"
            width: arrows.width + 20
            height: width
            anchors.right: rectOutput.right
            anchors.bottom: rectOutput.bottom
            color: "transparent"
            visible: Qt.inputMethod.visible

            MouseArea {
              anchors.fill: parent
              onPressed: Lisp.call("editor:ensure-focus")
            }

            Item {
              id: arrows
              width: up.width * 3
              height: width
              anchors.horizontalCenter: parent.horizontalCenter
              anchors.verticalCenter: parent.verticalCenter

              Ext.ArrowButton {
                id: up
                objectName: "up"
                text: "\uf139"
                anchors.horizontalCenter: parent.horizontalCenter
              }

              Ext.ArrowButton {
                objectName: "left"
                text: "\uf137"
                anchors.verticalCenter: parent.verticalCenter
              }

              Ext.ArrowButton {
                objectName: "right"
                text: "\uf138"
                anchors.verticalCenter: parent.verticalCenter
                anchors.right: parent.right
              }

              Ext.ArrowButton {
                objectName: "down"
                text: "\uf13a"
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.bottom: parent.bottom
              }
            }
          }
        }
      }
    }

    Component {
      id: cursor

      Rectangle {
        width: 2
        color: main.cursorColor
        visible: parent.activeFocus

        SequentialAnimation on opacity {
          running: true
          loops: Animation.Infinite

          NumberAnimation { to: 0; duration: 500; easing.type: "OutQuad" }
          NumberAnimation { to: 1; duration: 500; easing.type: "InQuad" }
        }
      }
    }

    Rectangle {
      id: buttonsTop
      objectName: "buttons_top"
      y: -height // hidden
      width: rowButtonsTop.width
      height: rowButtonsTop.height
      anchors.horizontalCenter: parent.horizontalCenter
      opacity: 0.7

      Row {
        id: rowButtonsTop
        padding: 4
        spacing: 6

        Ext.MenuButton {
          objectName: "undo"
          text: "\uf0e2"
          enabled: edit.canUndo
        }
        Ext.MenuButton {
          objectName: "redo"
          text: "\uf01e"
          enabled: edit.canRedo
        }
        Ext.MenuButton {
          objectName: "font_smaller"
          text: "\uf010"
          font.pixelSize: main.small ? 10 : 15
        }
        Ext.MenuButton {
          objectName: "font_bigger"
          text: "\uf00e"
          font.pixelSize: main.small ? 16 : 25
        }
      }
    }

    Ext.MenuButton {
      id: showMenu
      objectName: "show_menu"
      anchors.right: parent.right
      anchors.rightMargin: 4
      y: 4
      opacity: 0.7
      text: "\uf142"

      onClicked: {
        showButtonsTop.start()
        showButtonsRight.start()
        menuTimer.start()
      }
    }

    Timer {
      id: menuTimer
      objectName: "menu_timer"
      interval: 3000
      onTriggered: {
        if (buttonsTop.y === 0) {
          hideButtonsTop.start()
          hideButtonsRight.start()
        }
      }
    }

    Rectangle {
      id: buttonsRight
      objectName: "buttons_right"
      x: -width // hidden
      width: colButtonsRight.width
      height: colButtonsRight.height

      Column {
        id: colButtonsRight
        padding: 4
        spacing: 6

        Ext.Button {
          objectName: "clear"
          text: "\uf014"
        }
        Ext.Button {
          objectName: "open_file"
          text: "\uf115"
        }
        Ext.Button {
          objectName: "save_file"
          text: "\uf0c7"
        }
        Ext.Button {
          objectName: "eval"
          text: "\u03bb" // lambda
        }
      }
    }

    // animations for showing/hiding editor menu buttons

    NumberAnimation {
      id: showButtonsTop
      objectName: "show_buttons_top"
      target: buttonsTop
      property: "y"
      from: -buttonsTop.height
      to: 0
      duration: 500
      easing.type: Easing.OutExpo
    }

    NumberAnimation {
      id: showButtonsRight
      objectName: "show_buttons_right"
      target: buttonsRight
      property: "x"
      from: buttonsRight.parent.width
      to: buttonsRight.parent.width - buttonsRight.width
      duration: 500
      easing.type: Easing.OutExpo
    }

    NumberAnimation {
      id: hideButtonsTop
      target: buttonsTop
      property: "y"
      from: 0
      to: -buttonsTop.height
      duration: 500
      easing.type: Easing.InExpo
    }

    NumberAnimation {
      id: hideButtonsRight
      target: buttonsRight
      property: "x"
      from: buttonsRight.parent.width - buttonsRight.width
      to: buttonsRight.parent.width
      duration: 500
      easing.type: Easing.InExpo
    }
  }

  // custom font loader

  function loadFont(file) {
    var font = Qt.createQmlObject("import QtQuick 2.15; FontLoader { source: '" + file + "' }", main)
    return font.name
  }

  // not visible dialog / menu instances

  Ext.QueryDialog { id: dialogQuery }
  Ext.FileBrowser { id: dialogFile;  opacity: 0 }
  Ext.DebugDialog { id: dialogDebug; opacity: 0 }
  Ext.Help        { id: dialogHelp;  opacity: 0 }

  Ext.ClipboardMenu {}

  // modal dialogs

  Dlg.Dialogs {}

  // dynamic QML items

  Ext.Dynamic {}
}
