"""
CEASIOMpy: Conceptual Aircraft Design Software

Developed by CFS ENGINEERING, 1015 Lausanne, Switzerland

Tool to create workflow for CEASIOMpy with or without GUI

Python version: >=3.6

| Author: Aidan jungo
| Creation: 2020-04-21
| Last modifiction: 2020-07-02

TODO:

    * more options for optim ?

"""

# ==============================================================================
#   IMPORTS
# ==============================================================================

import os
import sys
import shutil

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox, filedialog

import ceasiompy.utils.workflowfunctions as wkf
import ceasiompy.utils.ceasiompyfunctions as ceaf
import ceasiompy.utils.cpacsfunctions as cpsf
import ceasiompy.utils.moduleinterfaces as mi

from ceasiompy.Optimisation.optimisation import routine_launcher
from ceasiompy.utils.ceasiomlogger import get_logger
log = get_logger(__file__.split('.')[0])

import ceasiompy.__init__
LIB_DIR = os.path.dirname(ceasiompy.__init__.__file__)

MODULE_DIR = os.path.dirname(os.path.abspath(__file__))
MODULE_NAME = os.path.basename(os.getcwd())


# ==============================================================================
#   IMPORTS
# ==============================================================================

class WorkflowOptions:
    """ Class to pass option of the workflow """

    def __init__(self):

        cpacs_path = mi.get_toolinput_file_path(MODULE_NAME)
        if os.path.isfile(cpacs_path):
            self.cpacs_path = cpacs_path
        else:
             self.cpacs_path = ''

        self.optim_method = 'None' # 'None', 'Optim', 'DoE'
        self.module_pre = []
        self.module_optim = []
        self.module_post = []


class Tab(tk.Frame):
    """ Class to create tab in the WorkflowCreator GUI """

    def __init__(self, master, name, **kwargs):

        tk.Frame.__init__(self, master, **kwargs)

        self.name = name

        # Get list of available modules
        self.modules_list = mi.get_submodule_list()
        self.modules_list.sort()

        self.modules_list.remove('SettingsGUI')
        self.modules_list.insert(0,'SettingsGUI')

        self.modules_list.remove('CPACSUpdater')
        self.modules_list.remove('WorkflowCreator')
        self.modules_list.remove('utils')
        try:
            self.modules_list.remove('WKDIR')
        except:
            log.info('No WKDIR yet.')

        self.selected_list = []

        row_pos = 0

        if name == 'Optim':

            label_optim = tk.Label(self, text='Optimisation method')
            label_optim.grid(column=0, row=0, columnspan=1,pady=10)

            # The Combobox is directly use as the varaible
            optim_choice = ['None', 'DoE', 'Optim']
            self.optim_choice_CB = ttk.Combobox(self, values=optim_choice, width=15)
            self.optim_choice_CB.grid(column=4, row=row_pos)
            row_pos += 1

        # ListBox with all available modules
        tk.Label(self, text='Available modules').grid(column=0, row=row_pos, pady=5)
        self.LB_modules = tk.Listbox(self, selectmode=tk.SINGLE, width=25, height=len(self.modules_list))
        item_count = len(self.modules_list)
        self.LB_modules.grid(column=0, row=row_pos+1, columnspan=3, rowspan=15, padx=10, pady=3)
        for item in self.modules_list:
            self.LB_modules.insert(tk.END, item)

        # Button
        addButton = tk.Button(self, text='   Add >   ', command=self._add)
        addButton.grid(column=4, row=row_pos+1)
        removeButton = tk.Button(self, text='< Remove', command=self._remove)
        removeButton.grid(column=4, row=row_pos+2)
        upButton = tk.Button(self, text='    Up  ^   ', command=self._up)
        upButton.grid(column=4, row=row_pos+3)
        downButton = tk.Button(self, text='  Down v  ', command=self._down)
        downButton.grid(column=4, row=row_pos+4)

        # ListBox with all selected modules
        tk.Label(self, text='Selected modules').grid(column=5, row=row_pos)
        self.LB_selected = tk.Listbox(self, selectmode=tk.SINGLE, width=25, height=len(self.modules_list))
        self.LB_selected.grid(column=5, row=row_pos+1, columnspan=3, rowspan=15, padx=10, pady=3)
        for item in self.selected_list:
            self.LB_selected.insert(tk.END, item)
            row_pos += (item_count + 1)

    def _add(self, event=None):
        """ Function of the button add: to pass a module from Available module
            list to Selected module list"""

        try:
            select_item = [self.LB_modules.get(i) for i in self.LB_modules.curselection()]
            self.selected_list.append(select_item[0])
            self.LB_selected.insert(tk.END, select_item)
        except IndexError:
            self.selected_item = None

    def _remove(self, event=None):
        """ Function of the button remove: to remove a module from the Selected
            module list"""

        sel = self.LB_selected.curselection()
        for index in sel[::-1]:
            self.LB_selected.delete(index)

    def _up(self, event=None):
        """ Function of the button up: to move upward a module in the Selected
            module list"""

        pos_list = self.LB_selected.curselection()

        if not pos_list:
            return

        for pos in pos_list:
            if pos == 0:
                continue
            item = self.LB_selected.get(pos)
            self.LB_selected.delete(pos)
            self.LB_selected.insert(pos - 1, item)

    def _down(self, event=None):
        """ Function of the button down: to move downward a module in the
            Selected module list."""

        pos_list = self.LB_selected.curselection()

        if not pos_list:
            return

        for pos in pos_list:
            if pos == self.LB_selected.size():
                continue
            item = self.LB_selected.get(pos)
            self.LB_selected.delete(pos)
            self.LB_selected.insert(pos + 1, item)


