#!/usr/bin/python3

'''
migrate.py
'''

import sys
import os
from shutil import *
from tkinter import *
from tkinter import filedialog
import configparser
import gettext

if os.path.dirname(__file__).startswith('/usr/share'):
    localeDir = '/usr/share/locale'
    INSTYPE = ['pkg', 'linuxcnc']
    HALLIB = '/usr/share/linuxcnc/hallib'
else:
    path = os.path.dirname(__file__).split('configs')[0]
    localeDir = os.path.join(path, 'share/locale')
    INSTYPE = ['rip', os.path.join(path, 'scripts/linuxcnc')]
    HALLIB = os.path.join(path, 'lib/hallib')

iconPath = os.path.join(os.path.dirname(__file__), 'lib/images/chips_plasma.png')

gettext.install("linuxcnc", localedir=localeDir)
_ = gettext.gettext

class Dialog(Toplevel):
    def __init__(self, parent, title, text, buttons=1, insType=False, entry=None):
        super().__init__()
#        self.transient(parent)
        self.grab_set()
        self.attributes('-type', 'popup_menu')
        self.overrideredirect(True)
        self.resizable(False, False)
        self.reply = False
        self.value = StringVar()
        self.entry = entry
        self.parent = parent
        self.focus_set()
        justify = 'left' if 'Migration Log' in title else 'center'
        f = Frame(self, relief='ridge', borderwidth=4)
        f.grid(row=0, column=1, padx=1, pady=1)
        t = Label(f, text=title)
        t.grid(row=0, column=0, columnspan=3, sticky='ew')
        l = Label(f, text=f"{text}\n", justify=justify)
        l.grid(row=1, column=0, columnspan=3, padx=8, pady=(8,0))
        if entry:
            e = Entry(f, textvariable=self.value, justify='center')
            e.grid(row=2, column=0, columnspan=3, sticky='ew', padx=8, pady=(0,8))
            e.insert(END, entry)
            e.icursor(END)
            e.focus_set()
        if insType:
            text = 'Copy Command'
            key = '<c>'
        else:
            text = 'Cancel'
            key = '<Key-Escape>'
        b1 = Button(f, text='OK', width=12, command=lambda:self.on_button(1))
        self.bb1 = self.bind('<Key-Return>', lambda e:self.on_button(1))
        if buttons == 3:
            b2 = Button(f, text='View Log', width=12, command=lambda:self.on_button(2))
            self.bb2 = self.bind('<v>', lambda e:self.on_button(2))
            b2.grid(row=3, column=1, pady=(0,8))
        if buttons > 1:
            b0 = Button(f, text=text, width=12, command=lambda:self.on_button(0))
            self.bb0 = self.bind(key, lambda e:self.on_button(0))
            b1.grid(row=3, column=0, sticky='w', padx=(8,0), pady=(0,8))
            b0.grid(row=3, column=2, sticky='e', padx=(0,8), pady=(0,8))
        else:
            b1.grid(row=3, column=1, padx=(0,8), pady=(0,8))
        parent.eval(f"tk::PlaceWindow {self} center")

    def on_button(self, button):
        if button == 2:
            title = 'plasmac2 Migration Log'
            text = ''
            with open(self.parent.logFile, 'r') as inFile:
                for line in inFile:
                    text += line
            Dialog(self.parent, title, text).show()
            self.reply = 'log'
        elif button and self.entry:
            self.reply = self.value.get()
        else:
            self.reply = button
        self.parent.focus_set()
        self.destroy()

    def show(self):
        self.wm_deiconify()
        self.wait_window()
        return self.reply

