#!/usr/bin/env python3
"""
    Copyright (C) Ηλίας Ηλιάδης, 2019-09-24; Ηλίας Ηλιάδης <iliadis@kekbay.gr>

    This file is part of «Sudoku as puzzle».

    «Sudoku as puzzle» 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.

    «Sudoku as puzzle» 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 «Sudoku as puzzle».  If not, see <http://www.gnu.org/licenses/>.
"""

"""
        WARNING !!!

Since we are in python, any subclass of this class
will take over any method with the same name.
So do not declare here methods that exist also in the subclass.
(subclass is the class that uses the class here as Base.
Imagine the subclass as a super-duper class,
although in computing superclass is the base class)
"""
#FIXME: correct the version
__version__ = '0.1.28'
VERSIONSTR = 'v. {}'.format(__version__)

try:
    import os
    import sys

    # Gtk and related
    from gi import require_version as gi_require_version
    gi_require_version('Gtk', '3.0')
    from gi.repository import Gtk
    from gi.repository import Gdk
    from gi.repository import GObject
    # space for extra Gtk imports

    # Configuration and message boxes
    from auxiliary import *
    from constants import *

    # Load static methods
    from windowmain_statics import *

except ImportError as eximp:
    print(eximp)
    sys.exit(-1)

class WindowMainBase(object):
    #FIXME: fix the docstring.
    """ Main window with all components. """

    def __init__(self, *args, **kwargs):
        # Set the app
        self.myparent = None
        self.custom_args = kwargs['custom_args']

        # bind settings,options to a class variable
        global settings
        settings = self.settings
        global options
        options = self.options

        # Before builder.
        self._run_before_builder()

        # Read GUI from file and retrieve objects from Gtk.Builder
        thebuilder = Gtk.Builder()
        thebuilder.set_translation_domain(self.Application.id)
        try:
            thebuilder.add_from_file(os.path.join(self.Application.custom_args.APP_DIR,
                'ui',
                'windowmain.glade')
                )
        except GObject.GError:
            print("Error reading GUI file")
            raise

        # Fire up the main window
        #We must get the object from builder to set the Application
        #before loading the objects, so that the rest will be loaded
        #using locale
        self.WindowMain = thebuilder.get_object("WindowMain")
        self.WindowMain.set_application(self.Application)
        self._get_from_builder(thebuilder)
        self.mywindow = self.WindowMain
        self._post_initialisations()
        self.mywindow.show()