class WorkFlowGUI(tk.Frame):
    def __init__(self, master=None, **kwargs):

        tk.Frame.__init__(self, master, **kwargs)
        self.pack(fill=tk.BOTH)

        self.Options = WorkflowOptions()

        space_label = tk.Label(self, text=' ')
        space_label.grid(column=0, row=0)

        # Input CPACS file
        self.label = tk.Label(self, text='  Input CPACS file')
        self.label.grid(column=0, row=1)

        self.path_var = tk.StringVar()
        self.path_var.set(self.Options.cpacs_path)
        value_entry = tk.Entry(self, textvariable=self.path_var, width= 45)
        value_entry.grid(column=1, row=1)

        self.browse_button = tk.Button(self, text="Browse", command=self._browse_file)
        self.browse_button.grid(column=2, row=1, pady=5)

        # Notebook for tabs
        self.tabs = ttk.Notebook(self)
        self.tabs.grid(column=0, row=2, columnspan=3,padx=10,pady=10)

        self.TabPre = Tab(self, 'Pre')
        self.TabOptim = Tab(self, 'Optim')
        self.TabPost = Tab(self, 'Post')

        self.tabs.add(self.TabPre, text=self.TabPre.name)
        self.tabs.add(self.TabOptim, text=self.TabOptim.name)
        self.tabs.add(self.TabPost, text=self.TabPost.name)

        # General buttons
        self.close_button = tk.Button(self, text='Save & Quit', command=self._save_quit)
        self.close_button.grid(column=2, row=3)


    def _browse_file(self):

        cpacs_template_dir = os.path.join(MODULE_DIR,'..','..','test','CPACSfiles')
        self.filename = filedialog.askopenfilename(initialdir = cpacs_template_dir, title = "Select a CPACS file" )
        self.path_var.set(self.filename)

    def _save_quit(self):

        self.Options.optim_method = self.TabOptim.optim_choice_CB.get()

        self.Options.module_pre = [item[0] for item in self.TabPre.LB_selected.get(0, tk.END)]
        self.Options.module_optim = [item[0] for item in self.TabOptim.LB_selected.get(0, tk.END)]
        self.Options.module_post = [item[0] for item in self.TabPost.LB_selected.get(0, tk.END)]

        self.Options.cpacs_path = self.path_var.get()
        if self.path_var.get() == '':
            messagebox.showerror('ValueError', 'Yon must select an input CPACS file!')
            raise TypeError('No CPACS file has been define !')

        self.quit()


# ==============================================================================
#    MAIN
# ==============================================================================

