# This file contains the base classes for ChoiceList and Choice

from types import DictionaryType, ListType
from sys import stderr

import string

try:
    from WebUtils.Funcs import htmlEncode
except ImportError:
    from Webware.WebUtils.Funcs import htmlEncode

from BaseTagClasses import HTMLElement

class Choice(HTMLElement):
    '''Base class for a single choice in a choice list'''
    def __init__( self, value, label, type='select',languageStandard='XHTML'):

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

        self._value = value     # choice value, so it's here rather than HTMLElement
        self._type = type                                       # select | checkbox | radio switch
        self._selected = None                                   # will hold selection bit state for this choice (yes/no)
        self._parentID = None                                   # will hold the id string from the parent field

    def _setType(self,type):                                    # private method used by ChoiceList
        self._type = type

    def _setSelected(self):                                     # private method used by ChoiceList
        self._selected = 1
        
    def _setUnSelected(self):                                   # private method used by ChoiceList
        self._selected = None

    def _setName(self, name):
        self._name = name

    def _setParentID(self,id):
        '''private utility method to set the id of the parent field; useful in deriving ids for each choice tag'''
        self._parentID = id
    

    def _generateID(self):
        '''Private utility method for generating id attributes for tags'''
        # we want SOMETHING to be output if this choice isn't associated with a field yet
        # so make up something that will flag it as such
        parentID = self._parentID
        if not parentID:
            parentID = "orphanChoice"
        # the id of this choice will be the parent's id, with the value of the choice added to it
        return '%s_%s' % ( parentID, htmlEncode( str( self.value() ) ).replace(' ','_') )

    ## access methods ##

    def tag(self):
        '''Public method to return the HTML tag for the choice'''
        if self._type == 'select':
            return self._tagSelect()
        elif self._type == 'checkbox':
            return self._tagCheckbox()
        else:
            return self._tagRadio()

    def _tagSelect(self):
        '''Private method responsible for returning the <option> tags used by select boxes'''
        # prep some parts of the tag
        tagAttributes = self._tagAttributes()
        if tagAttributes and tagAttributes[0] != " ":
            # make sure tagAttributes has a space on the beginning for splatting into the string below
            tagAttributes = " %s" % tagAttributes

        # determine any status attribute we have to add (if item is selected)               
        if self._selected:
            if self._languageStandard == 'XHTML':
                selectedStr = ' selected="selected"'
            else:
                selectedStr = ' SELECTED'
        else:
            selectedStr = ''

        return '\n\t<option id="%s" value="%s"%s%s>%s</option>' % (
            self.id(),
            self.value(),
            selectedStr,
            tagAttributes,
            htmlEncode(self.label()) )

    
    def _tagCheckbox(self):
        '''Private method responsible for returning checkbox-type <input> tags'''

        # prep some parts of the tag
        tagAttributes = self._tagAttributes()
        if tagAttributes and tagAttributes[0] != " ":
            # make sure tagAttributes has a space on the beginning for splatting into the string below
            tagAttributes = " %s" % tagAttributes

        # prep for whether we hit the end of the input with a / or not
        if self._languageStandard != 'XHTML':
            slashStr = ''
        else:
            slashStr = ' /'
            
        # determine any status attribute we have to add (if item is selected)               
        if self._selected:
            if self._languageStandard == 'XHTML':
                selectedStr = ' checked="checked"'
            else:
                selectedStr = ' CHECKED'
        else:
            selectedStr = ''

        # create tag
        return '''<input type="checkbox" id="%s" name="%s" value="%s"%s%s%s>''' % (
            self.id(),
            self._name,
            self.value(),
            selectedStr,
            tagAttributes,
            slashStr,)

    
    def _tagRadio(self):
        '''Private method responsible for returning radio-type <input> tags'''

        # prep some parts of the tag
        tagAttributes = self._tagAttributes()
        if tagAttributes and tagAttributes[0] != " ":
            # make sure tagAttributes has a space on the beginning for splatting into the string below
            tagAttributes = " %s" % tagAttributes

        # prep for whether we hit the end of the input with a / or not
        if self._languageStandard != 'XHTML':
            slashStr = ''
        else:
            slashStr = ' /'

        # determine any status attribute we have to add (if item is selected)               
        if self._selected:
            if self._languageStandard == 'XHTML':
                selectedStr = ' checked="checked"'
            else:
                selectedStr = ' SELECTED'
        else:
            selectedStr = ''

        return '<input type="radio" id="%s" name="%s" value="%s"%s%s%s>' % (
            self.id(),
            self._name,
            self.value(),
            selectedStr,
            tagAttributes,
            slashStr,)


