# Copyright (c) 2012, 2020, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0,
# as published by the Free Software Foundation.
#
# This program is designed to work with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms, as
# designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an additional
# permission to link the program and your derivative works with the
# separately licensed software that they have either included with
# the program or referenced in the documentation.
# This program is distributed in the hope that it will be useful,  but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
# the GNU General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

import mforms
import grt
import os

from workbench.ui import WizardPage
from workbench.ui import WizardProgressPage


class MainView(WizardPage):
    def __init__(self, main):
        WizardPage.__init__(self, main, "Target Creation Options")

        self.main.add_wizard_page(self, "ObjectMigration", "Target Creation Options")

        label = mforms.newLabel("Select options for the creation of the migrated schema in the target\nMySQL server and click [Next >] to execute.")
        label.set_name("Page Description")
        self.content.add(label, False, True)

        panel = mforms.newPanel(mforms.TitledBoxPanel)
        panel.set_title("Schema Creation")
        panel.set_name("Schema Creation")
        self.content.add(panel, False, True)

        box = mforms.newBox(False)
        panel.add(box)
        box.set_padding(12)

        self._create_db = mforms.newCheckBox()
        self._create_db.set_text("Create schema in target RDBMS")
        self._create_db.set_name("Create Schema")
        box.add(self._create_db, False, True)

        # spacer
        box.add(mforms.newLabel(""), False, True)

        self._create_script = mforms.newCheckBox()
        self._create_script.set_text("Create a SQL script file")
        self._create_script.set_name("Create a Script File")
        self._create_script.add_clicked_callback(self._toggle_sql_script)
        box.add(self._create_script, False, True)
        self._file_hbox = mforms.newBox(True)
        self._file_hbox.set_spacing(4)
        label = mforms.newLabel("Script File:")
        label.set_name("Script File")
        self._file_hbox.add(label, False, True)
        self._file_hbox.set_name("Script File")
        self._create_script_file = mforms.newTextEntry()
        self._create_script_file.set_name("Script File Value")
        self._create_script_file.set_value(os.path.join(os.path.expanduser('~'), 'migration_script.sql'))
        self._file_hbox.add(self._create_script_file, True, True)
        button = mforms.newButton()
        button.set_text("Browse...")
        button.set_name("Browse")
        button.add_clicked_callback(self._browse_files)
        self._file_hbox.add(button, False, True)
        box.add(self._file_hbox, False, True)

        panel = mforms.newPanel(mforms.TitledBoxPanel)
        panel.set_title("Options")
        self.content.add(panel, False, True)

        box = mforms.newBox(False)
        panel.add(box)
        box.set_padding(12)
        box.set_spacing(8)

        self._keep_schema = mforms.newCheckBox()
        self._keep_schema.set_text("Keep schemas if they already exist. Objects that already exist will not be recreated or updated.")
        self._keep_schema.set_name("Keep Schema")
        box.add(self._keep_schema, False, True)

        self._create_db.set_active(True)
        self._toggle_sql_script()
        self._check_file_duplicate = True


    def _toggle_sql_script(self):
        self._file_hbox.set_enabled(self._create_script.get_active())

    def _filename_changed(self):
        self._check_file_duplicate = True

    def _browse_files(self):
        form = mforms.newFileChooser(mforms.Form.main_form(), mforms.SaveFile)
        form.set_title("Save SQL Script As")
        default_path = os.path.abspath(self._create_script_file.get_string_value())
        # Use as starting directory the directory corresponding to the path stored in the text box if it exists
        # or use the home directory if it doesn't
        form.set_path(default_path if os.path.exists(default_path if os.path.isdir(default_path)
                                                                  else os.path.dirname(default_path))
                                   else os.path.expanduser('~')
                     )
        if form.run_modal():
            self._create_script_file.set_value(form.get_path())
            self._check_file_duplicate = False

    def go_next(self):
        self.main.plan.state.objectCreationParams["KeepSchemata"] = self._keep_schema.get_active()

        self.main.plan.state.objectCreationParams["CreateInDB"] = self._create_db.get_active()    

        if self._create_script.get_active():
            path = self._create_script_file.get_string_value()
            if not path or not os.path.isdir(os.path.dirname(path)):
                mforms.Utilities.show_error("Create Script File", "Create Script File option was enabled, but the provided path is invalid.\nPlease correct and retry.",
                                            "OK", "", "")
                return

            if os.path.isdir(path):
                mforms.Utilities.show_error("Create Script File", "'%s' is a directory name. Please provide a file name for saving the script as and retry." % path,
                                            "OK", "", "")
                return

            if os.path.exists(path) and self._check_file_duplicate:
                if mforms.Utilities.show_error("Create Script File", "The file '%s' provided for the SQL script already exists. Do you want to replace it?" % path,
                                            "Replace", "Cancel", "") == mforms.ResultCancel:
                    return
            self.main.plan.state.objectCreationParams["CreateSQLFile"] = path
        elif "CreateSQLFile" in self.main.plan.state.objectCreationParams:
            del self.main.plan.state.objectCreationParams["CreateSQLFile"]
        
        WizardPage.go_next(self)



