"""
(c) 2020 FEI SAS, a part of Thermo Fisher Scientific. All rights reserved.
Created on Tue Sep 15 22:42:49 2020

@author: fabien.arnaud
"""

# Bookmarks are an association of a name, and a list of TCL commands necessary
#  to retrieve a given state
# Bookmarks are stored in Text Edit ports
# The text of the port contains
# - the name of the bookmark, preceded by 'bm_'
# - the '#' character as a separator
# - the tcl command
# Bookmarks are stored in an roder, so addition, insertion, move up and down,
# of a bookmarks necessitate the sorting of the ports content
# The list of bookmarks has its own order, which is the same as the one in the
# ports
# Free ports (with no bookmarks in them) are tagged with the text 'bm_free#'


import _hx_core
import re

DEBUG = False
max_bookmarks_count = 100
if DEBUG : max_bookmarks_count = 5
    

##############################################################################
# HELPERS 
##############################################################################

#_____________________________________________________________________________
# returns a name based on the string, which last characters are an increased
# number. If the first name is not finished with digits, new are added
def get_next_numbered_name( string ) :
    
    # default values
    base = string
    next_id = 1
    fmt = '{:d}'
    
    m = re.search(r'\d+$', string)
    # if string ends with digits, change the defaults
    if m is not None:
        count_digits = len(m.group())
        fmt = '{:0' + str( count_digits ) + '}'
        next_id = int(m.group()) + 1
        base = string[:-len(m.group())]

    # Build the result
    new_string = base + fmt.format(next_id)
    return new_string