#********* Auto created "class defs" START ************************************************************
    def _run_before_builder(self):

        self.return_position  = None
        pass

    def _get_from_builder(self, builder):
        """ Create self names for easy access. """
        self.BoxForOptionsInfo = builder.get_object('BoxForOptionsInfo')
        self.BoxMain = builder.get_object('BoxMain')
        self.BoxOnBottom = builder.get_object('BoxOnBottom')
        self.BoxOnTop = builder.get_object('BoxOnTop')
        self.ButtonAbout = builder.get_object('ButtonAbout')
        self.ButtonContinue = builder.get_object('ButtonContinue')
        self.ButtonExit = builder.get_object('ButtonExit')
        self.ButtonNew = builder.get_object('ButtonNew')
        self.ButtonOptions = builder.get_object('ButtonOptions')
        self.GridForCurrentOptions = builder.get_object('GridForCurrentOptions')
        self.LabelDifficulty = builder.get_object('LabelDifficulty')
        self.LabelDummyDifficulty = builder.get_object('LabelDummyDifficulty')
        self.LabelDummyNumbers = builder.get_object('LabelDummyNumbers')
        self.LabelNumbers = builder.get_object('LabelNumbers')
        self.LabelOptionsInfo = builder.get_object('LabelOptionsInfo')
        self.LabelVersion = builder.get_object('LabelVersion')
        self.WindowMain = builder.get_object('WindowMain')

        # Connect signals existing in the Glade file.
        builder.connect_signals(self)

        # Connect generated by OCPgenerator signals:
        # to builder's main window
        # first are the defaults for window
        self.WindowMain.connect('delete-event', self.on_WindowMain_delete_event)
        self.WindowMain.connect('destroy', self.on_WindowMain_destroy)
        self.WindowMain.connect('size-allocate', self.on_WindowMain_size_allocate)
        self.WindowMain.connect('window-state-event', self.on_WindowMain_window_state_event)
        self.ButtonAbout.connect('clicked', self.on_ButtonAbout_clicked)
        self.ButtonContinue.connect('clicked', self.on_ButtonContinue_clicked)
        self.ButtonExit.connect('clicked', self.on_ButtonExit_clicked)
        self.ButtonNew.connect('clicked', self.on_ButtonNew_clicked)
        self.ButtonOptions.connect('clicked', self.on_ButtonOptions_clicked)

    def _post_initialisations(self):
        """ Do some extra initializations.

        Display the version if a labelVersion is found.
        Set defaults (try to load them from a configuration file):
            - Window size and state (width, height and if maximized)
        Load any custom settings from a configuration file.
        """
        if 'parent' in self.custom_args:#Is a child window, get the parent window
            self.myparent = self.custom_args['parent']
            #if has parent check for transient
            # modality can be false, and parent may not be present
            if 'transient' in self.custom_args:
                self.mywindow.set_transient_for(self.myparent)
                if 'modal' in self.custom_args:
                    self.mywindow.set_modal(True)
                    # print("is modal")

        if 'trigger_before_exit' in self.custom_args:
            # must be a function on calling class
            self.trigger_before_exit = self.custom_args['trigger_before_exit']
            self.return_parameters = None

        # Bind message boxes.
        self.MessageBox = MessageBox(self.WindowMain, self.Application)
        self.msg = self.MessageBox.Message
        self.are_you_sure = self.MessageBox.are_you_sure

        # Reset MainWindow to a default or previous size and state.
        width = settings.get('width', 350)
        height = settings.get('height', 350)
        self.WindowMain.set_title(self.Application.custom_args.localizedname)
        self.WindowMain.resize(width, height)
        self.WindowMain.set_icon(self.Application.icon)
        if settings.get_bool('maximized', False):
            self.WindowMain.maximize()
        # Set the label for LabelVersion
        self.LabelVersion.set_label("v." + self.Application.custom_args.version)
        self.LabelVersion.set_tooltip_text(_("Version of this window:") + "\n" + VERSIONSTR)
        # Load any other settings here.

#********* Auto created handlers START *********************************
    def on_ButtonAbout_clicked(self, widget, *args):
        """ Handler for ButtonAbout.clicked. """
        self.MessageBox.AboutBox()
        return True

    def on_ButtonContinue_clicked(self, widget, *args):
        """ Handler for ButtonContinue.clicked. """
        self.open_windowpuzzle(True)
        return True

    def on_ButtonExit_clicked(self, widget, *args):
        """ Handler for ButtonExit.clicked. """
        self.exit_requested()
        return True

    def on_ButtonNew_clicked(self, widget, *args):
        """ Handler for ButtonNew.clicked. """
        self.open_windowpuzzle(False)
        return True

    def on_ButtonOptions_clicked(self, widget, *args):
        """ Handler for ButtonOptions.clicked. """
        self.open_windowoptions()
        return True

    def on_WindowMain_key_release_event(self, widget, event, *args):
        """ Handler for WindowMain.key-release-event. """
        txt = Gdk.keyval_name(event.keyval)
        if type(txt) == type(None):
            return False

        unichar = chr(Gdk.keyval_to_unicode(event.keyval))
        txt = txt.replace('KP_', '')
        if event.get_state() & Gdk.ModifierType.CONTROL_MASK:
            if txt.lower() == "n":
                self.open_windowpuzzle(False)
                return True
            elif txt.lower() == "c":
                if self.ButtonContinue.get_sensitive():
                    self.open_windowpuzzle(True)
                return True
            elif txt.lower() == "p":
                self.open_windowoptions()
                return True
            elif txt.lower() == "q":
                self.exit_requested()
                return True
            elif txt.lower() == "a":
                self.MessageBox.AboutBox()
                return True
