# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright 2016 Continuum Analytics, Inc.
#
# May be copied and distributed freely only as part of an Anaconda or
# Miniconda installation.
# -----------------------------------------------------------------------------
"""Main Application Window."""

from __future__ import absolute_import, division

# Standard library imports
import json
import os
import sys

# Third party imports
from qtpy.QtCore import QCoreApplication, QPoint, QSize, Qt, QUrl, Signal
from qtpy.QtGui import QDesktopServices, QIcon, QPixmap
from qtpy.QtWidgets import (QApplication, QHBoxLayout, QMainWindow,
                            QProgressBar, QVBoxLayout, QWidget)

# Local imports
from anaconda_navigator.api.anaconda_api import AnacondaAPI
from anaconda_navigator.config import CHANNELS_PATH, CONF
from anaconda_navigator.static import images
from anaconda_navigator.utils.analytics import GATracker
from anaconda_navigator.utils.logs import logger
from anaconda_navigator.utils.qthelpers import create_action
from anaconda_navigator.utils.styles import load_style_sheet
from anaconda_navigator.widgets import (ButtonBase, ButtonLabel, ButtonLink,
                                        ButtonPrimary, FrameBase, LabelBase,
                                        SpacerHorizontal)
from anaconda_navigator.widgets.dialogs import (MessageBoxError,
                                                MessageBoxInformation)
from anaconda_navigator.widgets.dialogs.about import AboutDialog
from anaconda_navigator.widgets.dialogs.logger import LogViewerDialog
from anaconda_navigator.widgets.dialogs.login import AuthenticationDialog
from anaconda_navigator.widgets.dialogs.preferences import PreferencesDialog
from anaconda_navigator.widgets.dialogs.quit import QuitApplicationDialog
from anaconda_navigator.widgets.dialogs.update import DialogUpdateApplication
from anaconda_navigator.widgets.tabs.community import CommunityTab
from anaconda_navigator.widgets.tabs.environments import EnvironmentsTab
from anaconda_navigator.widgets.tabs.home import HomeTab
from anaconda_navigator.widgets.tabs.tabwidget import TabWidget


# --- Widgets used with CSS styling
# -----------------------------------------------------------------------------
class ButtonLinkLogin(ButtonLink):
    """Button used in CSS styling."""


class ButtonLabelLogin(ButtonLabel):
    """Button used in CSS styling."""


class ButtonHeaderUpdate(ButtonBase):
    """Button used in CSS styling."""


class ButtonLogin(ButtonPrimary):
    """Button used in CSS styling."""


class FrameHeader(FrameBase):
    """
    Frame used in CSS styling.

    Top application header.
    """


class FrameBody(FrameBase):
    """Frame used in CSS styling."""


class LabelHeaderLogo(LabelBase):
    """Label used in CSS styling."""


class LabelHeaderUpdate(LabelBase):
    """Label used in CSS styling."""


class LabelBeta(LabelBase):
    """Label used in CSS styling."""

    pass


# --- Main widget
# -----------------------------------------------------------------------------
class MainWindow(QMainWindow):
    """Main window widget."""

    sig_logged_in = Signal()
    sig_logged_out = Signal()

    DOCS_URL = 'https://docs.continuum.io/anaconda/navigator'

    # Web content
    VIDEOS_URL = "http://content.continuum.io/api/videos"
    EVENTS_URL = "http://content.continuum.io/api/events"
    WEBINARS_URL = "http://content.continuum.io/api/webinars"

    # Web content development site
    TEST_URL = "http://api-dev-continuum-content.pantheonsite.io/"
    TEST_VIDEOS_URL = TEST_URL + "api/videos"
    TEST_EVENTS_URL = TEST_URL + "api/events"
    TEST_WEBINARS_URL = TEST_URL + "api/webinars"

