import cgi

class form(object):
    """ a former form

        usually all you need to do is create a form with some instances of
        fields (see field.py) passed to it, and call 'form.render(data)', where
        data is a cgi.FieldStorage instance or a dict, to have your form both
        validated and rendered - the function returns a tuple (html, errors),
        where errors is None 
    """
    def __init__(self, id, fields, action='.'):
        self.id = id
        self.fields = fields
        self.action = action
    
    def render(self, data=None):
        """ quick way to render a form, though it doesn't return the clean data
        """
        if data is not None and not data.has_key(self.id):
            # we always expect the submit button in the fs
            data = None
        errors = []
        ret = []
        for field in self.fields:
            html, error = field.render(data)
            if error:
                errors.append((field, error))
            ret.append(html)
        ret.insert(0, self.render_open(errors))
        ret.append(self.render_close(errors))
        return '\n'.join(ret), errors

    def process_data(self, data=None):
        """ return the validated/normalized data and a list of errors (if any)

            data is a dict or FieldStorage instance
        """
        if data is None or not data.has_key(self.id):
            return None, []
        errors = []
        ret = {}
        for field in self.fields:
            value, error = field.get_value(data)
            ret[field.id] = value
            if error:
                errors.append((field, error))
        return ret, errors

    def render_validated(self, data, errors):
        """ render the form with data that has already validated
        """
        ret = [self.render_open(errors)]
        errors = dict(errors)
        for field in self.fields:
            value = None
            if data is not None:
                value = data[field.id]
            ret.append(field.render_validated(value, errors.get(field)))
        ret.append(self.render_close(errors))
        return '\n'.join(ret)

    def render_open(self, errors):
        """ mostly internal - render the open tag
        """
        enctype = self.get_enctype()
        onsubmitdata = self.get_onsubmit()
        onsubmit = ''
        if onsubmitdata:
            onsubmit = ' onsubmit="%s"' % (cgi.escape(onsubmit),)
        return '<form action="%s" id="%s" method="post" enctype="%s"%s>' % (
            self.action, self.id, enctype, onsubmit)

    def render_close(self, errors):
        """ mostly internal - render the close tag
        """
        return '\n'.join([
            '<div class="former-field">',
            '<input type="submit" name="%s" value="submit" />' % (self.id,),
            '</div>',
            '</form>',
        ])

    def add_field(self, field):
        """ add a field to the form
        """
        self.fields.append(field)

    def get_fields(self):
        """ yield all the form fields
        """
        for field in self.fields:
            yield field

    def get_field(self, id):
        for field in self.fields:
            if field.id == id:
                return field

    def get_enctype(self):
        default = 'application/x-www-form-urlencoded'
        for field in self.fields:
            if field.enctype != default:
                return field.enctype
        return default

    def get_scripts(self):
        scripts = {}
        for field in self.fields:
            for script in field.scripts:
                scripts[script] = True
        return scripts.keys()

    def get_onload(self):
        onloads = {}
        for field in self.fields:
            onload = field.onload.strip()
            if onload:
                if not onload.endswith(';'):
                    onload += ';'
                onloads[onload] = True
        return ' '.join(onloads)

    def get_onsubmit(self):
        onsubmits = {}
        for field in self.fields:
            onsubmit = field.onsubmit.strip()
            if onsubmit:
                if not onsubmit.endswith(';'):
                    onsubmit += ';'
                onsubmits[onsubmit] = True
        return ' '.join(onsubmits)

    def get_onunload(self):
        onunloads = {}
        for field in self.fields:
            onunload = field.onunload.strip()
            if onunload:
                if not onunload.endswith(';'):
                    onunload += ';'
                onunloads[onunload] = True
        return ' '.join(onunloads)

    def copy(self):
        f = form(self.id, [], self.action)
        for field in self.fields:
            f.add_field(field.copy())
        return f

