'''
/** Original work Copyright 2024 damiano IU3QGD
 * https://to-be-defined
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *     Unless required by applicable law or agreed to in writing, software
 *     distributed under the License is distributed on an "AS IS" BASIS,
 *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *     See the License for the specific language governing permissions and
 *     limitations under the License.
 
 * Addition to the licence: NO company with more than 50employees can use this SW and you are NOT entitled to store this software 
 * on ANY storage that in some way attempts to monetize it (eg: github)
 * corporations and banks are too rich and too powerful, they MUST be split and ALL the money given back to the state
 
this should handle multi language resources, for what is best
There are tools for this, with some pretty good logic, but I wish to be able to change languege "on the fly"
And still have a way to undertand what is happening (meaning long names and not a single _ ).... ? really

So, THIS class should start as first (allocated lists) BUT will operate ONLY when the saved config are loaded
Labels that add themselves to the listener are candidate for translation 


'''


from __future__ import annotations

import codecs
import os
import pathlib
from tkinter import ttk, StringVar, filedialog
import tkinter
import typing

from app_config import AppConfig, ConfigSavable
from glob_fun import getApplicationResourcesPath
from glob_gui import LogPanel, JtkCombo, JtkPanelPackLeft, \
    JtkPanelPackTop, JtkWinToplevel, GUI_hide_show_window, JtkLabel
import qpystat

MLANG_languages_key="languages"
MLANG_rareg_key="BandPlan"

