import cgi
import re
import datetime as datetimemod
import decimal
import copy

class field(object):
    """ base class for former fields

        instantiate with an id, title, optional default value, optional
        mandatory (defaulting to True), and an optional set of attributes
        (which will be placed on the tag on rendering - XXX not sure about that
        one yet)
    """
    # the following list is used to determine what scripts should be included
    # for widgets that use JS, the form will provide functionality to generate
    # a list of script tags, with each script appearing only once
    scripts = []

    # allows overriding the form's enctype
    enctype = 'application/x-www-form-urlencoded'

    # the following bits allow hooking into certain events, note that multiple
    # instances of the same string will appear only once
    onload = ''
    onunload = ''
    onsubmit = ''

    def __init__(self, id, title, value=None, validators=None,  mandatory=True,
                 attributes=None):
        self.id = id
        self.title = title
        self.value = value or ''
        self.validators = validators or []
        self.mandatory = mandatory
        self.attributes = attributes or []

    def get_value(self, data):
        """ returns the best value available after validation

            this checks if there's data available, if not returns the default
            value (can be empty) for this field, if there is data it tries to
            validate and returns either the validated/normalized data, or the
            value as provided by the user
        """
        value = self.value
        error = None
        if data is not None:
            try:
                value = self.value_from_data(data)
                value = self.validate(value)
            except ValueError, e:
                error = e.args[0]
        return value, error

    def value_from_data(self, data):
        """ retrieve the (single) value from data dict or FieldStorage

            this should extract the value for this field from 'data', which
            can be a cgi.FieldStorage or cgi.MiniFieldStorage instance or
            a dictionary (not None, this has been dealt with already), and
            return it

            for more complex widgets, it may be that the value should be
            built-up from multiple keys in the dict/fs, for instance a date
            field could render itself as seperate day, month and year fields,
            in that case this should be able to both deal with that three field
            situation, and with a single key/value pair
        """
        if (isinstance(data, cgi.FieldStorage) or
                isinstance(data, cgi.MiniFieldStorage)):
            value = data.getvalue(self.id)
        else:
            value = data.get(self.id)
        if value is None:
            # simple cases will want a string, obviously this depends on the
            # type of field (may be date, or list for multi fields, etc.)
            value = ''
        return value

    def validate(self, value):
        """ get the data from 'data' (dict or FieldStorage) and validate

            this is responsible running the validators on the single value
            extracted by 'value_from_data'

            if something goes wrong during validation, this should raise a
            ValueError (usually you can just let the validators raise theirs,
            though), if not it should return the validated value
        """
        if self.mandatory and (value is None or str(value).strip() == ''):
            raise ValueError('field is mandatory')
        for validator in self.validators:
            # allow validators to normalize value
            value = validator.validate(value)
        return value

    def render(self, data):
        """ render the whole widget, including label etc.

            this method extracts the value from data (FieldStorage or dict),
            validates it, and renders the field all at once, and returns a
            tuple (html, error) with the generated html and the error
            that were generated (string, None if nothing went wrong)

            call with None as data argument to skip validation and use the
            default value of this field, or None, as value
        """
        value, error = self.get_value(data)
        return (self.render_validated(value, error), error)

    def render_validated(self, value, error):
        """ render the tag including the label, error message, etc.

            you can override this in subclasses, but usually it makes more
            sense to just override render_tag below
        """
        cls = 'former-field'
        if self.mandatory:
            cls += '-mandatory'
        ret = ['<div class="%s">' % (cls,)]
        if error:
            ret.append('<div class="former-error">%s</div>' % (
                        cgi.escape(error),))
        ret += ['<div class="former-label">',
                '<label for="%s">%s</label>' % (self.id,
                                                cgi.escape(self.title)),
                '</div>']
        ret.append(self.render_tag(value, error))
        ret += [
            '</div>',
        ]
        return '\n'.join(ret)

    def render_tag(self, value, error):
        """ render just the field tag without label etc.

            override this in subclasses
        """
        attrs = ''
        if self.attributes:
            attrs = ' ' + ' '.join(self.attributes)
        return '<input%s />' % (attrs,)

    def copy(self):
        return self.__class__(self.id, self.title, self.value,
                              self.validators[:], self.mandatory,
                              self.attributes[:])