def create_wf_gui():
    """ Create a GUI with Tkinter to fill the workflow to run

    Args:
        cpacs_path (str): Path to the CPACS file
        cpacs_out_path (str): Path to the output CPACS file
        module_list (list): List of module to inclue in the GUI

    """

    root = tk.Tk()
    root.title('Workflow Creator')
    root.geometry('475x490+400+100')
    my_gui = WorkFlowGUI()
    my_gui.mainloop()
    disg = my_gui.Options

    root.iconify() # Not super solution but only way to make it close on Mac
    root.destroy()

    return disg


if __name__ == '__main__':

    log.info('----- Start of ' + os.path.basename(__file__) + ' -----')

    cpacs_path_out = mi.get_tooloutput_file_path(MODULE_NAME)

    gui = False

    if len(sys.argv) > 1:
        if sys.argv[1] == '-gui':
            gui = True
        else:
            print(' ')
            print('Not valid argument!')
            print('You can use the option -gui to run this module with a user interface.')
            print(' ')
            sys.exit()

    if gui:
        Opt = create_wf_gui()
    else:
        ####### USER INPUT ########
        ### Available Module:
        # Settings: 'SettingsGUI'
        # Geometry and mesh: 'CPACSCreator','CPACS2SUMO','SUMOAutoMesh'
        # Weight and balance: 'WeightConventional','WeightUnconventional','BalanceConventional','BalanceUnconventional'
        # Aerodynamics: 'CLCalculator','PyTornado','SkinFriction','PlotAeroCoefficients','SU2MeshDef','SU2Run'
        # Mission analysis: 'Range','StabilityStatic'

        Opt = WorkflowOptions()

        # These options can be modified here if WorkflowCreator is used without GUI
        Opt.cpacs_path = '../../test/CPACSfiles/simpletest_cpacs.xml'
        Opt.module_pre = ['SettingsGUI', 'PyTornado']
        Opt.module_optim = []

        Opt.optim_method = 'Optim' # DoE, Optim, None
        Opt.module_post = []


    # Copy ToolInput.xml in ToolInput dir if not already there
    cpacs_path = mi.get_toolinput_file_path(MODULE_NAME)
    if not Opt.cpacs_path == cpacs_path:
        shutil.copy(Opt.cpacs_path, cpacs_path)
        Opt.cpacs_path = cpacs_path

    # Create a new wkdir
    tixi = cpsf.open_tixi(Opt.cpacs_path)
    wkdir = ceaf.get_wkdir_or_create_new(tixi)
    cpsf.close_tixi(tixi, Opt.cpacs_path)

    # Run Pre-otimisation workflow
    if Opt.module_pre:
        wkf.run_subworkflow(Opt.module_pre, Opt.cpacs_path)

        if not Opt.module_optim and not Opt.module_post:
            shutil.copy(mi.get_tooloutput_file_path(Opt.module_pre[-1]), cpacs_path_out)

    # Run Optimisation workflow
    if Opt.module_optim:
        if Opt.module_pre:
            wkf.copy_module_to_module(Opt.module_pre[-1], 'out', 'Optimisation', 'in')
        else:
            wkf.copy_module_to_module('WorkflowCreator', 'in', 'Optimisation', 'in')

        if Opt.optim_method != 'None':
            routine_launcher(Opt)
        else:
            log.warning('No optimization method has been selected!')
            log.warning('The modules will be run as a simple workflow')
            wkf.run_subworkflow(Opt.module_optim)

        if not Opt.module_post:
            shutil.copy(mi.get_tooloutput_file_path(Opt.module_optim[-1]), cpacs_path_out)

    # Run Post-optimisation workflow
    if Opt.module_post:

        if Opt.module_optim:
            wkf.copy_module_to_module(Opt.module_optim[-1], 'out', Opt.module_post[0], 'in')
        elif Opt.module_pre:
            wkf.copy_module_to_module(Opt.module_pre[-1], 'out', Opt.module_post[0], 'in')
        else:
            wkf.copy_module_to_module('WorkflowCreator', 'in', Opt.module_post[0], 'in')

        # wkf.copy_module_to_module('CPACSUpdater','out',Opt.module_post[0],'in')  usefuel?
        wkf.run_subworkflow(Opt.module_post)
        shutil.copy(mi.get_tooloutput_file_path(Opt.module_post[-1]), cpacs_path_out)

    log.info('----- End of ' + os.path.basename(__file__) + ' -----')