# ===========================================================================
# There is the need to wrap resources, so I can pick them up in different places
# core resources will be distributed with th application
# then there are language resources and radio related resources
# the user should have a way to select the appropriate resource
# this is a separate window since the size of overlapping panels is the MAX of all subpanels
# and this panel was messing up the intro panel !
class PrepperMlang(ConfigSavable,GUI_hide_show_window):

    # ----------------------------------------------------------------
    def __init__(self, stat : qpystat.Qpystat):
        
        self._stat = stat
        
        self._stat.appconfig.addMyselfToSavables(self)

        self._toplevel = JtkWinToplevel("Language Resources Window")

        # if something is not found, try to get it from here
        self._fallbackResdir = getApplicationResourcesPath()
        
        _apanel : ttk.Frame = self._newWorkPanel(self._toplevel)
        _apanel.pack(fill='both',expand=True)
        
        self.GUI_hide_window() 
        
        self._resLoadedListeners : list[ResourcesLoadedListener] = []
        
        self._label_key_value_map : dict[str, str] = {}
        
    # --------------------------------------------------------------------
    # show a window that has been hidden using the withdraw method
    def GUI_show_window(self):
        self._toplevel.deiconify()    

    # --------------------------------------------------------------------
    # hide completely a window
    def GUI_hide_window(self):
        self._toplevel.withdraw()    
        
    # --------------------------------------------------------------------------
    # an object can be added to the list of objets to call on resources loaded
    # not on constructor, since circular import issues
    def addToLoadedListeners(self, listener : ResourcesLoadedListener ):
        self._resLoadedListeners.append(listener)
        
        
    # -------------------------------------------------------------------------
    # Every label should be loaded indipendently, to avoid one bad translation to mess up everything
    def _onResourcesLoaded_A(self, alistener : ResourcesLoadedListener ):
        try:
            alistener.onResourcesLoaded(self)
        except Exception as _exc :
            self._println("_onResourcesLoadedListener: "+str(alistener)+' exc '+str(_exc))
        
    # ------------------------------------------------------------------------
    # @return true if the dir exist        
    def _checkTopResourcesDirExist(self) -> bool:
        
        a_dir : pathlib.Path = self.getMlangTopDir()
        
        if not a_dir.suffix == '.prepperdock':
            self._println('diretory '+str(a_dir)+" does not end with prepperdock")
            return False 
        
        if not a_dir.exists():
            self._println('diretory '+str(a_dir)+" does not exist")
            return False
        
        return True

        

        
    # -------------------------------------------------------------------------
    # called on swing thread when resource selector is changed
    # MUST load into ram the map between a key and a label
    def _onResourcesLoadedSwing(self):
        
        if self._checkTopResourcesDirExist():
        
            self._clickSaveResourceKeys()
        
            self._reloadLabelKeyValueMap() 
        
            for alistener in self._resLoadedListeners:
                self._onResourcesLoaded_A(alistener)
                
        else:
            
            self.GUI_show_window()
            
            self._clickSelectResourcesTopdir()
            
            
             
        
    # --------------------------------------------------------------------------
    # need to have some labels and a log area        
    def _newWorkPanel(self, parent_panel ) -> ttk.Frame:
        apanel = JtkPanelPackTop(parent_panel)
        
        apanel.addItem(JtkLabel(apanel,'Please select resources.prepperdock directory, Language and Region',style='Header1.TLabel'))

        apanel.addItem(self._newEditResourcesDir(apanel))
        apanel.addItem(self._newEditLangRareg(apanel))
        
        self._logPanel = LogPanel(apanel,"Resources Log")
        apanel.addItem(self._logPanel,fill='both',expand=True)

        return apanel

    # -----------------------------------------------------------------------
    def _println(self, msg : str ):
        self._logPanel.println(msg)



    # ---------------------------------------------------------------------------------
    # go trough all listener and ask them the label_key
    # Append a brief instruction at the beginning
    def _saveResourceKeys(self, afile ):

        afile.write('# you should write the translation to the english text after the TAB\n')
        afile.write('# You can use libreoffice calc, import - export as CSV, TAB separated\n')
        afile.write('# Or any text editor that shows you the TAB\n')
        afile.write('# Once you have done the translation you can put the result in the appropriate language ID directory\n')
        afile.write('# Calling the file labels.csv\n')
        afile.write('# Eg: languages/IT/labels.csv\n')
        afile.write('\n')

        for alistener in self._resLoadedListeners:
            akey = alistener.getResourceKey()
            if akey:
                afile.write(akey+'\t\n')

        


    # ---------------------------------------------------------------------------------
    def _newEditLangRareg(self, parent_panel : ttk.Frame ):
        apanel = JtkPanelPackLeft(parent_panel)

        self._edit_language = JtkCombo(apanel, width=10)
        self._edit_language.bind("<<ComboboxSelected>>", self._onComboBoxSelected)

        apanel.addItem(JtkLabel(apanel,"Language",style="Bold.TLabel"))
        apanel.addItem(self._edit_language)
        
        self._edit_rareg = JtkCombo(apanel, width=15)

        apanel.addItem(JtkLabel(apanel,"Radio Bandplan Region",style="Bold.TLabel"))
        apanel.addItem(self._edit_rareg)
        
        return apanel

    
    # ---------------------------------------------------------------------------------
    def _newEditResourcesDir(self, parent_panel : ttk.Frame ) -> ttk.Widget:
        apanel = JtkPanelPackTop(parent_panel)
        
        apanel.addItem(self._newEditResourcesDir_a(apanel))
        apanel.addItem(ttk.Label(apanel, textvariable=self._edit_topdir))
        
        return apanel
        
    # ---------------------------------------------------------------------------------
    # by default the value is this
    def _newEditResourcesDir_a(self, parent_panel : ttk.Frame) -> ttk.Widget:
        apanel = JtkPanelPackLeft(parent_panel)
        
        initial_resdir=pathlib.Path.home() / 'resources.prepperdock'
        
        self._edit_topdir = StringVar(apanel,value=str(initial_resdir))

        apanel.addItem(ttk.Button(apanel,text="Select" , command=self._clickSelectResourcesTopdir ))
        apanel.addItem(JtkLabel(apanel,'"resources.prepperdock" directory',style="Bold.TLabel"))
        return apanel

    # ---------------------------------------------------------------------------------
    def _reloadLabelKeyValueMap_B(self, aline : str ):
        #self._println("KV "+str(aline))

        if not aline:
            return

        if aline[0] == '#':
            return

        # strip newline off the line...
        aline = aline.strip()

        a_split = aline.split('\t')
        
        if len(a_split) <2:
            return
        
        # remove possibly inserted quotes 
        key = str(a_split[0]).strip('"')
        
        if not key:
            return

        # remove possibly inserted quotes 
        value = str(a_split[1]).strip('"')
        
        if not value:
            return
        
        self._label_key_value_map[key] = value
        
        #self._println("KV "+str(self._label_key_value_map))

    # ---------------------------------------------------------------------------------
    # given a file, try to import the key values
    def _reloadLabelKeyValueMap_A(self, afile ):
        self._label_key_value_map.clear()
        
        for line in afile:
            self._reloadLabelKeyValueMap_B(line)
        
    # ---------------------------------------------------------------------------------
    # this should possibly be used from a NON swing thread...
    def _reloadLabelKeyValueMap(self):

        langdir : pathlib.Path = self._getLanguageDir()

        file_name = langdir / 'labels.csv'

        try:
            #with open(file_name, 'r') as csv_file:
            with codecs.open(str(file_name), encoding='utf-8') as csv_file:
                self._reloadLabelKeyValueMap_A(csv_file)
        except Exception as exc :
            self._println("_reloadLabelKeyValueMap "+str(exc))

    
    # ---------------------------------------------------------------------------------
    # Only the language combo should be listened to
    # when the language cahnges, the mapping for the resources key should be reloaded
    
    def _onComboBoxSelected(self, event ):
        self._onResourcesLoadedSwing()
    
    # ---------------------------------------------------------------------------------
    # this should go trough all components registered for callback and save into a file the 
    # key that is used to pick up the multilanguage label
    # note that the key is the english value of the label
    def _clickSaveResourceKeys(self):
        
        adir = self.getMlangTopDir()
        
        file_name : pathlib.Path = adir / "labels_keys.csv"
        self._println("Save ResourceKeys to "+str(file_name))
        
        try:
            with open(file_name, 'w') as csv_file:
                self._saveResourceKeys(csv_file)
        except Exception as exc :
            self._println("_clickSaveResourceKeys "+str(exc))



    # ----------------------------------------------------------------------------
    def _clickSelectResourcesTopdir(self):
        
        initdir=pathlib.Path.home()
        
        # apparently there is no extension filter available
        folder_path = filedialog.askdirectory(parent=self._toplevel,initialdir=initdir,mustexist=True)
        if not folder_path:
            self._println('NO directory selected')
            return

        if not folder_path.endswith('prepperdock'):
            self._println('diretory '+str(folder_path)+" does not end with prepperdock")
            return 
        
        a_dir = pathlib.Path(str(folder_path))
        
        if not a_dir.exists():
            self._println('diretory '+str(folder_path)+" does not exist")
            return 
            
        self._println('selected '+str(folder_path))
        self._edit_topdir.set(folder_path)
        
        # now, I should update the combo for the language and regulatory domain
        self._fillComboValues(self._edit_language, MLANG_languages_key)
        self._fillComboValues(self._edit_rareg, MLANG_rareg_key)
        
        # when the top directory is selected, can save the keys
        self._clickSaveResourceKeys()
        
        self._onResourcesLoadedSwing


    # ------------------------------------------------------------------------
    # can be used to retrieve the topmost path
    def getMlangTopDir(self) -> pathlib.Path:
        return pathlib.Path(self._edit_topdir.get())
        
    # ------------------------------------------------------------------------
    # I need to do a dir listing and fill the fiven combo
    def _fillComboValues(self, combo : JtkCombo, pre_dir : str):
        adir = self.getMlangTopDir()
        adir = adir / pre_dir
        
        filelist : list[str] = []

        try:        
            for filename in os.listdir(adir):
                fullpath = adir / filename
                if os.path.isdir(fullpath):
                    filelist.append(filename)
            
            combo['values'] = filelist

        except Exception as _exc :
            self._println("_fillComboValues "+str(_exc))            
        
    
    # -----------------------------------------------------------------------
    def _getIfNotEmpty(self, v_in : str, v_default : str ) -> str:
        if v_in:
            return v_in
        else:
            return v_default
    
    # -----------------------------------------------------------------------
    def _validateRsrTopdir(self, v_in : str ) -> str:
        if v_in:
            return v_in
        else:
            return str(getApplicationResourcesPath())
        
    # -----------------------------------------------------------------------
    # this could possibly throw an error...
    def _getLanguageDir(self) -> pathlib.Path:
        adir = self.getMlangTopDir()
        adir = adir / 'languages'
        return adir / self._edit_language.get()
        
    # -----------------------------------------------------------------------
    # this could possibly trow an error
    def getBandplanDir(self) -> pathlib.Path:
        adir = self.getMlangTopDir()
        adir = adir / 'BandPlan'
        return adir / self._edit_rareg.get()
        
    # -----------------------------------------------------------------------
    # this should return the path of the fiven fname in the selected language
    # the point is that if it is NOT found, then an attempt is made to look in the resource directory
    def getLanguageFname(self, fname : str ) -> pathlib.Path:
        langdir : pathlib.Path = self._getLanguageDir()
        return langdir / fname
    
    # --------------------------------------------------------------------
    # implement the config savable
    # once config is loaded, there should be a check for "is it valid" .... maybe
    def appImportConfig(self, cfg : AppConfig ):
        adict = cfg.getDictValue("prepper_resources_cfg", {})

        try:
            # first, so it works even if errors
            self._toplevel.setGeometry(adict['gui_cfg'])
            
            self._edit_topdir.set(self._validateRsrTopdir(adict['rsr_topdir']))
            self._println(self._edit_topdir.get())

            self._fillComboValues(self._edit_language, MLANG_languages_key)
            self._fillComboValues(self._edit_rareg, MLANG_rareg_key)

            self._edit_language.set(self._getIfNotEmpty(adict['rsr_language'], 'EN'))
            self._edit_rareg.set(self._getIfNotEmpty(adict['rsr_rareg'], 'EUROPE'))
            
            # run this fun in swing thread, this is not a swing thread
            self._toplevel.after_idle(self._onResourcesLoadedSwing)
            
        except Exception as _exc :
            self._println('appImportConfig'+str(_exc))            


    # --------------------------------------------------------------------
    # implement the config savable
    def appSaveConfig(self, cfg : AppConfig ):
        adict : dict[str,typing.Any] = {}

        adict['gui_cfg'] = self._toplevel.getGeometry()

        adict['rsr_topdir'] = self._edit_topdir.get()
        adict['rsr_language'] = self._edit_language.get()
        adict['rsr_rareg'] = self._edit_rareg.get()

        cfg.setDictValue("prepper_resources_cfg", adict )            
    
    # -------------------------------------------------------------------
    # return a PhotoImage that pick up the file from the right language resources
    # if no image is found then it will return an empty image
    def getLanguagePhotoimage(self,filename) -> tkinter.PhotoImage:
        try:
            fpath : pathlib.Path = self.getLanguageFname(filename)
            self._println("image "+str(fpath))
            return tkinter.PhotoImage(file = fpath)
        except Exception as _exc:
            print("getLanguagePhotoimage: cannot find ",filename)
            return tkinter.PhotoImage(file="") 

    # -------------------------------------------------------------------
    # return a translated version of the given key
    # can be called on callback to get the new translation
    def getLanguageLabel(self, en_key : str ) -> str:
        return self._label_key_value_map.get(en_key,en_key)
    