class CreationProgressView(WizardProgressPage):
    def __init__(self, main):
        WizardProgressPage.__init__(self, main, "Create Schemas", description="""The SQL scripts generated for the migrated schema objects will now be executed
in the target database. You can monitor execution in the logs. If there are errors 
you may correct them in the next step. Table data will be migrated at a later step.""")

        self._autostart = True
        main.add_wizard_page(self, "ObjectMigration", "Create Schemas")


    def create_ui(self):
        self.clear_tasks()
        self._script_task = self.add_task(self._create_script_task, "Create Script File", "Create Script File")
        self._db_task1 = self.add_task(self._connect_task, "Connect to Target Database", "Connect to Target Database")
        self._db_task2 = self.add_task(self._validate_existing_schemata, "Perform Checks in Target", "Perform Checks in Target")
        self._db_task3 = self.add_threaded_task(self._create_task, "Create Schemas and Objects", "Create Schemas and Objects")

    def page_activated(self, advancing):
        WizardProgressPage.page_activated(self, advancing)
        if advancing:
            if self.main.plan.state.objectCreationParams.get("CreateSQLFile", ""):
                self._script_task.set_enabled(True)
            else:
                self._script_task.set_enabled(False)

            if self.main.plan.state.objectCreationParams.get("CreateInDB", True):
                self._db_task1.set_enabled(True)
                self._db_task2.set_enabled(True)
                self._db_task3.set_enabled(True)
            else:
                self._db_task1.set_enabled(False)
                self._db_task2.set_enabled(False)
                self._db_task3.set_enabled(False)
    
    
    def go_back(self):
        self.reset()
        WizardProgressPage.go_back(self)
        

    def _create_script_task(self):
        self.main.plan.createTargetScript(self.main.plan.state.objectCreationParams["CreateSQLFile"])

        
    def _connect_task(self):
        self.main.plan.migrationTarget.connect()

    def _validate_existing_schemata(self):
        
        grt.send_progress(0.0, "Validating for existing schemas on target MySQL Server...")
        schema_set = set(schema.name.upper() for schema in self.main.plan.migrationTarget.catalog.schemata)
        target_schema_set = set(schema.upper() for schema in grt.modules.DbMySQLFE.getSchemaNames(self.main.plan.migrationTarget.connection))

        existing_schemas = list(schema_set.intersection(target_schema_set))

        continue_migration = True
        if len(existing_schemas) > 0:
            if self.main.plan.state.objectCreationParams.get("KeepSchemata", False):
                message = ''
                for schema in self.main.plan.migrationTarget.catalog.schemata:
                    if not schema.name.upper() in existing_schemas:
                        continue
                    target_schema_tables = set(table_name.upper() for table_name in
                                               grt.modules.DbMySQLFE.getTableNames(self.main.plan.migrationTarget.connection, schema.name) )
                    existing_tables = [table.name for table in schema.tables if table.name.upper() in target_schema_tables]
                    if existing_tables:
                        message += 'In schema ' + schema.name + ':\n    '
                        message += ', '.join(existing_tables) + '\n'

                if message:
                    if mforms.Utilities.show_message("Existing Tables", "The following tables already exist in their "
                                                     "target schemas:\n%sThey won't be recreated. Delete those "
                                                     "tables before continuing if you want them to be recreated. "
                                                     "Do you want to continue?" % message , "Yes", "No", "") == mforms.ResultCancel:
                        continue_migration = False

                    
            elif mforms.Utilities.show_message("Existing Schemas", "The %s %s " % ( 'schema' if len(existing_schemas) == 1 else 'schemas', ", ".join(existing_schemas)) +
                    "will be dropped in the target MySQL Server and all the existing data will be" +
                    " lost. Do you want to continue?" , "Yes", "No", "") == mforms.ResultCancel:
                continue_migration = False

        self._db_task3.set_enabled(continue_migration)
        

    def _create_task(self):
        if self.main.plan.state.objectCreationParams.get("KeepSchemata", False):
            # Regenerate the creation code to take into account this option
            self.main.plan.generateSQL()
        self.main.plan.createTarget()
        self.main.plan.migrationTarget.disconnect()



