# Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 of the
# License.
# 
# 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 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 functools

import mforms

class DatabaseSchemaSelector(mforms.Box):
    '''Widget to select database schemata within a collection of catalogs (optional) and schemata

    This wigdet will help you to display a set of schemata in a nice tree structure, grouping them
    by catalogs (if the catalog information is supported/available). Next to each listed schema is
    a checkbox for the selection/unselection of the schema. The user can quickly select all of the
    schemata for a given catalog by clicking on the checkbox next to the catalog name.

    .. image:: ../../../library/python/workbench/doc/images/schema_selection_widget.png


    :param database_objects:      Pass a list/tuple of schema names if you don't use catalogs.
                                  If you do use catalogs then pass a list of tuples of the form
                                  ``(catalog_name, schema_list)`` where :attr:`schema_list` is a
                                  list/tuple with the schemata that belongs to the given :attr:`catalog_name`.
    :type database_objects:       list/tuple

    :param tree_checked_callback: A function object that will be called by this widget whenever
                                  a schema is checked/unchecked. It should accept an integer
                                  argument with the number of selected schemata.
    :type tree_checked_callback:  callable

    :param ui_settings:           A dictionary to customize the appearance of the widget. In
                                  case you need to customize something, just pass the relevant
                                  modifications in this dict. The other (default) settings are
                                  kept. Check the definition of :attr:`self.ui_settings` in the
                                  class implementation to see the configurable settings. 
    :type ui_settings:            dict

    .. rubric:: Sample usage:
    ::

        db_objects = [ ('catalog1', ('schema1', 'schema2')),
                       ('catalog2', ('schema3', 'schema4'))
                     ] 

        content = mforms.newBox()
        object_selector = DatabaseObjectSelector(db_objects)
        content.add(object_selector, True)
    '''

    def __init__(self, database_objects, tree_checked_callback=lambda n:None, ui_settings={}):
        '''Constructor.
        '''
        super(DatabaseSchemaSelector, self).__init__(False)
        self.database_objects = database_objects
        self.tree_checked_callback = tree_checked_callback
        
        self.ui_settings = {
            'catalogs' : { 'icon': 'workbench.physical.Layer.16x16.png',
                           'should_expand': True,
                         },
            'schemas'  : { 'icon': 'db.Schema.16x16.png',
                         },
            'general'  : { 'summary_text': '%(nSchemata)d schemata selected',
                           'show_select_all_button': False,
                           'show_unselect_all_button': True,
                         },
        }
        
        # Update the ui settings dict with the custom settings supplied by the user (if any):
        if isinstance(ui_settings, dict):
            for key, value in ui_settings.iteritems():
                if key not in self.ui_settings or not isinstance(value, dict):
                    continue
                self.ui_settings[key].update(value)
        
        # Check if we should use catalogs:
        self.use_catalogs = ( isinstance(database_objects, (list, tuple)) and 
                              len(database_objects) > 0 and
                              all( isinstance(item, (list, tuple)) and 
                                   len(item) == 2 and
                                   isinstance(item[0], (str, unicode)) and 
                                   isinstance(item[1], (list, tuple)) 
                                   for item in database_objects
                                 )
                            )
        
        if self.use_catalogs:
            self._catalogs = tuple(item[0] for item in database_objects)
            self._schemata = dict(database_objects)
        else:
            self._catalogs = None
            self._schemata = database_objects

        self._schema_nodes = []
        self._catalog_nodes = []

        # Create UI:
        self.set_spacing(8)
        
        self.schema_list_tree = mforms.newTreeNodeView(mforms.TreeDefault)
        self.schema_list_tree.add_column(mforms.CheckColumnType, 'Include', 60, True)
        self.schema_list_tree.add_column(mforms.IconColumnType, 'Catalog/Schema' if self.use_catalogs else 'Schema', 300, False)
        self.schema_list_tree.end_columns()
        self.schema_list_tree.set_cell_edited_callback(self._schema_tree_checked)
        self.schema_list_tree.set_allow_sorting(False)
        self.add(self.schema_list_tree, True, True)

        helper_buttons_box = mforms.newBox(True)
        helper_buttons_box.set_spacing(12)

        self.select_summary_label = mforms.newLabel('')
        helper_buttons_box.add(self.select_summary_label, True, True)

        if self.ui_settings['general']['show_unselect_all_button']:
            self.unselect_all_btn = mforms.newButton()
            self.unselect_all_btn.set_text('Unselect All')
            self.unselect_all_btn.add_clicked_callback(functools.partial(self._mark_all_schemata, checked=False))
            helper_buttons_box.add_end(self.unselect_all_btn, False)

        if self.ui_settings['general']['show_select_all_button']:
            self.select_all_btn = mforms.newButton()
            self.select_all_btn.set_text('Select All')
            self.select_all_btn.add_clicked_callback(functools.partial(self._mark_all_schemata, checked=True))
            helper_buttons_box.add_end(self.select_all_btn, False)


        self.add(helper_buttons_box, False, True)
        
        self._fill_schema_tree()
        
    def _fill_schema_tree(self):
        self._schema_nodes = []
        self._catalog_nodes = []
        if self.use_catalogs:
            for catalog_name in self._catalogs:
                catalog_node = self.schema_list_tree.add_node()
                catalog_node.set_bool(0, False)
                catalog_node.set_icon_path(1, self.ui_settings['catalogs']['icon'])
                catalog_node.set_string(1, catalog_name)
                catalog_node.set_tag(catalog_name)
                self._catalog_nodes.append(catalog_node)
                
                for schema_name in self._schemata[catalog_name]:
                    schema_node = catalog_node.add_child()
                    schema_node.set_bool(0, False)
                    schema_node.set_icon_path(1, self.ui_settings['schemas']['icon'])
                    schema_node.set_string(1, schema_name)
                    schema_node.set_tag(catalog_name + '.' + schema_name)
                    self._schema_nodes.append(schema_node)
                
                if self.ui_settings['catalogs']['should_expand']:
                    catalog_node.expand()
        else:  # Not using catalogs
            for schema_name in self._schemata:
                schema_node = self.schema_list_tree.add_node()
                schema_node.set_bool(0, False)
                schema_node.set_icon_path(1, self.ui_settings['schemas']['icon'])
                schema_node.set_string(1, schema_name)
                schema_node.set_tag(schema_name)
                self._schema_nodes.append(schema_node)
            
        self.select_summary_label.set_text(self.ui_settings['general']['summary_text'] % {'nSchemata':0})
        
    def _schema_tree_checked(self, node_list, col, data):
        # This allows to change the status of several nodes at once too:
        if not isinstance(node_list, list):
            node_list = [node_list]
        for node in node_list:
            if col == 0:
                checked = data == '1'
                node.set_bool(0, checked)
                if self.use_catalogs:
                    parent_node = node.get_parent()
                    is_catalog = parent_node == self.schema_list_tree.root_node()
                    catalog_name = node.get_string(1) if is_catalog else parent_node.get_string(1)
                    schema_count = len(self._schemata[catalog_name])
                    if is_catalog:
                        for idx in range(schema_count):
                            node.get_child(idx).set_bool(0, checked)
                    else:
                        # Check/uncheck parent catalog if all of its schemata are checked/unchecked:
                        parent_node.set_bool(0, checked and all( schema_node.get_bool(0) for schema_node in ( parent_node.get_child(idx) for idx in range(schema_count) ) ) )

        selected_schema_count = len(tuple(schema_node for schema_node in self._schema_nodes if schema_node.get_bool(0)))
        self.select_summary_label.set_text(self.ui_settings['general']['summary_text'] % {'nSchemata':selected_schema_count})
        self.tree_checked_callback(selected_schema_count)
        
    def _select_objects(self, checked):
        if self.use_catalogs:
            catalog_schemata = {}
            for schema_node in self._schema_nodes:
                if schema_node.get_bool(0) != checked:
                    continue
                catalog_name, dot, schema_name = schema_node.get_tag().partition('.')
                catalog_schemata.setdefault(catalog_name, []).append(schema_name)
            return catalog_schemata
        else:
            return [ schema_node.get_string(1) for schema_node in self._schema_nodes if schema_node.get_bool(0) == checked ]
    
    def get_selected(self):
        return self._select_objects(True)
    
    def get_ignored(self):
        return self._select_objects(False)
    
    def _mark_all_schemata(self, checked):
        nodes = self._catalog_nodes if self.use_catalogs else self._schema_nodes
        self._schema_tree_checked(nodes, 0, '1' if checked else '0')