# =============================================================================
# a class that is interested on being informed when resources are loaded must implement this
# the method will be called with the main label storage and the class can pick up the resource it needs
class ResourcesLoadedListener():
    
    # -------------------------------------------------------------------------
    # this method will be called when new resources are loaded and thecomponent should update
    def onResourcesLoaded(self, mlang : PrepperMlang ):
        pass    
    
    # ---------------------------------------------------------------------------
    # this will be called to ask the resource key that will be used by this component
    def getResourceKey(self) -> str:
        return ''
    
    
# =============================================================================
# I wish to have a label that adjust the content when language resource changes
# so, it need ot register itself to be informed of changes
# NOTE that there is NO WAY that the label has the correct content at construction time
# so, just ..... relax and wait for onResourcesLoaded event
# Since the label need to register with the resource manager.... I need to pass it....
# Well, screw it, this is a snake, I can get the value from globals
# Jes, I know, Java COULD have it locked up but satanists have screwed it too, on purpose
# Ah, too fast....
# NO CAN DO to get the handler from globals, since, circular import issues
# hopefully the whole python crap will eventually burn in hell and take the whole snake with it
# if you wonder, why I am still here... because, I need to know exactly how bad it is
# yes, you can write a one page script and it will work
# AH, where is the smarty panty that "I wrote a TWO pages script and it works !"
# We need LESS people programming, NOT more
# do you realize that the whole crap is just because some idiot could not type {} ?

class MlangLabel(ttk.Label,ResourcesLoadedListener):    

    # -------------------------------------------------------------------------------------------
    # @param en_text is the KEY to pick up the correct label value
    def __init__(self, parent_panel : ttk.Frame, en_text : str , mlang : PrepperMlang, **varargs ):
        ttk.Label.__init__(self, parent_panel, text=en_text, **varargs)
        
        self._en_text = en_text
        self._mlang = mlang
        
        mlang.addToLoadedListeners(self)
        
    # -------------------------------------------------------------------------------------------
    # should pick the updated value for the label and  update it
    # NOTE: IF the new label INCLUDES a newline, ttk.Label WILL adjust the height of the space being used
    # SO, you CAN include more than one line, if you wish
    def onResourcesLoaded(self, mlang : PrepperMlang ):
        translated = mlang.getLanguageLabel(self._en_text)
        self.configure(text=translated)    
        
    # -------------------------------------------------------------------------------------------
    # return the key that will be used to update this label text
    def getResourceKey(self) -> str:
        return self._en_text