#    VIDEOS_URL = TEST_VIDEOS_URL
#    EVENTS_URL = TEST_EVENTS_URL
#    WEBINARS_URL = TEST_WEBINARS_URL

    def __init__(self, splash=None, config=CONF):
        """Main window widget."""
        super(MainWindow, self).__init__()

        self.app = QCoreApplication.instance()
        self.tracker = None
        self.splash = splash
        self.config = config
        self.fullscreen_flag = False
        self.maximized_flag = True

        # Anaconda API
        self.api = AnacondaAPI()
        self.api_info = self.api.download_get_api_info()
        self.busy = False
        self.logged = False
        self.username = ''
        self._login_text = 'Sign in to Anaconda Cloud'
        self.first_run = self.config.get('main', 'first_run')
        self.application_update_version = None

        # Widgets
        self.frame_header = FrameHeader(self)
        self.frame_body = FrameBody(self)
        self.label_logo = LabelHeaderLogo('ANACONDA NAVIGATOR')
        self.label_beta = LabelBeta('BETA')
        self.button_logged_text = ButtonLabelLogin('')
        self.button_logged_username = ButtonLinkLogin('')
        self.button_update_available = ButtonHeaderUpdate('')
        self.button_login = ButtonLogin(self._login_text)
        self.central_widget = QWidget()
        self.statusbar = self.statusBar()
        self.progressbar = QProgressBar()

        self.stack = TabWidget(self)
        self.stack.add_link('Documentation',
                            url="https://docs.continuum.io/anaconda/navigator")
        self.stack.add_link('Developer Blog',
                            url="https://www.continuum.io/blog/developer-blog")
        self.stack.add_link(
            'Feedback',
            url="https://github.com/ContinuumIO/anaconda-issues/issues")
        self.stack.add_social(
            'Twitter',
            url="https://twitter.com/ContinuumIO")
        self.stack.add_social(
            'Youtube',
            url="https://www.youtube.com/c/ContinuumIo")
        self.stack.add_social(
            'Github',
            url="https://github.com/ContinuumIO")
        self.home_tab = HomeTab(parent=self)
        self.environments_tab = EnvironmentsTab(parent=self)
        self.learning_tab = CommunityTab(
            parent=self,
            tags=['webinar', 'documentation', 'video', 'training'],
            content_urls=[self.VIDEOS_URL, self.WEBINARS_URL], )
        self.community_tab = CommunityTab(
            parent=self,
            tags=['event', 'forum', 'social'],
            content_urls=[self.EVENTS_URL], )

        # Note: Icons are set in CSS
        self.stack.addTab(self.home_tab, text='Home')
        self.stack.addTab(self.environments_tab, text='Environments')
        self.stack.addTab(self.learning_tab, text='Learning')
        self.stack.addTab(self.community_tab, text='Community')

        # Widget setup
        self.button_login.setDefault(True)
        self.label_logo.setPixmap(QPixmap(images.ANACONDA_NAVIGATOR_LOGO))
        self.setWindowTitle("Anaconda Navigator - Beta")
        self.statusbar.addPermanentWidget(self.progressbar)
        self.progressbar.setVisible(False)
        self.label_logo.setVisible(False)
        self.label_beta.setVisible(False)

        # Layout
        header_layout = QHBoxLayout()
        header_layout.addWidget(self.label_logo)
        header_layout.addWidget(self.label_beta)
        header_layout.addStretch()
        header_layout.addWidget(self.button_update_available)
        header_layout.addWidget(SpacerHorizontal())
        header_layout.addWidget(self.button_logged_text)
        header_layout.addWidget(self.button_logged_username)
        header_layout.addWidget(SpacerHorizontal())
        header_layout.addWidget(self.button_login)
        self.frame_header.setLayout(header_layout)

        body_layout = QHBoxLayout()
        body_layout.addWidget(self.stack)
        body_layout.setContentsMargins(0, 0, 0, 0)
        body_layout.setSpacing(0)
        self.frame_body.setLayout(body_layout)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.frame_header)
        main_layout.addWidget(self.frame_body)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)
        self.central_widget.setLayout(main_layout)
        self.setContentsMargins(0, 0, 0, 0)
        self.setCentralWidget(self.central_widget)

        # Signals
        self.button_login.clicked.connect(self.login)
        self.button_logged_username.clicked.connect(self.open_login_page)
        self.button_update_available.clicked.connect(self.update_application)
        self.stack.sig_current_changed.connect(self._track_tab)
        self.stack.sig_url_clicked.connect(self.open_url)

        # Setup
        self.api.set_data_directory(CHANNELS_PATH)
        self.update_style_sheet()

    # Helpers
    # -------------------------------------------------------------------------
    def _track_tab(self, index=None):
        """Track the active tab by index, or set `Home` when index is None."""
        if index is None:
            index = self.stack.currentIndex()

        text = self.stack.currentText().lower()

        if self.tracker:
            page = '/{0}'.format(text)
            self.tracker.track_page(page)

    def _metadata_updated(self, worker, path, error):
        """Callback for update_metadata."""
        self.set_splash('Updating repodata...')
        if error:
            logger.error(str(error))

        if path and os.path.isfile(path):
            with open(path, 'r') as f:
                data = f.read()
        try:
            self._metadata = json.loads(data)
        except Exception:
            self._metadata = {}

        active_channels = self.config.get('main', 'conda_active_channels',
                                          default=tuple())
        if not active_channels:
            active_channels = self.api.conda_get_condarc_channels()
            self.config.set('main', 'conda_active_channels', active_channels)

        self.api.update_repodata()
        self.api.sig_repodata_updated.connect(self._repodata_updated)

    def _repodata_updated(self, paths):
        """Callback for update repodata."""
        self.set_splash('Loading repodata...')
        self.api.sig_repodata_updated.disconnect(self._repodata_updated)

        if self.first_run:
            self.set_splash('Initial configuration...')
            # self.api.create_default_project()

        worker = self.api.client_load_repodata(paths, self._metadata)
        worker.sig_finished.connect(self.process_application_info)

    # --- Public API
    # -------------------------------------------------------------------------
    def setup(self):
        """
        Perform initial setup and configuration.

        This is the first method called after the main window has been created.
        """
        self.set_splash('Updating metadata...')

        # Get user info if it has previously logged in via anaconda client
        user = self.api.client_set_domain()
        self.update_login_status(user)
        self.setup_toolbars()
        self.set_application_icon()
        worker = self.api.update_metadata()
        worker.sig_finished.connect(self._metadata_updated)
        statusbar = self.statusBar()
        statusbar.setVisible(False)
        statusbar.setMaximumHeight(0)
        statusbar.hide()

    def process_application_info(self, worker, output, error):
        """Create application projects."""
        if error:
            logger.error(str(error))

        packages, apps = output
        self.conda_packages = packages
        self.conda_apps = apps
        processed_apps = self.api.process_apps(apps)
        self.post_setup(processed_apps)
        self.check_for_updates(packages)

    def post_setup(self, apps):
        """Setup tab widgets."""
        self.config.set('main', 'first_run', False)
        self.set_splash('Loading applications...')
        self.home_tab.setup_tab(apps)
        self.set_splash('Loading environments...')
        self.environments_tab.setup_tab(metadata=self._metadata)
        self.set_splash('Loading content...')
        self.community_tab.setup_tab()
        self.set_splash('Loading content...')
        self.learning_tab.setup_tab()
        self.update_style_sheet()

        self.showMaximized()
        self.post_visible_setup()

    def set_application_icon(self):
        """Set application icon."""
        app_icon = QIcon()
        app_icon.addFile(images.ANACONDA_ICON_16_PATH, QSize(16, 16))
        app_icon.addFile(images.ANACONDA_ICON_24_PATH, QSize(24, 24))
        app_icon.addFile(images.ANACONDA_ICON_32_PATH, QSize(32, 32))
        app_icon.addFile(images.ANACONDA_ICON_48_PATH, QSize(48, 48))
        app_icon.addFile(images.ANACONDA_ICON_256_PATH, QSize(256, 256))
        self.app.setWindowIcon(app_icon)

    def setup_toolbars(self):
        """Setup toolbar menus and actions."""
        menubar = self.menuBar()

        file_menu = menubar.addMenu('&File')
        file_menu.addAction(create_action(self, "&Preferences",
                                          triggered=self.show_preferences,
                                          shortcut="Ctrl+P"))