class CreationReportView(WizardPage):
    def __init__(self, main):
        WizardPage.__init__(self, main, "Create Target Results", wide=True)

        self.main.add_wizard_page(self, "ObjectMigration", "Create Target Results")

        text = """Scripts to create the target schema were executed. No data has been migrated yet. Review the creation report below 
for errors or warnings. If there are any errors, you can manually fix the scripts and click [Recreate Objects] to retry 
the schema creation or return to the Manual Editing page to correct them there and retry the target creation."""
        description = mforms.newLabel(text)
        description.set_name("Page Description")
        self.content.add(description, False, True)

        hbox = mforms.newBox(True)
        hbox.set_spacing(12)
        hbox.set_homogeneous(True)
        self._tree = mforms.newTreeView(mforms.TreeFlatList)
        self._tree.set_name("Script Results")
        self._tree.add_column(mforms.IconStringColumnType, "Object", 200, False)
        self._tree.add_column(mforms.IconStringColumnType, "Result", 600, False)
        self._tree.end_columns()
        self._tree.add_changed_callback(self._selection_changed)
        hbox.add(self._tree, True, True)
        #self.content.add(self._tree, True, True)

        self._advbox = mforms.newPanel(mforms.TitledBoxPanel)
        self._advbox.set_title("SQL CREATE Script for Selected Object")
        self._advbox.set_name("SQL IDE")
        box = mforms.newBox(False)
        self._code = mforms.newCodeEditor()
        self._code.set_language(mforms.LanguageMySQL)
        self._code.add_changed_callback(self._code_changed)
        box.add(self._code, True, True)
        vbox = mforms.newBox(True)
        vbox.set_padding(12)
        vbox.set_spacing(8)
        self._comment_check = mforms.newCheckBox()
        self._comment_check.set_text("Comment out")
        self._comment_check.set_name("Comment Out")
        self._comment_check.add_clicked_callback(self._comment_clicked)
        vbox.add(self._comment_check, False, True)

        self._revert_btn = mforms.newButton()
        self._revert_btn.set_text("Discard")
        vbox.add_end(self._revert_btn, False, True)
        self._revert_btn.add_clicked_callback(self._discard_clicked)

        self._apply_btn = mforms.newButton()
        self._apply_btn.set_text("Apply")
        vbox.add_end(self._apply_btn, False, True)
        self._apply_btn.add_clicked_callback(self._apply_clicked)
        box.add(vbox, False, True)

        self._advbox.add(box)
        #self._advbox.set_size(-1, 200)
        #self._advbox_shown = True
        #self.go_advanced() # toggle to hide
        
        self.advanced_button.set_text("Recreate Objects")
        
        #self.content.add(self._advbox, False, True)
        hbox.add(self._advbox, True, True)
        self.content.add(hbox, True, True)

        self._msgbox = mforms.newPanel(mforms.TitledBoxPanel)
        self._msgbox.set_title("Output Messages")
        self._msgbox.set_name("Output Messages")
        box = mforms.newBox(False)
        box.set_padding(8)
        self._msgs = mforms.newTextBox(mforms.VerticalScrollBar)
        box.add(self._msgs, True, True)
        self._msgbox.add(box)
        self.content.add(self._msgbox, False, True)
        self._msgbox.set_size(-1, 200)

        self._error_tables = []


    def go_next(self):
        if self._error_tables:
            r = mforms.Utilities.show_warning("Table Creation Errors",
                    "Some tables could not be created in the target database.\nWould you like to flag them to be skipped and copy the data for the remaining tables only?",
                    "Skip Failed Tables", "Cancel", "")
            if r == mforms.ResultOk:
                for table in self._error_tables:
                    table.commentedOut = 1
            else:
                return
        
        WizardPage.go_next(self)

    
    def _selected_log_object(self):
        selected = self._tree.get_selected_node()
        if selected and selected.get_tag():
            oid = selected.get_tag()
            object = self._object_dict.get(oid, None)
            # if a column is selected, show the table SQL
            if isinstance(object, grt.classes.db_Column):
                object = object.owner
            return object
        return None
    
    def _selected_object(self):
        log = self._selected_log_object()
        if log:
            return log.logObject
        return None

    def _comment_clicked(self):
        object = self._selected_object()
        if object:
            text = self._code.get_text(False)
            
            if self._comment_check.get_active():
                self._code.set_text("-- "+text.replace("\n", "\n-- "))
            else:
                if text.startswith("-- "):
                    self._code.set_text(text.replace("\n-- ", "\n")[3:])

            self._code.set_enabled(not object.commentedOut)
            self._apply_btn.set_enabled(True)
            self._revert_btn.set_enabled(True)


    def _selection_changed(self):
        log = self._selected_log_object()
        if log:
            text = []
            for entry in log.entries:
                if entry.entryType == 0:
                    text.append("\n    ".join(entry.name.split("\n")))
                elif entry.entryType == 1:
                    text.append("WARNING: %s" % "\n    ".join(entry.name.split("\n")))
                elif entry.entryType == 2:
                    text.append("ERROR: %s" % "\n    ".join(entry.name.split("\n")))
                else:
                    text.append("\n    ".join(entry.name.split("\n")))
            self._msgs.set_value("\n\n".join(text))

        object = log.logObject if log else None
        if object and hasattr(object, "temp_sql"):
            self._code.set_text(object.temp_sql)
            self._code.set_enabled(not object.commentedOut)
            self._comment_check.set_active(object.commentedOut != 0)
            self._advbox.set_enabled(True)
            self._apply_btn.set_enabled(False)
            self._revert_btn.set_enabled(False)
            return
            
        self._code.set_text("")
        self._code.set_enabled(False)
        self._comment_check.set_active(False)
        self._advbox.set_enabled(False)
        
        
    def _code_changed(self, x, y, z):
        self._apply_btn.set_enabled(True)
        self._revert_btn.set_enabled(True)
    
    
    def _apply_clicked(self):
        text = self._code.get_text(False)
        object = self._selected_object()
        if object:
            object.temp_sql = text
            object.commentedOut = self._comment_check.get_active()
        self._apply_btn.set_enabled(False)
        self._revert_btn.set_enabled(False)


    def _discard_clicked(self):
        object = self._selected_object()
        if object:
            self._code.set_text(object.temp_sql)
            self._comment_check.set_active(object.commentedOut != 0)
        self._apply_btn.set_enabled(False)
        self._revert_btn.set_enabled(False)
  
    
    def refresh(self):
        self._object_dict = {}
        self._error_tables = []
        self._tree.clear()
        for log in self.main.plan.state.creationLog:
            node = self._tree.add_node()
            obj = log.logObject
            icon = "GrtObject.16x16.png"
            for c in [grt.classes.db_Schema, grt.classes.db_Table, grt.classes.db_View, grt.classes.db_Routine, grt.classes.db_Trigger]:
                if isinstance(obj, c):
                    icon = c.__grtclassname__+".16x16.png"
                    break
            if not obj:
                grt.log_warning("Migration", "Object creation log '%s' referenced no object" % log.name)
                continue
            full_name = obj.name
            o = obj.owner
            while o:
                full_name = o.name + "." + full_name
                if isinstance(o, grt.classes.db_Schema):
                    break
                o = o.owner
            node.set_string(0, full_name)
            node.set_icon_path(0, icon)
            node.set_tag(log.__id__)
            self._object_dict[log.__id__] = log
            text = []
            worst = None
            for entry in log.entries:
                if worst is None:
                    worst = entry.entryType
                else:
                    worst = max(worst, entry.entryType)
                if entry.entryType == 1:
                    text.append("WARNING: %s" % entry.name)
                elif entry.entryType == 2:
                    text.insert(0, "ERROR: %s" % entry.name)
                    if isinstance(obj, grt.classes.db_Table):
                        self._error_tables.append(obj)
                else:
                    text.append("Script executed successfully")

            if worst == 0:
                node.set_icon_path(1, "task_checked.png")
            elif worst == 1:
                node.set_icon_path(1, "task_warning.png")
            elif worst == 2:
                node.set_icon_path(1, "task_error.png")
            node.set_string(1, ",\n".join(text))


    def page_activated(self, advancing):
        if advancing:
            self.refresh()
        WizardPage.page_activated(self, advancing)


    def go_advanced(self):
        page = self.main.go_previous_page()
        page.reset()
        
  
    #def go_advanced(self):
    #    self._advbox_shown = not self._advbox_shown
    #    if self._advbox_shown:
    #        self.advanced_button.set_text("Hide Code")
    #    else:
    #        self.advanced_button.set_text("Show Code")
    #    self._advbox.show(self._advbox_shown)


