# This file contains the base classes for Field

from types import DictionaryType, ListType
import string

try:
    from WebUtils.Funcs import htmlEncode
except ImportError:
    from Webware.WebUtils.Funcs import htmlEncode
    
from BaseValidatorClasses import InvalidField
from BaseTagClasses import HTMLElement
from BaseChoiceClasses import ChoiceList

class MethodUndefined(Exception):
    # simple error for methods that are supposed to be abstract only
    pass



# field is an abstract class. You will need to subclass it to make anything useful
class Field(HTMLElement):
    def __init__(self, name, validators=[], label='', attributes={}, languageStandard='XHTML'):
        '''The bulk of Field's functionality derives from the code in HTMLElement. In addition,
        the Field constructor sets up a bunch of additional containers; validators is a list of
        validator/converter instances; the label is a string that is used by some field classes, but not
        others, mainly for things like button labels; attributes is a dictionary of parameters that will
        be added to the field's HTML tag; languageStandard is whether you want HTML or XHTML tags returned'''

        # first, run the super's constructor
        HTMLElement.__init__(self,name,label,attributes,languageStandard)

        # storage
        self._validators = validators
        self._default = None                # this is the default value of the field (set by application)
        self._rawValue = None               # this is the raw input (pre-validation) for the field
        self._error = None                  # this holds a validation error message, if there is one


    ## processing, and other action ##

    def setDefault(self, value):
        '''Public method to directly set the default value of the field'''
        # note, this isn't a security or validity risk, since all defaults
        #   have to go through a post and therefore a ._submit() and validate
        self._default = value

    def _processDefaults(self, dict):
        '''Any value in dict whose key matches field name will be chosen as the new default value'''
        default = dict.get( self.name(), None)
        if default is not None:
            self._default = default

    def _processRawValues(self, dict):
        '''Mechanism for processing user submission through a field object's validator/converters'''

        hasError = None

        value = self._findSelfInDict(dict)

        # store the raw input, ends up being none if there's no data in dict for us
        self._rawValue = value

        # run value through validator/converters
        try:
            self._value = value                                 # first, run with whatever was input
                                                                # in the event this field has no
                                                                # no validators
            for validator in self._validators:
                self._value = validator._attemptConvert(value)  # get the validated value, if possible
                self._error = None                                  
        except InvalidField, msg:
            self._error = str(msg)
            self._value = None

            # also, a bit of frosting: if there is an error, flag this field with the styleClass of 'error'
            # that way you can have a global 'error' setting for an application, like color = red or somthing
            self.addClass('error')

    def _setValue(self, value):
        '''Sets the current value of the field element'''
        self._value = value

    def _findSelfInDict(self, dict):
        '''looks through the passed in dict, trying to find self.name()'''
        # note, this was split out to allow for the special case of names that are actually "_action_name" in webkit...
        # webkit _action_ submit buttons/images/urls will need to have subclasses overwrite this method
        return dict.get( self.name() )

    def _reset(self):
        '''Sets the value, default, and error to None'''

        self._value = self._default = self._error = None

        # also remove the error class from the _styleClass, if it's there (should only be there once)
        try:
            self._styleClass.remove('error')
        except ValueError:
            # don't bother with anything
            pass

    def _generateID(self):
        '''Private utility method for generating id attributes for tags'''
        # Note: overriding the id with setID() may cause you problems for checkboxsets, radiosets and compound fields
        #   because all the sub-fields will get assigned the same id. If you plan to do DOM manipulation of those
        #   subfields, beware.
        if self._idAttribute:
            return self._idAttribute
        else:
            return 'field_%s' % self.name()


    ## Access ##

    def error(self):
        '''Public method to return any error message resulting from validation (returns None if no error)'''
        return self._error

    def default(self):
        '''Public method to return the default value for this field (if any)'''
        return self._default

    def validators(self):
        '''Public method to return the list of validators assigned to this field (if any)'''
        return self._validators

    def addValidator(self,validator):
        '''Public method to add a validator instance to this field'''
        self._validators.append(validator)

    def clearValidators(self):
        '''Public method to remove all validators from this field'''
        self._validators = []

    def _valueForDisplay(self):
        '''Private method that returns the most appropriate value for form rendering.'''

        if self.default() is not None and self._value is None:
            # there IS a default, but there is NOT a value
            val = self._default
        else:
            # there is a value and default is none
            if self._value is None:
                val = '' # we return empty string rather than None, so "None" doesn't appear in boxes
            else:
                val = self._value
            
        if self._error and self._rawValue:
            # there is an error, and there is a _rawvalue
            val = self._rawValue

        return val          


    ## output ##

    def dump(self):
        '''Public method for returning a generic lump of HTML to represent the field'''
        # this just uses HTMLElement's dump, and adds in its error, if any
        error = self.error()
        if error is None:
            error = ""
        else:
            error += "<br%s>" % self._closeTag()
        return "%s%s" % ( error, HTMLElement.dump(self) )

    def tag(self):
        '''Public method for returning an <input> tag and nothing but an <input> tag; except when it doesn't, such as
        with radio button sets where the radio switches mean nothing without their labels'''
        # You aren't likely to often call this method directly.

        # ABSTRACT CODE TO BE OVERRIDDEN BY SUBCLASS
        # MUST RETURN A STRING OF HTML or XHTML
        raise MethodUndefined, '''Ooops! No tag method defined for this abstract field class!'''



