import re
import types

# The possible fields for each type of object.
#
# Each field is a tuple (fieldname, fieldtype).  Fieldtype is either
# 's' (string), 'd' (dictionary, where the values are lists of
# strings), 'l' (list of strings).
fields = {
    'book':
        [('isbn', 's'), ('id', 's'), ('title', 's'), ('category', 's'),
         ('subtitle', 's'), ('persons', 'd'), ('publisher', 's'),
         ('published_year', 'i'), ('edition', 'i'), ('pages', 'i'),
         ('series', 's'), ('description', 's'), # TODO picture, thumbnail
         ('references', 'd')],
    'person':
        [('id', 's'), ('firstname', 's'), ('lastname', 's')],
    'category':
        [('id', 's'), ('name', 's'), ('placement', 'l')] }

# Fields associated with each action.
#
# The 'type' is a key in the fields dictionary, indicating the set of
# fields which can be used with the action.  The 'required' list is a
# list of fields which must be present for the action to be accepted.
action_fields = {
    'new-book':
        { 'type': 'book',
          'required': ['isbn', 'title', 'category'] },
    'edit-book':
        { 'type': 'book',
          'required': ['isbn'] },
    'delete-book':
        { 'type': 'book',
          'required': ['isbn'] },
    'new-person':
        { 'type': 'person',
          'required': ['id', 'firstname', 'lastname'] },
    'edit-person':
        { 'type': 'person',
          'required': ['id'] },
    'delete-person':
        { 'type': 'person',
          'required': ['id'] },
    'new-category':
        { 'type': 'category',
          'required': ['id', 'name'] },
    'edit-category':
        { 'type': 'category',
          'required': ['id'] },
    'delete-category':
        { 'type': 'category',
          'required': ['id'] } }

class CommitFormatSyntaxError(Exception):
    def __init__(self, msg, linenr):
        super(CommitFormatSyntaxError, self).__init__(self, 'Syntax error on line %d: %s' %
                                                      (linenr, msg))

def read_field_value_str(val):
    if val.strip() == '':
        return None
    return '\n'.join(map(lambda x: x.strip(), val.split('\n'))).strip()

def read_field_value_int(val):
    if val.strip() == '':
        return None
    return int(val.strip())

def read_field_value_dict(val):
    d = {}
    if val.strip() == '':
        return d
    for line in val.strip().split('\n'):
        key, value = line.strip().split(' ', 1)
        if key in d:
            d[key].append(value)
        else:
            d[key] = [value]
    return d

def read_field_value_list(val):
    return val.strip().split(' ')

def read_action(lines):
    '''
    Parse text as an action, returning a dictionary.
    '''
    print 'reading action'
    print 'lines:'
    print lines
    d = {}
    lastfield = None
    for (linenr, line) in lines:
        if len(line) == 0:
            raise CommitFormatSyntaxError('Empty line in action', linenr)
        if line[0] in [' ', '\t']: # continuation line
            if not lastfield:
                raise CommitFormatSyntaxError('First line is continuation line: ' + line, linenr)
            d[lastfield] = d[lastfield] + '\n' + line.strip()
        else:
            field, value = line.split(':', 1)
            d[field] = value.strip()
            lastfield = field

    firstlinenr = lines[0][0]

    if 'action' not in d:
        raise CommitFormatSyntaxError('Missing \'action\' field', firstlinenr)
    action = d['action']

    print 'dict:'
    print d

    for field in action_fields[action]['required']:
        if field not in d:
            raise CommitFormatSyntaxError('Missing required field \'%s\' in \'%s\' action' %
                                          (field, action),
                                          firstlinenr)
    data_type = action_fields[action]['type']
    result = { 'action': action }
    for field, ftype in fields[data_type]:
        if field in d:
            reader = { 's': read_field_value_str,
                       'i': read_field_value_int,
                       'd': read_field_value_dict,
                       'l': read_field_value_list }[ftype]
            result[field] = reader(d[field])
        else:
            result[field] = None

    print 'final dict:'
    print result

    return result

def read_paragraphs(text):
    lines = text.split('\n')
    current_para = []
    paragraphs = []
    comment_re = r'^#.*$'
    blank_re = r'^[ \t]*$'
    for i in xrange(len(lines)):
        l = lines[i]
        if re.match(comment_re, l):
            print 'comment:', l
            continue
        elif re.match(blank_re, l):
            print 'blank:', l
            if len(current_para) > 0:
                paragraphs.append(current_para)
                current_para = []
        else:
            current_para.append((i+1, l))
    if len(current_para) > 0:
        paragraphs.append(current_para)
        current_para = []
    return paragraphs

def read_actionlist(text):
    '''
    Parse text as a list of actions.

    The result is a list of dictionaries.
    '''
    paragraphs = read_paragraphs(text)
    print 'paragraphs:', paragraphs
    return map(read_action, paragraphs)

def write_field_value_str(val):
    lines = ''
    if not val:
        val = ''
    val = unicode(val)
    value_lines = val.split('\n')
    for l in value_lines:
        lines += ' ' + l + '\n'
    return lines

def write_field_value_dict(val):
    lines = '\n'
    for (key,values) in val.items():
        for single_value in values:
            lines += ' ' + key + ' ' + unicode(single_value) + '\n'
    return lines

def write_field_value_list(val):
    lines = ''
    for single_value in val:
        lines += ' ' + unicode(single_value)
    return lines

def make_comment(s):
    return '\n'.join(map(lambda x: '# ' + x,
                         s.split('\n'))) + '\n'

def write_action(d):
    if type(d) in types.StringTypes:
        return make_comment(d)
    lines = ''
    if 'comment' in d:
        lines += make_comment(d['comment'])
    action = d['action']
    lines += 'action: ' + action + '\n'
    data_type = action_fields[action]['type']
    for field, ftype in fields[data_type]:
        if field in d:
            value_writer = {'s': write_field_value_str,
                            'i': write_field_value_str,
                            'd': write_field_value_dict,
                            'l': write_field_value_list}[ftype]
            lines += field + ':' + value_writer(d[field])
    return lines

def write_actionlist(actions):
    return '\n'.join(map(write_action, actions))

# test: print write_actionlist([{'comment':'Foo!\nBar!','action':'new-book','isbn':'434545'},{'action':'edit-book','isbn':'654654745','persons':{'author':['ab','foo'],'illustrator':['moo']}},'This\nis\na\ncomment.',{'action':'edit-category','id':'matematikk','name':'Matematikk','placement':['T10','T11']}])