#********* Auto created handlers END ***********************************

#********* Standard handlers START *************************************
    def msg_not_yet(self):
        self.msg(_('Not yet implemented'))

    def on_WindowMain_delete_event(self, widget, event, *args):
        """ Handler for our main window: delete-event. """
        return (self.exit_requested())

    def on_WindowMain_destroy(self, widget, *args):
        """ Handler for our main window: destroy. """
        return (self.exit_requested('from_destroy'))

    def on_WindowMain_size_allocate(self, widget, allocation, *args):
        """ Handler for our main window: size-allocate. """
        self.save_my_size()

    def on_WindowMain_window_state_event(self, widget, event, *args):
        """ Handler for our main window: window-state-event. """
        settings.set('maximized',
            ((int(event.new_window_state) & Gdk.WindowState.ICONIFIED) != Gdk.WindowState.ICONIFIED) and
            ((int(event.new_window_state) & Gdk.WindowState.MAXIMIZED) == Gdk.WindowState.MAXIMIZED)
            )
        self.save_my_size()

#********* Standard handlers END ***************************************
#********* Standard exit defs START *********************************************************
    def exit_requested(self, *args, **kwargs):
        """ Final work before exit. """
        self.WindowMain.set_transient_for()
        self.WindowMain.set_modal(False)
        self.set_unhandled_settings()# also saves all settings
        if 'from_destroy' in args:
            return True
        else:
            # Check if we should provide info to caller
            if 'trigger_before_exit' in self.custom_args:
                self.trigger_before_exit(exiting = True,
                    return_parameters = self.return_parameters)
            self.WindowMain.destroy()

    def present(self):
        """ Show the window. """
        pass

    def save_my_size(self):
        """ Save the window size into settings, if not maximized. """
        if not settings.get_bool('maximized', False):
            width, height = self.WindowMain.get_size()
            settings.set('width', width)
            settings.set('height', height)

    def set_unhandled_settings(self):
        """ Set, before exit, any settings not applied during the session.

        Additionally, flush all settings to .conf file.
        """
        # Set any custom settings
        # which where not setted (ex. on some widget's state changed)

        # Save all settings
        settings.save()
