# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018  Rickard Lindberg, Roger Lindberg
#
# This file is part of Timeline.
#
# Timeline is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Timeline is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Timeline.  If not, see <http://www.gnu.org/licenses/>.


import os

from timelinelib.canvas.data import TimePeriod
from timelinelib.wxgui.utils import display_error_message
from timelinelib.wxgui.utils import display_warning_message
from timelinelib.wxgui.utils import get_user_ack
from timelinelib.wxgui.frames.mainframe.lockhandler import LockHandler, LockedException
from timelinelib.meta.about import get_title
from timelinelib.wxgui.dialogs.exceptionreport.exceptionreport import exception_report
from timelinelib.db.utils import get_modification_date


class MainFrameController:
    """
    Controller for the
    :class:`~timelinelib.wxgui.frames.mainframe.mainframe.MainFrame`
    window.
    It is responsible for the business logic.
    """

    def __init__(self, main_frame, db_open_fn, config):
        self._main_frame = main_frame
        self._db_open_fn = db_open_fn
        self._config = config
        self._timeline = None
        self._last_changed = None
        self._lock_handler = LockHandler(main_frame)
        self._error_dialog = display_error_message

    def populate(self):
        """
        Find out if there is info saved in the config object of a last used timeline.
        If so open that timeline.
        """
        if self._config.has_files():
            self.open_or_create_a_timeline(self._config.first_file)
        elif self._config.has_recently_opened_files():
            self.open_timeline_if_exists(self._config.get_latest_recently_opened_file())

    def open_or_create_a_timeline(self, path, timetype=None, save_current_data=True):
        """
        If 'path' exists open and display the timeline file. Otherwise create an empty
        timeline file at 'path'.
        """
        if path is not None:
            if save_current_data and self._main_frame.timeline:
                self._main_frame.timeline.save_view_properties(self._main_frame.view_properties)
            try:
                self._timeline = self._db_open_fn(path, timetype=timetype)
            except Exception as e:
                message = _("Unable to open timeline '%s'.") % path + "\n\n" + str(e)
                exception_report(self._error_dialog, message)
            else:
                self._config.append_recently_opened(path)
                self._main_frame.UpdateOpenRecentSubmenu()
                self._timeline.path = path
                self._main_frame.DisplayTimeline(self._timeline)
                self._main_frame.DisplayReadonly(self._get_readonly_text_in_status_bar())
                self._last_changed = get_modification_date(self._timeline.path)
                self._main_frame.UpdateNavigationMenuItems()
                self._main_frame.EnableDisableMenus()
                self._main_frame.SetTitle(get_title(self._timeline.path))

    def open_timeline_if_exists(self, path):
        """
        Open the timeline file given by path.

        If the file doesn't exist an error message is displayed.
        """
        if os.path.exists(path):
            self.open_or_create_a_timeline(path)
        else:
            display_error_message(_("File '%s' does not exist.") % path, self._main_frame)

    def ok_to_edit(self):
        """Locks the file and returns True if no one else is editing the timeline."""
        try:
            if self._timeline is None:
                return True
            if self._timeline.is_read_only():
                return False
            if self._lock_handler.locked(self._timeline.path):
                display_warning_message("The Timeline is Locked by someone else.\nTry again later")
                return False
            if not os.path.exists(self._timeline.path):
                self._lock_handler.lock(self._timeline.path, self._timeline)
                return True
            last_changed = get_modification_date(self._timeline.path)
            if last_changed > self._last_changed:
                ack = get_user_ack(
                    _("Someone else has changed the Timeline.\nYou have two choices!\n  1. Set Timeline in Read-Only mode.\n  2. Synchronize Timeline.\n\nDo you want to Synchronize?"))
                if ack:
                    self._reload_from_disk()
                else:
                    self.set_timeline_in_readonly_mode()
                return False
            if last_changed > 0:
                self._lock_handler.lock(self._timeline.path, self._timeline)
            return True
        except LockedException:
            return False

    def edit_ends(self):
        """Release the lock after an edit session."""
        if self._timeline and self._lock_handler.the_lock_is_mine(self._timeline.path):
            self._last_changed = get_modification_date(self._timeline.path)
            self._lock_handler.unlock(self._timeline.path)

    def get_visible_categories(self):
        """
        Used by the function export-to-listbox to select events to export.

        filtered_listbox_export is a flag that can be set in the settings dialog
        and decides if the export function shall filter out events having categories
        that are not shown in the timeline.

        Returns:
            list: A list of categories
        """
        if self._config.filtered_listbox_export:
            return [cat for cat in self._timeline.get_categories()
                    if self._main_frame.view_properties.is_category_visible(cat)]
        else:
            return [cat for cat in self._timeline.get_categories()]

    def get_start_and_end_for_all_visible_events(self):
        """Return start time for the first visible event and the end time for the last visible event."""
        return self._timeline.get_start_and_end_for_all_visible_events(self._main_frame.view_properties.filter_events)

    def set_timeline_in_readonly_mode(self):
        """
        Is called to set a timeline in read-only-mode, which means that it cannot be edited.

        This state is not saved when the timeline application is closed or when another timeline
        is opened. When reopening the timeline file the read-only-mode is gone.
        """
        self._timeline.set_readonly()
        self._main_frame.DisplayReadonly(self._get_readonly_text_in_status_bar())
        self._main_frame.EnableDisableMenus()

    def exit(self):
        """
        Actions taken when the application is closed:

            * Stop timers
            * Save configuration data
            * Save timeline data
            * Destroy the MainFrame window
        """
        self._save_application_config()
        try:
            if self._main_frame.timeline and self._main_frame.controller.ok_to_edit():
                self._main_frame.timeline.save_view_properties(self._main_frame.view_properties)
        finally:
            self._main_frame.controller.edit_ends()
        self._main_frame.Destroy()

    def set_error_dialog(self, error_dialog):
        """For test purposes."""
        self._error_dialog = error_dialog

    def _save_application_config(self):
        """Save user settings to persistent storage."""
        self._config.set_window_size(self._main_frame.GetSize())
        self._config.set_window_pos(self._main_frame.GetPosition())
        self._config.window_maximized = self._main_frame.IsMaximized()
        self._config.sidebar_width = self._main_frame.main_panel.timeline_panel.sidebar_width
        self._config.write()

    def _reload_from_disk(self):
        self.open_or_create_a_timeline(self._timeline.path, save_current_data=False)
        self._main_frame.canvas.Redraw()

    def _period_for_all_visible_events(self):
        start_and_end = self.get_start_and_end_for_all_visible_events()
        if start_and_end:
            return TimePeriod(*start_and_end).zoom(-1)
        else:
            return self._timeline.displayed_period

    def _get_readonly_text_in_status_bar(self):
        """Return the text to be displayed in the third column of the status bar."""
        if self._timeline is not None and self._timeline.is_read_only():
            return _("read-only")
        return ""
