#!/usr/bin/env python3

import argparse
import asyncio
import atexit
import os
import logging
import sys

from PyQt5 import QtCore, QtGui, QtWidgets
from qasync import QEventLoop

from sipyco.asyncio_tools import atexit_register_coroutine
from sipyco import common_args

from artiq import __version__ as artiq_version
from artiq import __artiq_dir__ as artiq_dir
from artiq.tools import get_user_config_dir
from artiq.gui import state, applets, models, log
from artiq.browser import datasets, files, experiments


logger = logging.getLogger(__name__)


def get_argparser():
    parser = argparse.ArgumentParser(description="ARTIQ Browser")
    parser.add_argument("--version", action="version",
                        version="ARTIQ v{}".format(artiq_version),
                        help="print the ARTIQ version number")
    parser.add_argument("--db-file", default=None,
                        help="database file for local browser settings "
                        "(default: %(default)s)")
    parser.add_argument("--browse-root", default="",
                        help="root path for directory tree "
                        "(default %(default)s)")
    parser.add_argument(
        "-s", "--server", default="::1",
        help="hostname or IP of the master to connect to "
             "when uploading datasets")
    parser.add_argument(
        "--port", default=3251, type=int,
        help="TCP port to use to connect to the master")
    parser.add_argument("select", metavar="SELECT", nargs="?",
                        help="directory to browse or file to load")
    common_args.verbosity_args(parser)
    return parser


class Browser(QtWidgets.QMainWindow):
    def __init__(self, smgr, dataset_sub, dataset_ctl, browse_root,
                 *, loop=None):
        QtWidgets.QMainWindow.__init__(self)
        smgr.register(self)

        icon = QtGui.QIcon(os.path.join(artiq_dir, "gui", "logo.svg"))
        self.setWindowIcon(icon)
        self.setWindowTitle("ARTIQ Browser")

        qfm = QtGui.QFontMetrics(self.font())
        self.resize(140*qfm.averageCharWidth(), 38*qfm.lineSpacing())

        self.exit_request = asyncio.Event()

        self.setUnifiedTitleAndToolBarOnMac(True)

        self.experiments = experiments.ExperimentsArea(
            browse_root, dataset_sub)
        smgr.register(self.experiments)
        self.experiments.setHorizontalScrollBarPolicy(
            QtCore.Qt.ScrollBarAsNeeded)
        self.experiments.setVerticalScrollBarPolicy(
            QtCore.Qt.ScrollBarAsNeeded)
        self.setCentralWidget(self.experiments)

        self.files = files.FilesDock(dataset_sub, browse_root)
        smgr.register(self.files)

        self.files.dataset_activated.connect(
            self.experiments.dataset_activated)
        self.files.dataset_changed.connect(
            self.experiments.dataset_changed)

        self.applets = applets.AppletsDock(self, dataset_sub, dataset_ctl, self.experiments, loop=loop)
        smgr.register(self.applets)
        atexit_register_coroutine(self.applets.stop, loop=loop)

        self.datasets = datasets.DatasetsDock(dataset_sub, dataset_ctl)
        smgr.register(self.datasets)
        self.files.metadata_changed.connect(self.datasets.metadata_changed)

        self.log = log.LogDock(None, "log")
        smgr.register(self.log)
        self.log.setFeatures(self.log.DockWidgetMovable |
                             self.log.DockWidgetFloatable)

        self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.files)
        self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.applets)
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.datasets)
        self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.log)

        g = self.menuBar().addMenu("&Experiment")
        a = QtWidgets.QAction("&Open", self)
        a.setIcon(QtWidgets.QApplication.style().standardIcon(
            QtWidgets.QStyle.SP_DialogOpenButton))
        a.setShortcuts(QtGui.QKeySequence.Open)
        a.setStatusTip("Open an experiment")
        a.triggered.connect(self.experiments.select_experiment)
        g.addAction(a)

        g = self.menuBar().addMenu("&View")
        a = QtWidgets.QAction("Cascade", self)
        a.setStatusTip("Cascade experiment windows")
        a.triggered.connect(self.experiments.cascadeSubWindows)
        g.addAction(a)
        a = QtWidgets.QAction("Tile", self)
        a.setStatusTip("Tile experiment windows")
        a.triggered.connect(self.experiments.tileSubWindows)
        g.addAction(a)

    def closeEvent(self, event):
        event.ignore()
        self.exit_request.set()

    def save_state(self):
        return {
            "geometry": bytes(self.saveGeometry()),
            "state": bytes(self.saveState()),
        }

    def restore_state(self, state):
        self.restoreState(QtCore.QByteArray(state["state"]))
        self.restoreGeometry(QtCore.QByteArray(state["geometry"]))


def main():
    # initialize application
    args = get_argparser().parse_args()
    if args.db_file is None:
        args.db_file = os.path.join(get_user_config_dir(), "artiq_browser.pyon")
    widget_log_handler = log.init_log(args, "browser")

    app = QtWidgets.QApplication(["ARTIQ Browser"])
    loop = QEventLoop(app)
    asyncio.set_event_loop(loop)
    atexit.register(loop.close)

    dataset_sub = models.LocalModelManager(datasets.Model)
    dataset_sub.init({})

    smgr = state.StateManager(args.db_file)

    dataset_ctl = datasets.DatasetCtl(args.server, args.port)
    browser = Browser(smgr, dataset_sub, dataset_ctl, args.browse_root,
                      loop=loop)
    widget_log_handler.callback = browser.log.model.append

    if os.name == "nt":
        # HACK: show the main window before creating applets.
        # Otherwise, the windows of those applets that are in detached
        # QDockWidgets fail to be embedded.
        browser.show()
    smgr.load()
    smgr.start(loop=loop)
    atexit_register_coroutine(smgr.stop, loop=loop)

    if args.select is not None:
        browser.files.select(args.select)

    browser.show()
    loop.run_until_complete(browser.exit_request.wait())


if __name__ == "__main__":
    main()