class line(field):
    def render_tag(self, value, error):
        if value is None:
            value = ''
        attrs = ''
        if self.attributes:
            attrs = ' ' + ' '.join(self.attributes)
        return '<input type="text" name="%s" id="%s" value="%s"%s />' % (
            self.id, self.id, cgi.escape(value), attrs)


class hidden(field):
    def render_validated(self, value, error):
        if value is None:
            value = ''
        return '<input type="hidden" name="%s" id="%s" value="%s" />' % (
            self.id, self.id, cgi.escape(value))


class password(field):
    def render_tag(self, value, error):
        # note that we never render the actual value into the field
        attrs = ''
        if self.attributes:
            attrs = ' ' + ' '.join(self.attributes)
        return '<input type="password" name="%s" id="%s" value=""%s />' % (
            self.id, self.id, attrs)


class integer(line):
    # this can just inherit render_tag from line, string interpolation into the
    # tag works just fine with an int...
    def value_from_data(self, data):
        value = super(integer, self).value_from_data(data)
        try:
            value = int(value)
        except ValueError:
            raise ValueError('could not convert to integer')
        return value

    def render_tag(self, value, error):
        if value is None:
            value = ''
        return super(integer, self).render_tag(str(value), error)


class date(line):
    def value_from_data(self, data):
        from dateutil import parser
        if (isinstance(data, cgi.FieldStorage) or
                isinstance(data, cgi.MiniFieldStorage)):
            value = data.getvalue(self.id)
        else:
            value = data.get(self.id)
        if value is None:
            return None
        if isinstance(value, datetimemod.date):
            return value
        value = value.strip()
        p = parser.parser()
        p.info = parser.parserinfo()
        p.info.dayfirst = True
        try:
            value = p.parse(value).date()
        except ValueError:
            raise ValueError('unexpected date format')
        return value

    def render_tag(self, value, error):
        if not value:
            value = ''
        else:
            value = '%s-%s-%s' % (value.day, value.month, value.year)
        return super(date, self).render_tag(value, error)


class datetime(date):
    def value_from_data(self, data):
        from dateutil import parser
        if (isinstance(data, cgi.FieldStorage) or
                isinstance(data, cgi.MiniFieldStorage)):
            value = data.getvalue(self.id)
        else:
            value = data.get(self.id)
        if value is None:
            return None
        if isinstance(value, datetimemod.datetime):
            return value
        value = value.strip()
        p = parser.parser()
        p.info = parser.parserinfo()
        p.info.dayfirst = True
        try:
            value = p.parse(value)
        except ValueError:
            raise ValueError('unexpected date format')
        return value

    def render_tag(self, value, error):
        if not value:
            value = ''
        else:
            value = '%s-%s-%s %s:%s' % (value.day, value.month, value.year,
                                        value.hour, value.minute)
        return super(date, self).render_tag(value, error)

class money(line):
    """ handle money data

        allow using a comma instead of a dot for decimals, don't allow
        seperators for thousands, convert to plain decimal
    """
    reg_comma = re.compile('^\d*\,\d{1,2}$')
    def value_from_data(self, data):
        if (isinstance(data, cgi.FieldStorage) or
                isinstance(data, cgi.MiniFieldStorage)):
            value = data.getvalue(self.id)
        else:
            value = data.get(self.id)
        if value is None or isinstance(value, decimal.Decimal):
            return value
        value = value.strip()
        if self.reg_comma.match(value):
            value = value.replace(',', '.')
        try:
            value = decimal.Decimal(value)
        except decimal.InvalidOperation:
            raise ValueError('unexpected format')
        if value < 0:
            raise ValueError('can not be less than 0')
        return value

    def render_tag(self, value, error):
        if not value:
            value = ''
        else:
            value = ('%0.2f' % (value,)).replace('.', ',')
        return super(money, self).render_tag(value, error)


class text(field):
    """ a textarea
    """
    def render_tag(self, value, error):
        if value is None:
            value = ''
        attrs = ''
        if self.attributes:
            attrs = ' ' + ' '.join(self.attributes)
        return '<textarea name="%s" id="%s"%s>%s</textarea>' % (
            self.id, self.id, attrs, cgi.escape(value))


