# -*- coding: utf-8 -*-

#   This file is part of emesene.
#
#    Emesene 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; either version 2 of the License, or
#    (at your option) any later version.
#
#    emesene 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 emesene; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
'''base classes that represent abstract GUI elements to build configuration
dialog'''

class Base(object):
    '''the base class for all the items that can contain a value, it 
    contains a identifier and a value, the callback is called when the
    value changes, the callback must receive 4 arguments:
    * this object
    * the identifier
    * the value
    * a boolean that is true if the configurarion dialog is closed
    (that means that it's the last change on the element, useful if you
    dont want to update things everytime the user changes the content, and
    only when is the last change)'''

    def __init__(self, identifier, callback=None):
        '''class constructor'''
        self.identifier = identifier
        self._value = None
        self.callback = callback

    def _get_value(self):
        '''return value'''
        return self._value

    def _set_value(self, value):
        '''set value'''
        self._value = value
        self.on_value_changed()

    value = property(fget=_get_value, fset=_set_value)

    def on_value_changed(self, is_last_change=False):
        '''call the calback if not none with the arguments especified on the
        class doc, is_last_change will be True if it's the last change on the
        object (when the config dialog is closed'''
        if self.callback:
            self.callback(self, self.identifier, self.value, is_last_change)

    def validate(self, value=None):
        '''since no validations can be done on this fiels, always return
        True'''
        return (True, 'OK', self)

class Validable(Base):
    '''a class that contain methods to validate the content'''

    def __init__(self, identifier, callback=None):
        '''class constructor'''
        Base.__init__(self, identifier, callback)
        self.validators = []

    def add_validator(self, validator, error_message):
        '''add a validator method to the validators that will be called
        on the content when the validate method is called.
         if the validator return False, the error_message will be displayed'''

        self.validators.append((validator, error_message))

    def validate(self, value=None):
        '''validate the value with all the validators or self.value if
        value is None, the first that fails
        will return the error message, if some fails will return 
        (False, error_message, self) if none fails return (True, 'OK', self)'''

        for (validator, error_message) in self.validators:
            if not validator(value or self.value):
                return (False, error_message, self)

        return (True, 'OK', self)

class Text(Validable):
    '''a class that represent an abstract field that contains a label and
    a text field with an optional text content'''

    def __init__(self, identifier, label, text=None, callback=None):
        '''class constructor'''
        Validable.__init__(self, identifier, callback)
        self.label = label
        self.value = text or ''

class Password(Text):
    '''a class that represent an abstract field that contains a label and
    a password field with an optional text content'''

    def __init__(self, identifier, label, text=None, callback=None):
        '''class constructor'''
        Text.__init__(self, identifier, label, text, callback)

class CheckBox(Base):
    '''a class that represent an abstract field that contains a label and
    can be set to checked or not checked'''

    def __init__(self, identifier, label, value=False, callback=None):
        '''class constructor'''
        Base.__init__(self, identifier, callback)
        self.label = label
        self.value = value

class RadioGroup(Base):
    '''a class that represent an abstract group of fields on which only one
    can be selected, every item has a label and the group has a text that
    describe the groups, for example group_label="fruits",
    labels=("apple", "orange", "banana") selected_index=1
    the identifiers are the values that will be returned as the selecetd
    index, for example the label can be A_pple and the identidier apple,
    or the label can be translated, but identifier stay the same'''

    def __init__(self, identifier, labels, identifiers, group_label, 
            selected_index=0, callback=None):
        '''class constructor, labels is a list or tuple of strings and 
        selected_index is the index of the selected index by default, if
        the index is out of range, the first item will be selected.
        identifier is the identifier of the group, identifiers is a list or
        tuple of the identifier value for each label'''
        Base.__init__(self, identifier, callback)
        if len(labels) < 2:
            raise ValueError("labels size < 2")

        if len(labels) != len(identifiers):
            raise ValueError("number of labels and identifiers differ")

        self.labels = labels
        self.group_label = group_label
        self.selected_index = selected_index
        self.identifiers = identifiers
        
        if self.selected_index < 0 or self.selected_index > len(self.labels):
            self.selected_index = 0

class Group(object):
    '''a class that represent a logic group of elements, the way it is 
    represented can be a frame or something like that, it has a optional
    name for the group, if label is none, then no frame or label will be
    displayed on the group'''

    def __init__(self, label=None):
        '''class constructor'''
        self.label = label
        self.items = []

    def add_item(self, item):
        '''add an item to the group, the item can be any element'''
        self.items.append(item)

    def on_last_change(self):
        '''call last change for all the containing items'''
        for item in self.items:
            if issubclass(type(item), Base):
                item.on_value_changed(True)
            elif issubclass(type(item), Group):
                item.on_last_change()

    def validate(self):
        '''calidate all the containing elements and return (True, 'OK')
        if all validated and (False, error_message) of the first validation
        that failed'''

        for item in self.items:
            (validated, message, element) = item.validate()
            if not validated:
                return (validated, message, element)

        return (True, 'OK', self)


class Tab(Group):
    '''a class that represent a containter tab with elements'''

    def __init__(self, label):
        '''class constructor'''
        Group.__init__(self, label)

class TabGroup(Group):
    '''a class that represent a group of tabs'''

    def __init__(self):
        '''class constructor'''
        Group.__init__(self, None)
    
    def add_item(self, item):
        '''add an item to the group, the item can be any element'''
        if type(item) == Tab:
            Group.add_item(self, item)
        else:
            raise ValueError("item is not of type Tab")

def build(element):
    '''this method should be overrided by the implementation module, it should
    return a non modal window with an accept button, that will call to
    a method like this on close:
   
    (validated, message, element) = element.validate()

    # here a dialog should be displayed and the window not closed
    if not validated:
        dialog.error("field '" + element.identifier + \
        "' with value '" + element.value + "' not valid: \n" + message)
    else:
        # all fields are valid
        element.on_last_change()
        dialog_window.close() # or similar
    '''
    raise NotImplementedError