class Migrate(Tk):
    def __init__(self):
        super().__init__()
        icon = PhotoImage(file=iconPath)
        self.eval(f"wm iconphoto . {icon}")
        self.title('plasmac2 Migrate')
        text  = _('Migration utility for plasmac2')
        l = Label(self, text=text)
        self.label = Label(self, justify='left')
        self.migrate_text()
        b = Frame(self)
        bm = Button(b, text=_('Migrate'), width=10, command=self.migrate, padx=0)
        bq = Button(b, text=_('Quit'), width=10, command=self.shutdown, padx=0)
        bm.grid(row=0, column=0)
        bq.grid(row=0, column=2)
        l.grid(row=0, column=0, sticky='ew', padx=8, pady=(8,0))
        self.label.grid(row=1, column=0, sticky='ew', padx=8, pady=(8,0))
        b.grid(row=3, column=0, sticky='ew', padx=8, pady=(16,8))
        b.columnconfigure((0,1,2), weight=1)
        self.lcnc = os.path.expanduser('~/linuxcnc')
        if not os.path.isdir(self.lcnc):
            title = _('Directory Error')
            msg1 = _('The directory ~/linuxcnc does not exist')
            msg2 = _('It needs to have been created by LinuxCNC')
            msg3 = _('and have a working QtPlasmaC configuration')
            msg4 = _('to ensure that the structure is correct')
            Dialog(self, title, f"{msg1}.\n{msg2}\n{msg3}\n{msg4}.\n").show()
            raise SystemExit
        if 'root' in self.lcnc:
            title = _('User Error')
            msg = _('plasmac2 setup can not be run as a root user')
            Dialog(self, title, f"{msg}.\n").show()
            raise SystemExit
        self.configDir = os.path.join(self.lcnc, 'configs')
        self.currentDir = os.path.dirname(__file__)
        self.prefs = configparser.ConfigParser()
        self.prefs.optionxform=str
        self.eval(f"tk::PlaceWindow {self} center")
        self.bind('<m>', lambda e:self.migrate())
        self.bind('<q>', lambda e:self.shutdown())

    def migrate(self):
        self.migrate_text()
        ini = filedialog.askopenfilename(
            title='QtPlasmaC INI File',
            initialdir=self.configDir,
            filetypes=(('ini files', '*.ini'),))
        if not ini:
            return
        iniFile = os.path.basename(ini)
        self.oldDir = os.path.dirname(ini)
        path, base = self.oldDir.rsplit('/',1)
        newBase = f"{base}_plasmac2"
        title = _('Directory Name')
        msg1 = _('The new directory name will be')
        msg2 = _('If you change it to the same name as the QtPlasmaC config')
        msg3 = _('then the QtPlasmaC config directory will be renamed to:')
        msg4 = _('Accept it or change it then accept')
        reply = Dialog(self, title, f"{msg1}: {newBase}\n\n{msg2}\n{msg3}\n{base}_qtplasmac\n\n{msg4}\n", buttons=2, entry=newBase).show()
        if reply == base:
            try:
                orgDir = self.oldDir
                self.oldDir = f"{self.oldDir}_qtplasmac"
                os.rename(orgDir, self.oldDir)
            except Exception as e:
                title = 'Directory Error'
                msg1 = _('Error while renaming')
                Dialog(self, title, f"{msg1}: {orgDir}\n\nto: {self.oldDir}\n\n{e}").show()
                return
        elif not reply:
            return
        self.newDir = os.path.join(path, reply)
        self.newIni = os.path.join(self.newDir, iniFile)
        self.logFile = os.path.join(self.newDir, 'migrate.log')
        self.log = []
        if os.path.isfile(self.logFile):
            os.remove(self.logFile)
        if os.path.exists(self.newDir):
            title = _('Directory Exists')
            msg1 = _('already exists')
            msg2 = _('Overwrite')
            reply = Dialog(self, title, f"{self.newDir} {msg1}\n\n{msg2}?\n", buttons=2).show()
            if not reply:
                return
            if os.path.isfile(self.newDir) or os.path.islink(self.newDir):
                os.remove(self.newDir)
            elif os.path.isdir(self.newDir):
                rmtree(self.newDir)
        try:
            ignores = ignore_patterns('qtplasmac','backups','machine_log*.txt', \
                                      '*.qss','*.py','*.bak','qtvcp.prefs','M190', \
                                      'qtplasmac.prefs')
            # copy required files
            p2Path = os.path.join(self.newDir, 'plasmac2')
            copytree(self.oldDir, self.newDir, ignore=ignores)
            # remove any existing plasmac2
            if os.path.islink(p2Path):
                os.remove(p2Path)
            elif os.path.exists(p2Path):
                os.remove(p2Path)
            # create a link to plasmac2
            os.symlink(self.currentDir, p2Path)
            # copy user custom files
            self.copy_custom_files(self.newDir)
            self.do_ini_file()
            self.do_hal_files()
            self.do_prefs_file()
            # all done...
            path = os.path.join(self.newDir, iniFile)
            title = _('Migration Complete')
            msg1 = _('The INI file for the plasmac2 config is')
            msg2 = _('Copy Command will copy the complete command required to run LinuxCNC using this config')
            msg  = f"{msg1}:\n\n{path}\n\n{msg2}.\n"
            clip = False
            btns = 2
            if self.log:
                with open(self.logFile, 'w') as outFile:
                    for line in self.log:
                        outFile.write(line)
                msg1 = '-------------------------------------------------'
                msg2 = _('Some hal files contain references to qtplasmac and they were commented out in the file')
                msg3 = _('There is a log of these files in')
                msg4 = _('in the new config directory')
                msg += f"\n{msg1}\n\n{msg2}.\n\n{msg3} {os.path.basename(self.logFile)} {msg4}.\n"
                btns = 3
            while 1:
                reply = Dialog(self, title, msg, buttons=btns, insType=INSTYPE[0]).show()
                if reply == 1:
                    if clip:
                        text1 = _('Warning')
                        text2 = _('The copied command will be removed from the')
                        text3 = _('system clipboard when this window is closed')
                        self.label['text'] = f"{text1}:\n{text2}\n{text3}.\n"
                    break
                elif reply == 0:
                    clip = True
                    self.clipboard_clear()
                    self.clipboard_append(f"{INSTYPE[1]} {path}")
                    self.update()
        except Exception as e:
            title = _('Migration error')
            line = sys.exc_info()[-1].tb_lineno
            msg1 = _('Migration was unsuccessful')
            msg2 = _('Error in line')
            Dialog(self, title, f"{msg1}.\n\n{msg2}: {line}\n{str(e)}").show()

    def do_ini_file(self):
        with open(self.newIni, 'r') as inFile:
            config = inFile.readlines()
        # [DISPLAY] section
        section = {}
        for lNum in range(config.index('[DISPLAY]\n') + 1, len(config)):
            if config[lNum].startswith('['):
                break
            section[lNum] = config[lNum]
        insert = lNum - 1 if config[lNum - 1] == '\n' else lNum
        done = []
        for lNum in section:
            if section[lNum].startswith('DISPLAY') and 'd' not in done:
                config[lNum] = 'DISPLAY = axis\n'
                done.append('d')
            elif section[lNum].startswith('OPEN_FILE') and 'of' not in done:
                config[lNum] = 'OPEN_FILE = ""\n'
                done.append('of')
            elif section[lNum].startswith('TOOL_EDITOR') and 'te' not in done:
                config[lNum] = 'TOOL_EDITOR = tooledit x y\n'
                done.append('te')
            elif section[lNum].startswith('CYCLE_TIME') and 'ct' not in done:
                config[lNum] = 'CYCLE_TIME = 100\n'
                done.append('ct')
            elif section[lNum].startswith('USER_COMMAND_FILE') and 'uc' not in done:
                config[lNum] = 'USER_COMMAND_FILE = ./plasmac2/plasmac2.py\n'
                done.append('uc')
            elif section[lNum].startswith('POSITION_OFFSET') and 'po' not in done:
                config[lNum] = 'POSITION_OFFSET = RELATIVE\n'
                done.append('po')
            elif section[lNum].startswith('POSITION_FEEDBACK') and 'pf' not in done:
                config[lNum] = 'POSITION_FEEDBACK = ACTUAL\n'
                done.append('pf')
        for option in ['d','of','te','ct','uc','po','pf']:
            if option == 'd' and option not in done:
                config.insert(insert,'DISPLAY = axis\n')
            elif option == 'of' and option not in done:
                config.insert(insert,'OPEN_FILE = ""\n')
            elif option == 'te' and option not in done:
                config.insert(insert,'TOOL_EDITOR = tooledit x y\n')
            elif option == 'ct' and option not in done:
                config.insert(insert,'CYCLE_TIME = 100\n')
            elif option == 'uc' and option not in done:
                config.insert(insert,'USER_COMMAND_FILE = ./plasmac2/plasmac2.py\n')
            elif option == 'po' and option not in done:
                config.insert(insert,'POSITION_OFFSET = RELATIVE\n')
            elif option == 'pf' and option not in done:
                config.insert(insert,'POSITION_FEEDBACK = ACTUAL\n')
        # [RS274NGC] section
        section = {}
        for lNum in range(config.index('[RS274NGC]\n') + 1, len(config)):
            if config[lNum].startswith('['):
                break
            section[lNum] = config[lNum]
        insert = lNum - 1 if config[lNum - 1] == '\n' else lNum
        done = []
        for lNum in section:
            if section[lNum].startswith('USER_M_PATH') and 'u' not in done:
                mPath = config[lNum].split('=')[1].strip()
                config[lNum] = f"USER_M_PATH = ./plasmac2:{mPath}\n"
                done.append('u')
        for option in ['u']:
            if option == 'u' and option not in done:
                config.insert(insert,'USER_M_PATH = ./plasmac2:./\n')
        # [EMC] section - to get machine name
        section = {}
        for lNum in range(config.index('[EMC]\n') + 1, len(config)):
            if config[lNum].startswith('['):
                break
            section[lNum] = config[lNum]
        insert = lNum - 1 if config[lNum - 1] == '\n' else lNum
        done = []
        for lNum in section:
            if section[lNum].startswith('MACHINE'):
                self.machineName = config[lNum].split('=')[1].strip()
        # [FILTER] section
        section = {}
        for lNum in range(config.index('[FILTER]\n') + 1, len(config)):
            if config[lNum].startswith('['):
                break
            section[lNum] = config[lNum]
        insert = lNum - 1 if config[lNum - 1] == '\n' else lNum
        done = []
        for lNum in section:
            if section[lNum].startswith('PROGRAM_EXTENSION') and 'p' not in done:
                config[lNum] = 'PROGRAM_EXTENSION = .ngc,.nc,.tap (filter gcode files)\n'
                done.append('p')
            if section[lNum].startswith('ngc') and 'g' not in done:
                config[lNum] = 'ngc = qtplasmac_gcode\n'
                done.append('g')
            if section[lNum].startswith('nc') and 'c' not in done:
                config[lNum] = 'nc = qtplasmac_gcode\n'
                done.append('c')
            if section[lNum].startswith('tap') and 'a' not in done:
                config[lNum] = 'tap = qtplasmac_gcode\n'
                done.append('a')
        for option in ['a','c','g','p']:
            if option == 'a' and option not in done:
                config.insert(insert,'tap = qtplasmac_gcode\n')
            elif option == 'c' and option not in done:
                config.insert(insert,'nc = qtplasmac_gcode\n')
            elif option == 'g' and option not in done:
                config.insert(insert,'ngc = qtplasmac_gcode\n')
            elif option == 'p' and option not in done:
                config.insert(insert,'PROGRAM_EXTENSION = .ngc,.nc,.tap (filter gcode files)\n')
        # [HAL] section
        section = {}
        self.halList = []
        sim = False
        self.qtplasmacHal = []
        for lNum in range(config.index('[HAL]\n') + 1, len(config)):
            if config[lNum].startswith('['):
                break
            section[lNum] = config[lNum]
        for lNum in section:
            if section[lNum].startswith('HALFILE') or \
               section[lNum].startswith('POSTGUI_HALFILE') or \
               section[lNum].startswith('SHUTDOWN'):
                halFileRaw = config[lNum].split('=')[1].strip()
                if os.path.isfile(os.path.join(HALLIB, halFileRaw)):
                    halFile = os.path.join(HALLIB, halFileRaw)
                else:
                    halFile = os.path.realpath(os.path.join(self.oldDir, halFileRaw))
                    self.halList.append(halFileRaw)
                # if existing config is a sim
                if 'sim_postgui' in halFile:
                    sim = True
                    config[lNum] = ''
                elif 'sim_no_stepgen.tcl' in halFile:
                    config[lNum] = 'HALFILE = plasmac2/lib/sim/sim_no_stepgen.tcl\n'
                    sim = True
                elif 'sim_stepgen.tcl' in halFile:
                    config[lNum] = 'HALFILE  = plasmac2/lib/sim/sim_stepgen.tcl\n'
                    sim = True
                elif 'sim_' in halFile:
                    sim = True
        # add sim panel if required
        if sim:
            app1 = '[APPLICATIONS]'
            app2 = 'DELAY = 2'
            app3 = 'APP = plasmac2/lib/sim/sim_panel.py'
            config.insert(0, f"{app1}\n{app2}\n{app3}\n\n")
        # write the ini file
        with open(self.newIni, 'w') as outFile:
            for line in config:
                outFile.write(line)

    def do_hal_files(self):
        validPins = ['out_0', 'out_1', 'out_2', 'abort','frame-job','pause', \
                     'power','probe','pulse','run','run-pause','touchoff']
        for file in self.halList:
            if not os.path.dirname(file):
                halFile = os.path.join(self.newDir, file)
            edit = False
            logged = False
            with open(halFile, 'r') as inFile:
                config = inFile.readlines()
                for lNum in range(len(config)):
                    if 'qtplasmac.ext_' in config[lNum]:
                        for pin in validPins:
                            if f"qtplasmac.ext_{pin}" in config[lNum]:
                                config[lNum] = config[lNum].replace(f"qtplasmac.ext_{pin}", f"axisui.ext.{pin}")
                                edit = True
                    if 'qtplasmac' in config[lNum] or 'sim-torch' in config[lNum]:
                        config[lNum] = f"# MIGRATION: {config[lNum]}"
                        edit = True
                        if not logged:
                            self.log.append(f"\n{file}\n")
                            logged = True
                        self.log.append(f"{lNum+1} - {config[lNum]}")
            # write the hal fil
            if edit:
                with open(halFile, 'w') as outFile:
                    for line in config:
                        outFile.write(line)

    def do_prefs_file(self):
        prefsFile = os.path.join(self.newDir, f"{self.machineName}.prefs")
        with open(prefsFile, 'r') as inFile:
            config = inFile.readlines()
        # [BUTTONS] section
        edit = False
        section = {}
        for lNum in range(config.index('[BUTTONS]\n') + 1, len(config)):
            if config[lNum].startswith('['):
                break
            section[lNum] = config[lNum]
        for lNum in section:
            if 'Name =' in section[lNum]:
                name, value = config[lNum].split('=')
                value = value.replace('\\',' ').replace('&&', '& ')
                config[lNum] = f"{name}={value}"
                edit = True
            if 'Code =' in section[lNum] and 'offsets-view' in section[lNum]:
                config[lNum-1] = f"{config[lNum-1].split('=')[0]}= \n"
                config[lNum] = f"{config[lNum].split('=')[0]}= \n"
                self.log.append(f"\n{self.machineName}.prefs\n")
                text1 = _('is not available in plasmac2')
                text2 = _('it has been removed from the prefs file')
                self.log.append(f"{lNum} & {lNum+1} - offsets-view {text1}, {text2}.\n")
                edit = True
            elif 'qtplasmac.ext_' in config[lNum]:
                config[lNum] = config[lNum].replace('qtplasmac.ext_','axisui.ext.')
                edit = True
        # write the prefs file
        if edit:
            with open(prefsFile, 'w') as outFile:
                for line in config:
                    outFile.write(line)

    def copy_custom_files(self, dir):
        for f in ['commands', 'hal', 'periodic']:
            src = os.path.join(self.currentDir, 'lib/custom', f"user_{f}.py")
            copy(src, os.path.join(dir, '.'))

    def migrate_text(self):
        text1 = _('Makes a copy of an existing working QtPlasmaC config')
        text2 = _('Then converts that copy to a working plasmac2 config')
        self.label['text'] = f"{text1}.\n\n{text2}.\n"

    def shutdown(self):
        raise SystemExit

if __name__ == "__main__":
    migrate = Migrate()
    migrate.mainloop()