#        file_menu.addAction(create_action(self, "&Restart",
#                                          triggered=self.restart,
#                                          shortcut="Ctrl+R"))
        file_menu.addAction(create_action(self, "&Quit",
                                          triggered=self.close,
                                          shortcut="Ctrl+Q"))

        helpmenu = menubar.addMenu('&Help')
        helpmenu.addAction(create_action(self, "&Online Documentation",
                                         triggered=lambda:
                                             self.open_url(self.DOCS_URL)))
        helpmenu.addAction(create_action(self,
                                         "&Logs viewer",
                                         triggered=self.show_log_viewer,
                                         shortcut="F6"))
        helpmenu.addSeparator()
        helpmenu.addAction(create_action(self,
                                         "&About",
                                         triggered=self.show_about))

    def post_visible_setup(self):
        """Setup after show method has been applied."""
        if self.splash:
            self.splash.hide()

        self.config.set('main', 'first_run', False)

        # Start the tracker only after post_visible_setup
        self.tracker = GATracker()
        self._track_tab(0)  # Start tracking home
        self.fix_tab_ordering()
        self.show_welcome_screen()

    def check_for_updates(self, packages=None, version=None):
        """Check for application updates."""
        from distutils.version import LooseVersion as lv
        # Check if there is an update for navigator!
        version = version or self.api.conda_package_version(
            name='root', pkg='anaconda-navigator') or "0.0.1"
        # Temporal mock test
        # mock_versions = [version, '1.1.0']
        # packages['anaconda-navigator'] = {'versions': mock_versions}
        self.button_update_available.setEnabled(False)
        self.button_update_available.setVisible(False)

        if packages:
            package_data = packages.get('anaconda-navigator')
            if package_data:
                versions = package_data.get('versions')
                if versions and (lv(versions[-1]) > lv(version) or
                                 'dev' in version and
                                 versions[-1] == version.replace('dev', '')):
                    self.application_update_version = versions[-1]
                    self.button_update_available.setEnabled(True)
                    self.button_update_available.setVisible(True)

    def fix_tab_ordering(self):
        """Fix tab ordering of UI widgets."""
        return self