#********* Standard exit defs END **************************************
#********* Auto created "class defs" END **************************************************************

    def show_defaults(self):
        DIFFICULTY = 'easy'
        use_dict = "standard"
        DIFFICULTY = options.get("DIFFICULTY", DIFFICULTY)
        if DIFFICULTY not in ["simple", "easy", "intermediate", "expert", "any"]:
            DIFFICULTY = 'easy'

        strings = options.get("custom_dict","")
        strings_list = [x.strip() for x in strings.split(",")]
        if len(strings_list) == 9:
            strings_list.insert(0, ' ')
            THEDICTS['custom'] = strings_list
        use_dict = options.get("use_dict", use_dict)
        self.LabelDifficulty.set_label(_(DIFFICULTY))
        self.LabelNumbers.set_label(','.join(THEDICTS[use_dict][1:]))

        has_good_history = True
        try:
            last_puzzle_string = options.get("last_puzzle_string", "")
            if len(last_puzzle_string.strip()) != 81:
                has_good_history = False
            else:
                last_history_string = options.get("last_history","")
                thelist1 = last_history_string.strip().split(",")
                for amove in thelist1:
                    if len(amove.split(":")) != 4:
                        has_good_history = False
                        break
                if has_good_history:
                    last_history_position = options.get("last_history_position","-1")
                    if int(last_history_position) < 0:
                        has_good_history = False
                    last_history_position = int(last_history_position)
                    if has_good_history:
                        last_difficulty = options.get("last_difficulty", "")
                        if last_difficulty not in ["simple", "easy", "intermediate", "expert"]:
                            has_good_history = False
                        if has_good_history:
                            last_seconds_passed = int(options.get("last_seconds_passed","0"))
                            if last_seconds_passed == 0:
                                has_good_history = False
        except:
            print(_("WARNING! Wrong values for history in configuration file"))
            has_good_history = False
        if has_good_history:
            self.last_game = {'puzzle_string':last_puzzle_string,
                    'history_string':last_history_string,
                    'history_position': last_history_position,
                    'last_seconds_passed': last_seconds_passed,
                    'difficulty':last_difficulty}
        self.ButtonContinue.set_sensitive(has_good_history and (not options.get_bool('solved', True)))

    #call this from wherever event you like
    def open_windowpuzzle(self, continue_last=False):
        #save current position
        self.return_position = self.mywindow.get_position()
        #create args to send
        some_custom_args = {}
        # provide a local function to be triggered on exit
        some_custom_args['trigger_before_exit'] = self.trigger_from_windowpuzzle
        some_custom_args['parent'] = self.mywindow
        if continue_last:
            some_custom_args['continue'] = continue_last
            some_custom_args['last_game'] = self.last_game.copy()

        #import the class
        from windowpuzzle import WindowPuzzleClass
        #create an instance and add it to self.Application.MyArgs
        #as property
        self.Application.custom_args.windowpuzzle = WindowPuzzleClass(application = self.Application,
            custom_args = some_custom_args)
        self.Application.custom_args.windowpuzzle.mywindow.connect("destroy", self.windowpuzzle_destroyed)
        #open the window
        self.Application.custom_args.windowpuzzle.present()
        #hide this window or comment next line if not needed
        self.mywindow.hide()

    def windowpuzzle_destroyed(self, *args, **kwargs):
        """ Custom function to be triggered after opened window is destroyed.

        """
        self.Application.custom_args.windowpuzzle = None
        self.refresh_me()

    def trigger_from_windowpuzzle(self, *args, **kwargs):
        """ Custom function to be triggered before opened window exit.

        Opened "windowpuzzle" can use it to:
        - Return to this class a value.
        - Get from this class a processed value before exit.

        Notes
        -----
        This function will run BEFORE the opened window is destroyed.
        If you want to do something AFTER the window is destroyed,
        you must use windowpuzzle_destroyed.
        """
        # dummy return of my class name
        return self.__class__.__name__

    def refresh_me(self):
        #TODO: create and refresh also results?

        #TODO: return to a position depending on opened child window
        #not in the previous position
        #Return to previous position
        #Cause in case of multiple monitors
        #it returns to the center of the screen
        #in which a "fast mouse move" goes
        if self.return_position:
            self.mywindow.move(*self.return_position)
            self.return_position = None
        self.mywindow.show()
        self.show_defaults()

    #call this from wherever event you like
    def open_windowoptions(self):
        #save current position
        self.return_position = self.mywindow.get_position()
        #create args to send
        some_custom_args = {}
        # provide a local function to be triggered on exit
        some_custom_args['trigger_before_exit'] = self.trigger_from_windowoptions
        #import the class
        from windowoptions import WindowOptionsClass
        #create an instance and add it to self.Application.MyArgs
        #as property
        self.Application.custom_args.windowoptions = WindowOptionsClass(application = self.Application,
            custom_args = some_custom_args)
        self.Application.custom_args.windowoptions.mywindow.connect("destroy", self.windowoptions_destroyed)
        #open the window
        self.Application.custom_args.windowoptions.present()
        #hide this window or comment next line if not needed
        self.mywindow.hide()

    def windowoptions_destroyed(self, *args, **kwargs):
        """ Custom function to be triggered after opened window is destroyed.

        """
        self.Application.custom_args.windowoptions = None
        self.refresh_me()

    def trigger_from_windowoptions(self, *args, **kwargs):
        """ Custom function to be triggered before opened window exit.

        Opened "windowoptions" can use it to:
        - Return to this class a value.
        - Get from this class a processed value before exit.

        Notes
        -----
        This function will run BEFORE the opened window is destroyed.
        If you want to do something AFTER the window is destroyed,
        you must use windowoptions_destroyed.
        """
        # dummy return of my class name
        return self.__class__.__name__

#********* Window class  END***************************************************************************