class ChoiceList:
    '''This is an abstract class for handling finite lists of options for use in SelectField, CheckboxSet, RadioSet
    and related FormKit fields'''

    def __init__(self, type='select', languageStandard='XHTML'):
        ''' '''
        assert languageStandard.upper() in ('HTML','XHTML'), "You can only use HTML or XHTML for languageStandard."

        self._type = type                                           # a reasonable default state
        self._languageStandard = languageStandard
        self._choices = []                                          # list of Choices (ordered)
        self._name = ''
        self._parentID = None                                           # base ID to use for choices (provided by Field)

    def addChoice(self, value, label):
        '''Public method: pass in a value, and text label to add a selection to this ChoiceList'''
        if value in self.values():
            stderr.write('ChoiceList "%s" already contains a value of "%s"' % (self._name or '(unnamed)', value))
            return None
        else:
            # make a new choice class
            tmpChoice = Choice( value, label, self._type, self._languageStandard )
            
            # set its parentID
            tmpChoice._setParentID( self._parentID )

            # set its name
            tmpChoice._setName( self._name )

            # stick it on the stack
            self._choices.append( tmpChoice )

            # tmpChoice is a valid HTML Element with its
            # own operations.  Returning it here so that
            # clients of this code can modify the
            # attributes of the element in-line, thus
            # keeping all modification operations local to
            # the instantiation.
            return tmpChoice


    def _setParentID(self,id):
        '''Method to set the base id for the choices -- this is normally set automatically
        but you can override it on a choice-by-choice basis with the public setID'''
        self._parentID = id
        for choice in self._choices:
            choice._setParentID(id)
            
            
    def setType(self,type):
        '''Public method to set the type of choices you're making: select | radio | checkbox'''
        type = type.lower()
        assert type in ('select','radio','checkbox'), "You must use one of these types: select, radio, checkbox"
        self._type = type
        # teach each choice its new type state
        for choice in self.choices():
            choice._setType(type)

    def _reset(self):
        '''clears any selected choice to unselected'''
        # this can be done just by sending "" to setSelected
        self.setSelected("")

    def setSelected(self,value):
        '''Public method to attempt to set this choice list to the choice represented by value'''
        # first, turn them all off (to handle radios, etc.)
        # any ones that are "supposed" to be on should be in the values coming in
        for choice in self.choices():
            choice._setUnSelected()

        # value will be a list if this is a multi-select box    
        if type(value) != ListType:
            value = [value] # make it into a list, so we can use the following logic
        # go through the values and try and find them in our choices
        for v in map(str,value):                                    # we want to compare strings to strings, otherwise
                                                                    #   we might get false negatives from string-based
                                                                    #   form input ( '4' == 4 ), and that would be bad
            try:
                # try and recognize the value
                strValues = map(str,self.values())      # as above, we convert v to a string so we're guaranteed
                location = strValues.index(v)           # to be comparing apples and apples

                self.choices()[location]._setSelected()
            except ValueError:
                # can't set any of our choices to selected, because we don't recognize the value
                pass

    def clear(self):
        '''Public method to clear out all the choices'''
        self._choices = []

    def _setName(self,name):
        '''Private method to set the base name for the choices -- this is normally set automatically'''
        self._name = name
        for choice in self._choices:
            choice._setName(name)
            
        

    ## Access methods ##        

    def choices(self):
        '''Public method to return list of choice objects'''
        return self._choices

    def values(self):
        '''Public convenience method to return a list of possible values'''
        tmp = []
        for choice in self.choices():
            tmp.append( choice.value() )
        return tmp

    def labels(self):
        '''Public convenience method to return a list of possible choice labels'''
        tmp = []
        for choice in self.choices():
            tmp.append( choice.label() )
        return tmp