class ChooseField(Field):
    '''Subclass of field that accomodates Field types that are for multiple choice form elements:
    Select boxes, multi-select boxes, radio sets, checkbox sets'''
    # largely, this just adds the choicelist construct and extends the event model
    # (seeding, processing, resetting) to the Field class

    # this class basically implements select as the default when a default is required  
    
    def __init__(self, name, validators=[], label='', attributes={}, languageStandard='XHTML'):
        Field.__init__(self, name, validators, label, attributes, languageStandard)
        # set a default (blank) choicelist, for syntax convenience
        # you can also overwrite it by hand, if you've got a pre-built (or on-the-fly
        # calculated) choiceList that you want to use... ChoiceList can be a plugin...
        self._choiceList = ChoiceList(type='select')
        self._choiceList._setName( name )   # tell the ChoiceList what its name is going to be

        # tell the ChoiceList what its base id is going to be
        id = self._generateID()
        self._choiceList._setParentID( id )

    def _processDefaults(self, dict):
        '''need special cases of these to set the "selected" state in the ChoiceList'''
        Field._processDefaults(self,dict)   # super
        # now, attempt to set the 'selected' in the choicelist to the default value
        self._choiceList.setSelected(self._default)
        
    def _processRawValues(self, dict):
        '''need special cases of these to set the "selected" state in the ChoiceList'''
        Field._processRawValues(self,dict)  # super
        # now, attempt to set the 'selected' in the choicelist to the new value
        self._choiceList.setSelected(self._rawValue)

    def _reset(self):
        '''Sets the value, default, and error to None'''
        # special reset to also reset the choiceList
        Field._reset(self)
        self._choiceList._reset()

    def setID(self,string):
        '''Public utility method to surgically override the id tag attribute'''
        # this is here, to cascade the id change down into any choices
        # first do the standard thing
        Field.setID(self,string)
        # then do the choices
        self._choiceList._setParentID(string)
        
    def tag(self):
        '''Public method to return the HTML tags for this field'''
        # ABSTRACT CODE TO BE OVERRIDDEN BY SUBCLASS
        # MUST RETURN A STRING OF HTML or XHTML
        raise MethodUndefined, '''Ooops! No tag method defined for this abstract field class!'''

    def choices(self):
        '''Public method to return the choice class instances that are in this select field'''
        return self._choiceList.choices()

    def choiceList(self):
        '''Public method to return the ChoiceList class instance (for access to its methods)'''
        # you will probably very rarely need to use this... you probably just want .choices()
        return self._choiceList

    def addChoice(self, value, label):
        '''Pass in a value, and text label to add a selection to this ChoiceList'''
        # this method is really just a way to get to the .addChoice method inside of the _choices choicelist
        # Added a return statement to return the value of this operation back to the client code.
        return self._choiceList.addChoice(value, label)

    def setChoices(self,choiceListObject):
        '''Pass a selectionList object to this field object to set its selection set'''
        self._choiceList = choiceListObject
        self._choiceList._setName( self.name() )            # tell the ChoiceList what its name is going to be

        self._choiceList._setParentID( self.id() )      # tell the ChoiceList what its base id is going to be

        self._choiceList.setType( 'select' )                # tell the ChoiceList what its type is going to be

    def clearChoices(self):
        self._choiceList.clear()