#        for tab in [self.community_tab, self.learning_tab]:
#            self.setTabOrder(self.stack.tabbar.buttons[-1],
#                             tab.filter_widgets[0])
#            for i in range(len(tab.filter_widgets) - 1):
#                self.setTabOrder(tab.filter_widgets[i],
#                                 tab.filter_widgets[i + 1])
#            self.setTabOrder(tab.filter_widgets[-1], tab.text_filter)
#            self.setTabOrder(tab.text_filter.button_icon, tab.list)
#            self.setTabOrder(tab.list, self.button_login)
#        self.button_login.setFocus()
#
#        self.setTabOrder(self.stack.tabbar.buttons[-1],
#                         self.environments_tab.text_search)
#
#        self.environments_tab.packages_widget.table_last_row.add_focus_widget(
#            self.button_login)
#        self.setTabOrder(self.environments_tab.packages_widget.table_last_row,
#                         self.button_login)

    # FIXME: The restart method needs to be called via an external script
    # So that a new version can be loaded. The current method keeps loading
    # a potentially outdated versions after an update.
    def restart(self):
        """Restart application."""
        self.splash.show_message("Restarting...")
        w = MainWindow(splash=self.splash)
        self.successor = w
        w.setup()
        w.show()
        self.close()

    def update_style_sheet(self):
        """Update custom CSS style sheet."""
        style_sheet = load_style_sheet()
        self.home_tab.update_style_sheet(style_sheet=style_sheet)
        self.environments_tab.update_style_sheet(style_sheet=style_sheet)
        self.community_tab.update_style_sheet(style_sheet=style_sheet)
        self.learning_tab.update_style_sheet(style_sheet=style_sheet)
        self.setStyleSheet(style_sheet)

    def set_splash(self, message):
        """Set splash message."""
        if self.splash:
            self.splash.show_message(message)
        QApplication.processEvents()

    # --- Login
    # -------------------------------------------------------------------------
    @property
    def conda_url(self):
        """Return the conda url based on the api info from config url."""
        api_info = self.api.download_get_api_info()
        conda_url = api_info.get('conda_url', 'https://conda.anaconda.org')
        conda_url = conda_url[:-1] if conda_url[-1] == '/' else conda_url
        return conda_url

    @property
    def api_url(self):
        """Return the api url from anaconda client config."""
        return self.api.client_get_api_url()

    def update_login_status(self, user_data=None):
        """Update login button and information."""
        if user_data:
            self.username = user_data.get('login', '')
            self.logged = True

        if self.logged:
            username = self.username
            token = self.api.client_load_token(self.api_url)
            url = "{0}/{1}".format(self.conda_url, username)
            self.button_logged_text.setText('Signed in as ')
            self.button_logged_username.setText(username)
            self.button_logged_username.setToolTip(url)
            self.button_login.setText('Sign out')
            self.environments_tab.packages_widget.set_token(token)
            self.button_logged_text.setVisible(True)
            self.button_logged_username.setVisible(True)
        else:
            self.button_logged_text.setText('')
            self.button_logged_username.setText('')
            self.button_logged_text.setVisible(False)
            self.button_logged_username.setVisible(False)
            self.button_login.setText(self._login_text)
        QApplication.restoreOverrideCursor()

    def login(self, logout=False):
        """Open up login dialog or log out depending on logged status."""
        if logout:  # Force a logout
            self.logged = True

        if self.logged:
            QApplication.setOverrideCursor(Qt.WaitCursor)
            self.api.client_logout()
            self.api.client_remove_token()
            self.logged = False
            self.sig_logged_out.emit()
            self.tracker.track_event('authenticate', 'logout',
                                     label=self.username)
        else:
            dlg = AuthenticationDialog(self.api, parent=self)

            if self.tracker:
                self.tracker.track_page('/login', pagetitle='Login dialog')

            if dlg.exec_():
                self.api.client_store_token(dlg.token)
                self.username = dlg.username
                self.logged = True
                self.sig_logged_in.emit()

                if self.tracker:
                    self.tracker.track_event('authenticate', 'login',
                                             label=self.username)
            self._track_tab()

        self.update_login_status()
        logger.debug(str((self.logged, self.username)))

    # --- Dialogs
    # -------------------------------------------------------------------------
    def show_preferences(self):
        """Display the preferences dialog and apply the needed actions."""
        dlg = PreferencesDialog(self)
        self.tracker.track_page('/preferences',
                                pagetitle='Preferences dialog')
        set_domains = self.environments_tab.packages_widget.update_domains

        dlg.sig_urls_updated.connect(set_domains)
        dlg.sig_urls_updated.connect(lambda au, cu: self.login(logout=True))
        dlg.exec_()
        self._track_tab()

    def show_about(self):
        """Display the `About` dialog with information on the project."""
        dlg = AboutDialog(self)
        self.tracker.track_page('/about', pagetitle='About dialog')
        dlg.exec_()
        self._track_tab()

    def show_log_viewer(self):
        """Display the logs viewer to the user."""
        dlg = LogViewerDialog()
        self.tracker.track_page('/logs', pagetitle='Log Viewer Dialog')
        dlg.exec_()
        self._track_tab()

    def show_welcome_screen(self):
        """Show initial splash screen."""
        if (getattr(self, 'showme', True) and
                self.config.get('main', 'show_startup', True)):
            from anaconda_navigator.widgets.dialogs.splash import FirstSplash

            self.showme = False
            self.splash.hide()
            dlg = FirstSplash()
            dlg.raise_()
            dlg.exec_()

    def toggle_fullscreen(self):
        """Toggle fullscreen status."""
        if self.isFullScreen():
            self.fullscreen_flag = False
            self.showNormal()

            if self.maximized_flag:
                self.showMaximized()
        else:
            self.maximized_flag = self.isMaximized()
            self.fullscreen_flag = True
            self.showFullScreen()

        self.label_logo.setVisible(self.fullscreen_flag)
        self.label_beta.setVisible(self.fullscreen_flag)

    # --- Update Navigator
    # -------------------------------------------------------------------------
    def _update_application(self, worker, output, error):
        if error:
            self.button_update_available.setEnabled(True)
            text = 'Anaconda Navigator Update error:'
            dlg = MessageBoxError(text=text, error=error,
                                  title='Application Update Error')
            self.tracker.track_page('/update/error',
                                    pagetitle='Update Application Error '
                                              'Message Box')
            dlg.exec_()
        else:
            self.button_update_available.setEnabled(False)
            text = ('Anaconda Navigator Updated succefully.\n\n'
                    'Please restart the application')
            dlg = MessageBoxInformation(text=text, title='Application Update')
            self.tracker.track_page('/update/successful',
                                    pagetitle='Application Update Succesful '
                                              'Message Box')
            dlg.exec_()
        self._track_tab()

    def update_application(self):
        """Update application to latest available version."""
        version = self.application_update_version
        if version:
            dlg = DialogUpdateApplication(version=version)
            height = self.button_update_available.height()
            point = self.button_update_available.mapToGlobal(QPoint(0, height))
            dlg.move(point)
            self.tracker.track_page('/update',
                                    pagetitle='Update Application Dialog')
            reply = dlg.exec_()
            if reply:
                self.tracker.track_event('application', 'updated', version)
                self.busy = True
                pkg = 'anaconda-navigator={}'.format(version)
                worker = self.api.conda_install(name='root', pkgs=[pkg])
                worker.sig_finished.connect(self._update_application)
                self.button_update_available.setEnabled(False)
                self.button_update_available.setVisible(False)
            self._track_tab()

    def update_status_bar(self, message='', timeout=0, val=-1, max_val=-1):
        """Update the status bar message."""
        statusbar = self.statusBar()
        if val != -1 and max_val != -1:
            self.progressbar.setVisible(True)
            self.progressbar.setValue(val)
            self.progressbar.setMaximum(max_val)
        else:
            self.progressbar.setVisible(False)

        if message:
            statusbar.showMessage(message, timeout)
        else:
            statusbar.clearMessage()
        statusbar.setVisible(False)
        statusbar.setMaximumHeight(0)
        statusbar.hide()

    # --- Url handling
    # -------------------------------------------------------------------------
    # TODO: Route ALL url handling to this method.
    def open_url(self, url, category=None, action=None):
        """Open url and track event."""
        qurl = QUrl(url)
        QDesktopServices.openUrl(qurl)
        self.tracker.track_event('help', 'documentation', url)

    def open_login_page(self):
        """Open logged in user anaconda page."""
        url = "{0}/{1}".format(self.conda_url, self.username)
        qurl = QUrl(url)
        QDesktopServices.openUrl(qurl)
        self.tracker.track_event('content', 'clicked', url)

    # --- Qt methods
    # -------------------------------------------------------------------------
    def closeEvent(self, event):
        """Catch close event."""
        # TODO: check if an update is not in progress or things might break!!
#        if self.busy:
        show_dialog = not self.config.get('main', 'hide_quit_dialog')
        if show_dialog:
            if self.tracker:
                self.tracker.track_page('/quit', pagetitle='Quit dialog')
            dlg = QuitApplicationDialog()
            reply = dlg.exec_()

            if not reply:
                event.ignore()
                self._track_tab()

    def keyPressEvent(self, event):
        """Override Qt method."""
        key = event.key()
        modifiers = event.modifiers()
        if (key == Qt.Key_F5 or
                (key == Qt.Key_R and modifiers & Qt.ControlModifier)):
            print('Refreshing stylesheets...')
            self.update_style_sheet()
        elif key == Qt.Key_F11:
            self.toggle_fullscreen()

        super(MainWindow, self).keyPressEvent(event)


# --- Local testing
# -----------------------------------------------------------------------------
def test():  # pragma: no cover
    """Run local test."""
    from anaconda_navigator.utils.qthelpers import qapplication
    app = qapplication()
    w = MainWindow()
    w.showMaximized()
    sys.exit(app.exec_())

if __name__ == "__main__":  # pragma: no cover
    test()
