"""
========================================================================
USER APP SETTINGS - FILE (part of Frigcal)

Fetch and store user configurations in a JSON text file.
FC uses a JSON instead of PPUS's pickle: readability is useful.

This started with PPUS's code but has been trimmed much because:
- There are only 4 settings in FC, so automation isn't warranted
- Settings-screen mods are applied immediately in widget callbacks

In FC, self.settings here always reflects the settings file's 
contents, and Settings-screen widget mods only mod running app 
behavior.  self.settings is not updated until a call to 
update_and_save_settings() pushes user mods out to the file.

This ensures that calendar-folder auto-saves on asks change 
only the folder in the settings file, but requires settings 
to be managed explicitly on startup and restores and changes
in the Settings screen:

- globalfontsize, globalfontname: loaded and applied on startup 
  in App.start(), reapplied on Settings Restore and change here, 
  saved on Save in Settings here (CUT: globalfontname)

- maxbackups: loaded and passed into storage object on startup in 
  App.start(), resaved in storage object on Settings restores and 
  changes here, used by storage object on calendar saves

- editbubbles: loaded and set in app on startup in App.start(), 
  reset in app object on Settings restores and changes here,
  used by .kv UI code when dialogs and screens opened

- colortheme: loaded and applied on startup in App.build(), 
  reapplied on Settings Restore and change, saved on Save in Settings

- calendarsfolder: loaded at startup; changed and auto-saved by 
  the storage object on folder asks, including Settings changes
  here; and used on file access by storage object

Unlike others, calendarsfolder is not reset or repplied on Settings 
Restores: file value is always current because auto-saved on asks, 
and going back to preset None would require a check for changes, a 
new ask, and a calendars reload, which seems too much UI complexity
========================================================================
"""

from common import *