class selectfield(field):
    """ multiple choices, one result

        requires an additional argument 'values', a list (or tuple) of tuples
        (value, labeltext)
    """
    def __init__(self, id, title, values, value=None, validators=None,
                 mandatory=True, attributes=[]):
        super(selectfield, self).__init__(id, title, value, validators,
                                         mandatory, attributes)
        self.values = values

    def copy(self):
        return self.__class__(self.id, self.title, self.values, self.value,
                              self.validators, self.mandatory, self.attributes)


class radio(selectfield):
    def render_tag(self, value, error):
        if value is None:
            value = ''
        attrs = ''
        if self.attributes:
            attrs = ' ' + ' '.join(self.attributes)
        ret = ['<div class="former-field-valuegroup">']
        for i, (rvalue, labeltext) in enumerate(self.values):
            # check this checkbox if a) it has been selected, or b) this is
            # the first checkbox of a mandatory field and there is no value
            # (effectively selecting the first element by default)
            currentattrs = attrs
            checkvalue = value or self.value
            if ((not value and i == 0 and self.mandatory) or
                    rvalue == checkvalue):
                currentattrs += ' checked="checked"'
            ret += [
                '<input type="radio" name="%s" id="%s-%s" value="%s"%s />' % (
                 self.id, self.id, i, cgi.escape(rvalue), currentattrs),
                '<label for="%s-%s">%s</label>' % (self.id, i,
                                                   cgi.escape(labeltext)),
            ]
        ret.append('</div>')
        return '\n'.join(ret)


class singleselect(selectfield):
    def render_tag(self, value, error):
        if value is None:
            value = ''
        attrs = ''
        if self.attributes:
            attrs = ' ' + ' '.join(self.attributes)
        ret = ['<select name="%s" id="%s" class="former-field-select"%s>' % (
                self.id, self.id, attrs)]
        for i, (rvalue, labeltext) in enumerate(self.values):
            selectedstr = ''
            # check this option if a) it has been selected, or b) this is
            # the first option of a mandatory field and there is no value
            # (effectively selecting the first element by default)
            selectvalue = value or self.value
            if ((not value and i == 0 and self.mandatory) or
                    rvalue == selectvalue):
                selectedstr = ' selected="selected"'
            ret += [
                '<option id="%s-%s" value="%s"%s>' % (
                 self.id, i, cgi.escape(rvalue), selectedstr),
                cgi.escape(labeltext),
                '</option>',
            ]
        ret.append('</select>')
        return '\n'.join(ret)


class multifield(field):
    """ multiple choices, multiple results

        requires an additional argument 'values', a list (or tuple) of tuples
        (value, labeltext)

        self.value is not a single value, but a list
    """
    def __init__(self, id, title, values, value=None, validators=None,
                 mandatory=True, attributes=[]):
        super(multifield, self).__init__(id, title, value, validators,
                                         mandatory, attributes)
        self.value = value or []
        self.values = values

    def value_from_data(self, data):
        if (isinstance(data, cgi.FieldStorage) or
                isinstance(data, cgi.MiniFieldStorage)):
            value = data.getlist(self.id)
        else:
            value = data.get(self.id)
            if not isinstance(value, list):
                value = [value]
        if value is None:
            value = []
        return value

    def copy(self):
        return self.__class__(self.id, self.title, self.values, self.value,
                              self.validators, self.mandatory, self.attributes)


class checkbox(multifield):
    """ a set of checkboxes
    """
    def render_tag(self, value, error):
        if value is None:
            value = []
        attrs = ''
        if self.attributes:
            attrs = ' ' + ' '.join(self.attributes)
        ret = ['<div class="former-field-valuegroup">']
        for i, (rvalue, labeltext) in enumerate(self.values):
            # check this checkbox if a) it has been selected, or b) this is
            # the first checkbox of a mandatory field and there is no value
            # (effectively selecting the first element by default)
            currentattrs = attrs
            checkvalue = value or self.value
            if ((not value and i == 0 and self.mandatory) or
                    rvalue in checkvalue):
                currentattrs += ' checked="checked"'
            ret += [
                ('<input type="checkbox" name="%s" id="%s-%s" '
                 'value="%s"%s />') % (
                 self.id, self.id, i, cgi.escape(rvalue), currentattrs),
                '<label for="%s-%s">%s</label>' % (self.id, i,
                                                   cgi.escape(labeltext)),
            ]
        ret.append('</div>')
        return '\n'.join(ret)