##############################################################################
# Inherits from PyScriptObject to create a new module
##############################################################################
class Bookmarks(_hx_core.PyScriptObject):

    #_________________________________________________________________________
    # Constructor
    def __init__(self):
        
        self.dumb = True
        
        self.dont_go_to_bookmark = False

        # Hide default ports inherited from PyScriptObject
        if DEBUG:
            self.ports.data.visible = False
            self.ports.filename.visible = True
            self.ports.startStop.visible = True
            self.ports.showConsole.visible = True
            # Display the console associated to this module for debugging
            self.ports.showConsole.buttons[0].hit = True
            self.fire()
        else:
            self.ports.data.visible = False
            self.ports.filename.visible = False
            self.ports.startStop.visible = False
            self.ports.showConsole.visible = False

        # new bookmark name, 'append' and 'insert' buttons
        _hx_core.HxPortGeneric(self, 'add_bookmark_port', 'New bookmark')
        self.ports.add_bookmark_port.tooltip = 'Choose new bookmark name and push the "Append" or the "Insert" button'
        self._tcl_interp("add_bookmark_port insertText 0 25") # use TCL to specify width of text widget
        self.p_newname_txt = self.ports.add_bookmark_port.items[0]
        #self.ports.add_bookmark_port.items[0].text = 'Bookmark 1'
        self.p_newname_txt.text = 'Bookmark 1'
        self.ports.add_bookmark_port.items.insert(
            1, _hx_core.HxPortGeneric.GenericPushButton(caption='Append'))
        self.p_append_btn = self.ports.add_bookmark_port.items[1]
        self.ports.add_bookmark_port.items.insert(
            2, _hx_core.HxPortGeneric.GenericPushButton(caption='Insert'))
        self.p_insert_btn = self.ports.add_bookmark_port.items[2]
        self._tcl_interp("add_bookmark_port insertPushButton 3 5 12") # use TCL to specify width of text widget
        

        # bookmarks list, 'next' and 'previous' buttons
        _hx_core.HxPortGeneric(self, 'choose_port', 'Choose')
        self.ports.choose_port.items.insert(
            0, HxPortGeneric.GenericComboBox(elements=[]))
        self.p_bookmark_cbb = self.ports.choose_port.items[0]
        self.ports.choose_port.items.insert(
            1, HxPortGeneric.GenericPushButton( caption = 'Previous' ) )
        self.p_previous_btn = self.ports.choose_port.items[1]
        self.ports.choose_port.items.insert(
            2, HxPortGeneric.GenericPushButton( caption = 'Next' ) )
        self.p_next_btn = self.ports.choose_port.items[2]
        self.ports.choose_port.pinned = True
      

        # Edit bookmark
        _hx_core.HxPortGeneric(self, 'edit_bookmark_port', 'Edit')
        self.ports.edit_bookmark_port.tooltip = 'Edit current bookmark'
        self.ports.edit_bookmark_port.items.insert(
            0, _hx_core.HxPortGeneric.GenericPushButton(caption='Update'))
        self.p_update_btn = self.ports.edit_bookmark_port.items[0]
        self._tcl_interp("edit_bookmark_port insertText 1 25") # use TCL to specify width of text widget
        self.p_rename_txt = self.ports.edit_bookmark_port.items[1]
        self.ports.edit_bookmark_port.items.insert(
            2, _hx_core.HxPortGeneric.GenericPushButton(caption='Rename'))
        self.p_rename_btn = self.ports.edit_bookmark_port.items[2]

            
        # Actions            
        _hx_core.HxPortButtonList( self, 'actions_port', 'Actions')
        self.ports.actions_port.tooltip = "Actions on the selected bookmark"
        self.ports.actions_port.buttons = [
            _hx_core.HxPortButtonList.Button( caption = 'Move up'),
            _hx_core.HxPortButtonList.Button( caption = 'Move Down'),
            _hx_core.HxPortButtonList.Button( caption = 'Delete') ]
        self.p_move_up_btn = self.ports.actions_port.buttons[0]
        self.p_move_dn_btn = self.ports.actions_port.buttons[1]
        self.p_delete_btn = self.ports.actions_port.buttons[2]
        
        
        # Internal use

        # Create the ports to store the bookmarks
        for i in range( max_bookmarks_count ):
            bm_id = 'bm_' + str(i)
            _hx_core.HxPortTextEdit( self, bm_id, bm_id )      
            port = getattr( self.ports, bm_id )
            port.text='bm_free#'
            if not DEBUG: 
                port.visible = False
        

    #_________________________________________________________________________    
    # This function is called when a port is modified
    def update(self):

        # If a bookmark port is modified, that means that we might be loading module from project
        for i in range( max_bookmarks_count ):
            bm_id = 'bm_' + str(i)
            port = getattr( self.ports, bm_id )
            if port.is_new :
                if len(port.text) > 0:
                    bookmark_name = port.text[3:port.text.find('#')]
                    if bookmark_name != 'free' :
                        self.p_bookmark_cbb.elements.append( bookmark_name )
                        self.p_bookmark_cbb.selected = self.p_bookmark_cbb.elements.index( bookmark_name )
                        self.p_rename_txt.text = bookmark_name
                        # get next name. If it is already used, get the next one
                        next_name = get_next_numbered_name( bookmark_name )
                        while self.get_bookmark_port( next_name ) is not None :
                            next_name = get_next_numbered_name( next_name )
                        # pre-fill the name of the next bookmark
                        self.p_newname_txt.text = next_name
        
        # React to events
        if self.p_append_btn.hit :
            self.add_bookmark( self.p_newname_txt.text )
            self.dont_go_to_bookmark = True

        if self.p_insert_btn.hit :          
            self.add_bookmark( self.p_newname_txt.text, insert=True )
            self.dont_go_to_bookmark = True

        if self.p_bookmark_cbb.is_new :
            if self.dont_go_to_bookmark :
                self.dont_go_to_bookmark = False
            else :
                self.goto_bookmark( self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected ] )
            self.p_rename_txt.text = self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected ]
        
        if self.p_previous_btn.hit :
            self.p_bookmark_cbb.selected -= 1
            self.goto_bookmark( self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected ] )

        if self.p_next_btn.hit :
            self.p_bookmark_cbb.selected += 1
            self.goto_bookmark( self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected ] )
        
        if self.p_update_btn.hit:
            self.update_bookmark( self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected ] )
        
        if self.p_rename_btn.hit:
            self.rename_current_bookmark( self.p_rename_txt.text )
        
        if self.p_delete_btn.hit:
            self.delete_bookmark( self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected ] )

        if self.p_move_up_btn.hit :
            # Move in the ports
            bm_port = self.get_bookmark_port( self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected] )
            prev_port = self.get_bookmark_port( self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected-1] )
            tmp = bm_port.text
            bm_port.text = prev_port.text
            prev_port.text = tmp
            # Move in the combo box
            prev_bm = self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected - 1]
            self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected - 1] = self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected]
            self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected ] = prev_bm
            self.p_bookmark_cbb.selected -= 1
            
            
        if self.p_move_dn_btn.hit :
            # Move in the ports
            bm_port = self.get_bookmark_port( self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected] )
            next_port = self.get_bookmark_port( self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected+1] )
            tmp = bm_port.text
            bm_port.text = next_port.text
            next_port.text = tmp
            # Move in the combo box
            next_bm = self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected + 1]
            self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected + 1] = self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected]
            self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected ] = next_bm
            self.p_bookmark_cbb.selected += 1
            
        # Enable or disable the ports depending on the environment :
        # - almost nothing enabled if there is no bookmarks
        # - next/previous and move up/down buttons available dependng on list selexcted item position
        # - bookmark creation and renaming dependent on the text containing the name
        if len( self.p_bookmark_cbb.elements ) > 0 :
            self.p_bookmark_cbb.enabled = True
            if self.p_bookmark_cbb.selected > 0:
                self.p_previous_btn.enabled = True
                self.p_move_up_btn.enabled = True
            else:
                self.p_previous_btn.enabled = False
                self.p_move_up_btn.enabled = False
            if self.p_bookmark_cbb.selected < len(self.p_bookmark_cbb.elements) - 1 :
                self.p_next_btn.enabled = True
                self.p_move_dn_btn.enabled = True
            else:
                self.p_next_btn.enabled = False
                self.p_move_dn_btn.enabled = False
                
            self.p_update_btn.enabled = True
            self.p_delete_btn.enabled = True
            
        else :
            self.p_bookmark_cbb.enabled = False
            self.p_previous_btn.enabled = False
            self.p_next_btn.enabled = False
            self.p_update_btn.enabled = False
            self.p_delete_btn.enabled = False
            self.p_move_up_btn.enabled = False
            self.p_move_dn_btn.enabled = False
            
        self.p_rename_btn.enabled = False
        if len( self.p_bookmark_cbb.elements ) > 0:
            if self.p_bookmark_cbb.elements[self.p_bookmark_cbb.selected] != self.p_rename_txt.text :
                self.p_rename_btn.enabled = True
            
            
    #_________________________________________________________________________    
    # This function is called when a port is modified OR when the Apply button is clicked
    def compute(self):
        pass
    
    #_________________________________________________________________________
    # returns the actual port of a given bookmark if it exists
    def get_bookmark_port ( self, bookmark_name ):
        searched_label = 'bm_' + bookmark_name
        for bm_port_name in ( [p for p in self.portnames if p[:3] == 'bm_'] ):
            bm_port = getattr( self.ports, bm_port_name )
            bm_name = bm_port.text[:bm_port.text.find('#')]
            if bm_name == searched_label :
                return bm_port
        return None
    
    #_________________________________________________________________________
    # returns the status of the pool, i.e. the TCL commands to get back to current status
    def get_pool_status ( self ) :
        # Get all modules names in a list :
        # - replace spaces in "bracketed" names with "dummy spaced" names and back
        all_modules = _hx_core._tcl_interp('all')
        for name in re.findall(r'\{.*?\}', all_modules):
            all_modules = all_modules.replace(name,name.replace(' ','<-->')[1:-1])
        all_modules = [m.replace('<-->', ' ') for m in all_modules.split()]
            
        # List of TCL commands that will restore current state
        commands = []
        
        # Add commands to retrieve spatial data transformations and viewer mask
        data_modules = [m for m in all_modules if int(hx_project.get(m)._tcl_interp('isOfType "HxSpatialData"')) == 1 ]
        for m in data_modules:
            commands.append ( '"' + m + '" setTransform ' + hx_project.get(m)._tcl_interp('getTransform') )
            commands.append ( '"' + m + '" setViewerMask ' + hx_project.get(m)._tcl_interp('getViewerMask') )
            
        
        # Add commands to restore display modules. Display modules are of Type HxModule but not of type HxCompModules
        display_modules = [m for m in all_modules if int(hx_project.get(m)._tcl_interp('isOfType "HxModule"')) == 1 
                                and int(hx_project.get(m)._tcl_interp('isOfType "HxCompModule"')) == 0]
        for m in display_modules:
            commands += hx_project.get(m)._tcl_interp('getState').splitlines()
    
        # remove module creation and module selection
        commands = [c for c in commands if c[:6] != 'create']
        commands = [c for c in commands if c[-6:] != 'select']
        
        # remove creation of measures
        commands = [c for c in commands if 'GUI addMeasure' not in c]
        
        # Add command to retreive camera parameters for each visible viewer
        viewer_ids = _hx_core._tcl_interp('viewer getAllVisibleViewerIDs').split()
        for v_id in viewer_ids :
            commands += _hx_core._tcl_interp('viewer ' + v_id + ' getCamera').replace( 'viewer', 'viewer ' + v_id ).split('\n')
            
        # Add commands to restore viewer mask of compute modules
        compute_modules = [m for m in all_modules if int(hx_project.get(m)._tcl_interp('isOfType "HxCompModule"')) == 1]
        for m in compute_modules:
            commands.append ( '"' + m + '" setViewerMask ' + hx_project.get(m)._tcl_interp('getViewerMask') )
        
        # returns list of commands, separated by '<-->'
        return '<-->'.join( commands )
    
    
    #_________________________________________________________________________
    # returns the status of the pool, i.e. the TCL commands to get back to current status
    def set_pool_status ( self, status ) :
        if DEBUG: print('set_pool_status', status)
        # status is the list of commands, separated by '<-->'
        commands = status.split('<-->')
        for c in commands: 
            _hx_core._tcl_interp (c)
        
    
    #_________________________________________________________________________    
    # Action : Delete a bookmark
    def delete_bookmark( self, bookmark_name ):
        if DEBUG : print( 'delete_bookmark (' + bookmark_name  + ')') 
        
        # retrieve port
        bm_port = self.get_bookmark_port( bookmark_name )
        
        # if bookmark doesn't exist, nothing to do
        if bm_port is None : return
        
        # move up all following ports
        for i in range(int(bm_port.name[3:]), max_bookmarks_count-1):
            dst_port = getattr( self.ports, 'bm_'+str(i))
            src_port = getattr( self.ports, 'bm_'+str(i+1))
            dst_port.text = src_port.text
        dst_port = getattr( self.ports, 'bm_'+str(max_bookmarks_count-1))
        dst_port.text = 'bm_free#'

        # Build new list of bookmarks in the combo box
        new_items = []
        for i in range( len( self.p_bookmark_cbb.elements ) ) :
            if self.p_bookmark_cbb.elements[i]  != bookmark_name :
                new_items.append( self.p_bookmark_cbb.elements[i] )
        self.p_bookmark_cbb.elements = new_items
        if len( self.p_bookmark_cbb.elements ) == 0 :
            self.p_rename_txt.text = ''
        else:
            if(self.p_bookmark_cbb.selected > 0 ): self.p_bookmark_cbb.selected -= 1
            self.p_rename_txt.text = self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected ]
        
    
    #_________________________________________________________________________    
    # Action : Go to a bookmark
    def goto_bookmark(self, bookmark_name):
        if DEBUG : print( 'goto_bookmark (' + bookmark_name  + ')')
        bm_port = self.get_bookmark_port( bookmark_name )
        if bm_port is None :
            print ( 'Error : unable to find the bookmark')
            return

        self.set_pool_status( bm_port.text[bm_port.text.find('#')+1:] )
        
    
    #_________________________________________________________________________    
    # Action : Add a new bookmark
    def add_bookmark(self, bookmark_name, insert = False ):
        if DEBUG : print( 'add_bookmark (' + bookmark_name  + ', insert = ' + str(insert) + ')' )
        
        # If bookmark name is not yet used
        if self.get_bookmark_port( bookmark_name ) is None :
            
            # search for a free port to store the bookmark
            if(insert):
                # move down all port following the one selected, and free that one
                port = self.get_bookmark_port( 'free' )
                for i in range(int(port.name[3:]), self.p_bookmark_cbb.selected, -1):
                    dst_port = getattr( self.ports, 'bm_'+str(i))
                    src_port = getattr( self.ports, 'bm_'+str(i-1))
                    dst_port.text = src_port.text
                    src_port.text = 'bm_free#'
                
            # get first free port and set bookmark name in it
            port = self.get_bookmark_port( 'free' )
            if port is None :         
                print( 'No more bookmarks available' )
                return
            port.text = 'bm_' + bookmark_name + '#'
            
            # add bm to the combobox list
            # if len(self.p_bookmark_cbb.elements) > 0 : 
            #     self.dont_go_to_bookmark = True
            if insert:
                self.p_bookmark_cbb.elements.insert( self.p_bookmark_cbb.selected, bookmark_name )
            else :
                self.p_bookmark_cbb.elements.append( bookmark_name )
            self.p_bookmark_cbb.selected = self.p_bookmark_cbb.elements.index( bookmark_name )
            self.p_rename_txt.text = bookmark_name
            
            # get next name. If it is already used, get the next one
            next_name = get_next_numbered_name( bookmark_name )
            while self.get_bookmark_port( next_name ) is not None :
                next_name = get_next_numbered_name( next_name )
            
            # pre-fill the name of the next bookmark
            self.p_newname_txt.text = next_name
            
            # update the bookmark
            self.update_bookmark( bookmark_name )
        
        else :
            print( 'WARNING: id already in use')
    
    #_________________________________________________________________________    
    # Action : Rename current bookmark
    def rename_current_bookmark( self, new_name ):
        port = self.get_bookmark_port( self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected ] )
        port.text = 'bm_' + new_name + port.text[port.text.find('#'):]
        self.p_bookmark_cbb.elements[ self.p_bookmark_cbb.selected ] = new_name
        self.p_rename_txt.text = new_name
            
    #_________________________________________________________________________    
    # Action : Update bookmark (also called when adding or removing a bookmark)
    def update_bookmark( self, bookmark_name ):
        if DEBUG : print( 'update_bookmark (' + bookmark_name  + ')')
        port = self.get_bookmark_port( bookmark_name )
        if port is None :
            print( 'WARNING: bookmark unavialable')
            return
        port.text = port.text[:port.text.find('#')+1] + self.get_pool_status()

        