class AppSettings:

    """
    Manage persistent user-configurable settings.
    One instance, created by and stored in App instance.

    Public interface: attribute get/set for specific settings, 
    update_and_save_settings(), and restore_setting().  
    """


    def __init__(self, app):
        self.app = app                     # link back to main.py's App instance
        self._init_factory_presets()       # setup preset defaults 
        self._load_settings_from_file()    # initial load, file => self.settings



    def __getattr__(self, name):  

        """
        Map undef attrs to settings dict for brevity:
            App.settings.x => App.settings.settings['x']
        NB: name won't be a setting if same as a real attr name.
        """

        if 'settings' in self.__dict__ and name in self.settings:
            trace('getattr setting:',  name)
            return self.settings[name] 
        else:
            trace('getattr other')
            raise NameError(f'Undefined name: {name}')



    def __setattr__(self, name, value):

        """
        Map settings attrs to settings dict for brevity:
            App.settings.x=y => App.settings.settings['x']=y
        NB: unlike getattr, this works even if same as real attr.
        """

        if 'settings' in self.__dict__ and name in self.settings:
            trace('setattr setting:', name, value)
            self.settings[name] = value
        else:
            trace('setattr other:', name)
            object.__setattr__(self, name, value)



    def _init_factory_presets(self):

        """
        --------------------------------------------------------------------
        The 1:1 mapping from setting name to its GUI filed isn't as 
        clear-cut as in PPUS - some settings may be structured, and
        all are manually propagated to/from GUI fields as needed.  

        Calendars are all *.ics files, calendar events are stored in 
        .ics files, and category colors are stored in settings here
        and are the sole source of event colors in 4.0.  This is in 
        a method in case self is ever required for a setting value.
       
        All settings are in the Settings screen.  Calendar folder is set 
        both by Settings on demand and by asks before loads and saves,
        and it is auto-saved after asks because a manual Save would be 
        onerous.  Theme is also in main menu where it can only be toggled.
        --------------------------------------------------------------------
        """

        self.factory_presets = dict(

            #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            # Calendar folder in Settings screen
            # Auto saved on asks, manually saved/restored in Settings
            #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

            # Used on load/save, set by asks or Settings, initial falsy None
            # On PCs: always a pathname str, for POSIX calls
            # On Android: content URI py str, converted from/to java Uri for SAF

            calendarsfolder = None,                  # path or URI, false till ask

            #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            # Misc Settings-screen settings
            # Manually saved/restored in Settings
            #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

            maxbackups = 25,                         # number saves in _Backups/

            editbubbles = 'Enabled',                 # or 'Disabled', text handles+bubbles

            defaultcalendar = '',                    # event-dialog prefill if > 1 cals & exists

            colortheme = 'Dark',                     # 'Dark'|'Light', also a menu toggle

            globalfontsize = FONT_SIZE_DEFAULT,      # '15sp' to abs, set_font_size()

           #globalfontname = FONT_NAME_DEFAULT,      # CUT; roboto, etc., set_font_name()

            # toggles; TBD: user visible?
            retainLiteralBackslashes = True,         # icalendar format workaround in icsfiletools
            chooserShowHiddenFiles = False,          # folder-chooser option (TBD)
            
            #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            # Event colors shown in event-edit dialog and event-list dialog
            # Not user changeable (apart from unsupported JSON file edits...)
            #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

            # Major change in 4.0: calendars can no longer be colored,
            # because doing so requires users to sync or reset the 
            # settings file on each device they use.  Instead, colors 
            # are now based only on event category, which must be set
            # to one of a list of preset colors.  This ensures that a
            # calendar file renders the same on all devices with app.
            # Also now colors just event overscore bars: not (fg, bg).

            categorycolors = {

                # Used for events with a category: demoed in pulldown, not in Settings
                # Predefined primary, named, and theme-friendly colors, unchangeable
                # Now: just {'isocolorname': 'isocolorname'} for UX simplicity
                # Was: {'categoryname': ([r, g, b, a?] | #rrggbb(aa?) | 'isocolorname')}

                color: color for color in 

                    ['red',        'green',       'blue',       'cyan',      
                     'yellow',     'purple',      'lime',       'orange',   
                     'salmon',     'teal',        'pink',       'gray',
                     'steelblue',  'magenta',     'tan',        'maroon',     
                     'violet',     'chocolate',   'blueviolet', 'khaki',
                     'tomato',     'bisque',      'cadetblue',  'gold',       
                     'navy',       'yellowgreen', 'slateblue',  'sienna',
                     'black',      'white',
                    ] 

                    # Poor in light mode: 'azure', 'wheat', 'beige', 'lavender'
                    # Indistinguishable: 'aqua'=='cyan', 'fuchsia'=='magenta'
                    # Material-design accent color for blue looks like yellow 
                    # black/white are mapped to #121212 /#fafafa: black-ish 
                    # and white-ish, useful for no color bar on Month per theme
            },
        )



    def _load_settings_from_file(self):
 
        """
        --------------------------------------------------------------------
        Load all settings from dict in app-private JSON text file.
        Can use POSIX/filepath on Android: it's in app's own storage. 
        On PCs: in install folder (Windows/Linux) or ~ folder (macOS).
 
        Was pickle originally: 
        settings_file = open(settings_path, 'rb')
        settings = pickle.load(settings_file)
        --------------------------------------------------------------------
        """

        # in platform-specific folder
        app_priv_path = self.app.storage_path_app_private() or ''
        settings_path = osjoin(app_priv_path, SETTINGS_FILE)
        trace(settings_path)

        if not osexists(settings_path):
            settings = self.factory_presets.copy()    # first run: defaults
        else:
            try:
                # JSON text => Python objects
                settings_file = open(settings_path, 'r', encoding='utf8')
                settings = json.load(settings_file)
                settings_file.close()
            except:
                settings = self.factory_presets.copy()
                self.app.info_message('Cannot load settings: please report.', usetoast=True)

        # save file mirror 
        self.settings = settings

        # update loaded settings for anything new in presets
        for key in self.factory_presets: 
            if key not in self.settings:
                self.settings[key] = self.factory_presets[key]



    def _save_settings_to_file(self, settings):

        """
        --------------------------------------------------------------------
        Save all settings to dict in app-private JSON text file.
        Can use POSIX/filepath on Android: it's in app's own storage. 
        On PCs, this is either in install folder or macOS ~ folder.

        Was pickle originally: 
        settings_file = open(settings_path, 'wb')
        pickle.dump(settings, settings_file)
        --------------------------------------------------------------------
        """

        # in platform-specific folder
        app_priv_path = self.app.storage_path_app_private() or ''
        settings_path = osjoin(app_priv_path, SETTINGS_FILE)

        try:
            # Python => JSON
            settings_file = open(settings_path, 'w', encoding='utf8')
            json.dump(settings, settings_file, indent=4)
            settings_file.close()
        except:
            self.app.info_message('Cannot save settings: please report.', usetoast=True)



    def update_and_save_settings(self, **newsettings):

        """
        On Settings-screen Save and callbacks of its widgets.
        Call with one or more keyword args for setting=value:
 
            <this>(calendarsfolder=newpath)
            <this>(globalfontsize=20, colortheme='Dark', ...) 
        
        Updates all passed settings, only, both in memory and 
        in the persistence file.  Settings not passed are not 
        changed in either.  Nit: it's inefficient to save all 
        each time, but the set is small.
        """

        # pick up all passed settings from GUI's fields
        self.settings.update(**newsettings)

        # save full settings dict to json file
        self._save_settings_to_file(self.settings)



    def restore_settings(self):

        """
        Restore all Settings-screen settings to original "factory" presets.
        This resets the in-memory dict but does not update any GUI widgets.
        Prior tbd: should this also write them to persistence file now?
        No: this proved to be unwarranted in the PPUS app; user must Save.
        Update: this skips and does not restore calendarsfield because 
        doing so would be a UI mess; see the top of thisfile for info. 
        """

        # repopulate internal copy from defaults

        refills = self.factory_presets.copy()      # all preset values
        del refills['calendarsfolder']             # except for folder...
        self.settings.update(refills)




# Additional docs...

        """
        --------------------------------------------------------------------
        Settings start with the presets above before any saves, and 
        settings are saved manually on each Settings-screen Save tap.  

        The Settings-screen Restore button restores all settings, including
        the calendar-folder path, and the path is auto-saved after asks.

        Stored in app-private storage (not app install), in a JSON-file
        dict, where the .kv file's IDs == dict keys in settings.
  
        On PCS, app-private is install or ~/Library/appname.  On Android,
        app-private is app's data folder, can't be seen by explorers or 
        other apps, and goes away on app uninstall unless user toggles 
        the save option in uninstall popup? (manifest fragile setting tbd).

        Copies any unique items in presets to settings loaded from the
        file, so that they added to file on later saves.  Else, first 
        value may come from .kv, not presets.  This also makes new versions
        of the app backward compatible with existing settings files: new 
        keys are auto-added to the file as presets.  Running an old version
        of the app with a new settings will simply ignore newer keys.
        --------------------------------------------------------------------
        """