class CompoundField(Field):
    '''Abstract Subclass of Field that implements subfields, and modifies the behavior of the methods that Form uses to mess with fields
    to accomodate subFields.'''
    # Note, a Compound field can have it's own tags, or not, and can have subfields, or not
    def __init__(self, name, validators=[], label='', attributes={}, languageStandard='XHTML'):
        Field.__init__(self,name,validators,label,attributes,languageStandard)
        self._subFields = [ ]

        # this is the 'value' of the compound field; it may not be displayable, 
        # so just store it here quietly
        self._derivedValue = None
        
    def addSubField( self, field ):
        '''adds a field class instance to subfields'''
        self._subFields.append( field )

    def value( self ):
        '''access method that returns the 'value' of the compound set, NOT
        the value of the main form field. This value is usually derived from
        some combination of the field and its' subFields, or some other logic'''
        return self._derivedValue

    def nativeValue( self ):
        '''the same as .value() if this were a non-compound field class'''
        return Field.value(self)

    def subFields(self):
        '''returns a list of the sub fields'''
        return self._subFields
    

    ## Processing ##

    
    def _processRawValues(self,dict):
        '''may have to override (usually will) for compound fields... for special app logic'''
        # first, do the process on myself
        if hasattr(self,'choices'):             ## @@ The fact this condition is here reflects a minor design weakness
            ChooseField._processRawValues(self,dict)
        else:
            Field._processRawValues(self,dict)
        # and now on the kids
        for each in self._subFields:
            each._processRawValues(dict)

        # chance to derive a value
        self._deriveValue()


    def _deriveValue(self):
        '''figures out what the compound value should be... the value of the set'''
        # overwrite this method to do something interesting
        self._derivedValue = self.nativeValue()


    def _processDefaults(self, dict):
        # first, do the process on myself
        if hasattr(self,'choices'):             ## @@ The fact this condition is here reflects a minor design weakness
            ChooseField._processDefaults(self, dict)
        else:
            Field._processDefaults(self, dict)
            
        # and now on the kids
        for each in self._subFields:
            each._processDefaults(dict)


    def _reset(self):
        # clear out self
        if hasattr(self,'choices'):             ## @@ The fact this condition is here reflects a minor design weakness
            ChooseField._reset(self)
        else:
            Field._reset(self)
        # clear out subfields
        for each in self._subFields:
            each._reset()
        # erase derived value
        self._derivedValue = None


    ## Output ##

    def error(self):
        '''Returns validation errors, allowing for the errors of the CompoundField's sub-fields'''
        # is basically a hack that concatenates error strings together
        errors = []
        # add in myself
        if Field.error(self):
            errors.append( Field.error(self) )
        # add in the kids
        for each in self._subFields:
            if each.error():
                errors.append( each.error() )
        # send them back clumped together with commas
        return string.join(errors, ", ")

    def tag(self):
        '''This isn't terribly useful as-is; you may want to make it more so in the sub-class'''
        tags = []
        # add in myself
        try:
            tags.append( Field.tag(self) )
        except MethodUndefined:
            # no tag here; there must be only subfields
            pass
        
        # add in the kids
        # NOTICE THAT THERE WILL BE NO LABELS ON THE KIDS!
        for each in self._subFields:
            tags.append( each.tag() )
        # send them back clumped together with commas
        return string.join(tags, "<br%s>\n" % self._closeTag() )
        

    def dump(self):
        '''This isn't terribly useful as-is; you may want to make it more so in the sub-class'''
        tmpStr = ""
        # add in myself
        tmpStr += Field.dump(self)
        # add in the kids
        for each in self._subFields:
            tmpStr += each.dump()

        return tmpStr