#!/usr/bin/env python

import sys,os,re

class Error(Exception):
    pass


class ParseError(Error):
    def __init__(self, errline):
        Error.__init__(self, errline)


class Struct:
    pass


def createCNameMap():
    t = ''
    for i in range(256):
        if ((ord('A') <= i) and (i <= ord('Z'))) or \
               ((ord('a') <= i) and (i <= ord('z'))) or \
               ((ord('0') <= i) and (i <= ord('9'))):
            t += chr(i)
        else:
            t += '_'
    return t


def seekBegin(f):
    while True:
        line = f.readline()
        if not line:
            return False
        if line.startswith('BEGIN SANE_Option_Descriptor'):
            return True


def parseVerbatim(o, line):
    words = line.split(None, 1)
    if (len(words) < 2) or (words[1][0] != '@'):
        return False
    o[words[0]] = words[1]
    return True


def parseLine_type(o, line):
    words = line.split(None, 2)
    otype = words[1]
    o['type'] = 'SANE_TYPE_' + otype.upper()
    if otype == 'group':
        g.ngroups += 1
        oname = '_group_%d' % g.ngroups
        o['size'] = 0
    else:
        temp = words[2]
        idx = temp.find('[')
        if idx == -1:
            oname = temp
            o['size'] = 1
        else:
            oname = temp[0:idx]
            o['size'] = int(temp[idx+1:-1])
    o['name'] = oname


def parseLine_title(o, line):
    o['title'] = line.split(None, 1)[1]


def parseLine_desc(o, line):
    o['desc'] = line.split(None, 1)[1]


def parseLine_unit(o, line):
    o['unit'] = 'SANE_UNIT_' + line.split(None, 1)[1].upper()


def parseLine_default(o, line):
    o['default'] = line.split(None, 1)[1]


def parseLine_cap(o, line):
    words = line.split()
    o['cap'] = ['SANE_CAP_' + s.upper() for s in words[1:]]


def parseLine_constraint(o, line):
    c = line.split(None,1)[1]
    if c[0] == '{':
        o['constraint'] = c[1:-1].split('|')
    elif c[0] == '(':
        o['constraint'] = tuple(c[1:-1].split(','))
    else:
        sys.stderr.write('Ignored: %s\n' % line)


def parseLine_info(o, line):
    words = line.split()
    o['info'] = ['SANE_INFO_' + s.upper() for s in words[1:]]

def parseLine_rem(o, line):
    pass

def normalize(o):
    if 'cname' not in o:
        cname = o['name'].translate(cnameMap)
        o['cname'] = cname
    else:
        cname = o['cname']
    o['cname_opt'] = 'opt_' + cname
    o['cname_con'] = 'constraint_' + cname
    if 'title' not in o:
        o['title'] = 'NO TITLE'
    if 'desc' not in o:
        o['desc'] = '@sod->title' % o
    if 'unit' not in o:
        o['unit'] = 'SANE_UNIT_NONE'
    if 'constraint_type' not in o:
        if 'constraint' not in o:
            ct = 'SANE_CONSTRAINT_NONE'
        elif isinstance(o['constraint'], list):
            if o['type'] == 'SANE_TYPE_STRING':
                ct = 'SANE_CONSTRAINT_STRING_LIST'
            else:
                ct = 'SANE_CONSTRAINT_WORD_LIST'
        elif isinstance(o['constraint'], tuple):
            ct = 'SANE_CONSTRAINT_RANGE'
        elif isinstance(o['constraint'], str):
            oc = o['constraint']
            if oc.startswith('@range'):
                ct = 'SANE_CONSTRAINT_RANGE'
            elif oc.startswith('@word_list'):
                ct = 'SANE_CONSTRAINT_WORD_LIST'
            elif oc.startswith('@string_list'):
                ct = 'SANE_CONSTRAINT_STRING_LIST'
        o['constraint_type'] = ct
    return o


def parseFile(f):
    if not seekBegin(f):
        return None
    options = [ {
        'name' : '',
        'cname' : 'opt_num_opts',
        'title' : '@SANE_TITLE_NUM_OPTIONS',
        'desc' : '@SANE_DESC_NUM_OPTIONS',
        'type' : 'SANE_TYPE_INT',
        'unit' : 'SANE_UNIT_NONE',
        'size' : 1,
        'cap' : ['SANE_CAP_SOFT_DETECT'],
        'constraint_type' : 'SANE_CONSTRAINT_NONE',
        'default' : '@w = ' + opt_prefix + 'last'
        } ]
    o = {}
    while True:
        line = f.readline()
        if not line:
            break
        line = line.strip()
        if not line:
            continue
        token = line.split(None, 1)[0].lower()
        if token == 'end':
            break
        if token == 'type':
            if 'name' in o:
                options.append(o)
            o = {}
        funcName = 'parseLine_' + token
        if funcName in globals():
            if not parseVerbatim(o, line):
                func = globals()[funcName]
                func(o, line)
        else:
            sys.stderr.write('Skip: %s\n' % line)
    if 'name' in o:
        options.append(o)
    return [normalize(o) for o in options]


def genHeader(options):
    print """
typedef union {
  SANE_Word w;
  SANE_Int  i;
  SANE_Bool b;
  SANE_Fixed f;
  SANE_String s;
  void *ptr;
} option_value_t;
"""
    print 'typedef enum {'
    for o in options:
        print '  %(cname_opt)s,' % o
    print '  ' + opt_prefix + 'last'
    print '} option_t;'
    print """

typedef struct {
  SANE_Option_Descriptor sod;
  option_value_t val,def;
  SANE_Word info;
} option_descriptor_t;


struct pixma_sane_t;
static int build_option_descriptors(struct pixma_sane_t *ss);
"""


def genMinMaxRange(n, t, r):
    if t == 'SANE_TYPE_FIXED':
        r = ['SANE_FIX(%s)' % x for x in r]
    print 'static const SANE_Range ' + n + ' = '
    print '  { ' + r[0] + ',' + r[1] + ',' + r[2] + ' };'


def genList(n, t, l):
    if t == 'SANE_TYPE_INT':
        etype = 'SANE_Word'
        l = [str(len(l))] + l
    elif t == 'SANE_TYPE_FIXED':
        etype = 'SANE_Word'
        l = [str(len(l))] + ['SANE_FIX(%s)' % x for x in l]
    elif t == 'SANE_TYPE_STRING':
        etype = 'SANE_String_Const'
        l = ['SANE_I18N("%s")' % x for x in l] + ['NULL']
    print 'static const %s %s[%d] = {' % (etype, n, len(l))
    for x in l[0:-1]:
        print '\t' + x + ','
    print '\t' + l[-1] + ' };'


def genConstraints(options):
    for o in options:
        if 'constraint' not in o: continue
        c = o['constraint']
        oname = o['cname_con']
        otype = o['type']
        if isinstance(c, tuple):
            genMinMaxRange(oname, otype, c)
        elif isinstance(c, list):
            genList(oname, otype, c)
    print

def buildCodeVerbatim(o):
    for f in ('name', 'title', 'desc', 'type', 'unit', 'size', 'cap',
              'constraint_type', 'constraint', 'default'):
        if (f not in o): continue
        temp = o[f]
        if (not isinstance(temp,str)) or \
           (len(temp) < 1) or (temp[0] != '@'):
            continue
        o['code_' + f] = temp[1:]

def ccode(o):
    buildCodeVerbatim(o)
    if 'code_name' not in o:
        o['code_name'] = '"' + o['name'] + '"'
    for f in ('title', 'desc'):
        cf = 'code_' + f
        if cf in o: continue
        o[cf] = 'SANE_I18N("' + o[f] + '")'

    for f in ('type', 'unit', 'constraint_type'):
        cf = 'code_' + f
        if cf in o: continue
        o[cf] = o[f]

    if 'code_size' not in o:
        otype = o['type']
        osize = o['size']
        if otype == 'SANE_TYPE_STRING':
            code = str(osize + 1)
        elif otype == 'SANE_TYPE_INT' or otype == 'SANE_TYPE_FIXED':
            code = str(osize) + ' * sizeof(SANE_Word)'
        elif otype == 'SANE_TYPE_BUTTON':
            code = '0'
        else:
            code = 'sizeof(SANE_Word)'
        o['code_size'] = code

    if ('code_cap' not in o) and ('cap' in o):
        o['code_cap'] = reduce(lambda a,b: a+'|'+b, o['cap'])
    else:
        o['code_cap'] = '0'

    if ('code_info' not in o) and ('info' in o):
        o['code_info'] = reduce(lambda a,b: a+'|'+b, o['info'])
    else:
        o['code_info'] = '0'

    if ('code_default' not in o) and ('default' in o):
        odefault = o['default']
        otype = o['type']
        if odefault == '_MIN':
            rhs = 'w = sod->constraint.range->min'
        elif odefault == '_MAX':
            rhs = 'w = sod->constraint.range->max'
        elif otype in ('SANE_TYPE_INT', 'SANE_TYPE_BOOL'):
            rhs = 'w = %(default)s'
        elif otype == 'SANE_TYPE_FIXED':
            rhs = 'w = SANE_FIX(%(default)s)'
        elif otype == 'SANE_TYPE_STRING':
            rhs = 's = SANE_I18N("%(default)s")'
        o['code_default'] = rhs % o
    if 'code_default' in o:
        code = '  opt->def.%(code_default)s;\n'
        if o['constraint_type'] != 'SANE_CONSTRAINT_STRING_LIST':
            code += '  opt->val.%(code_default)s;\n'
        else:
            code += '  opt->val.w = find_string_in_list' \
                    '(opt->def.s, sod->constraint.string_list);\n'
        o['full_code_default'] = code % o
    else:
        o['full_code_default'] = ''

    if ('code_constraint' not in o) and ('constraint' in o):
        ct = o['constraint_type']
        idx = len('SANE_CONSTRAINT_')
        ctype = ct[idx:].lower()
        if ctype == 'range':
            rhs = '&%(cname_con)s' % o
        else:
            rhs = '%(cname_con)s' % o
        o['code_constraint'] = ctype + ' = ' + rhs
    if 'code_constraint' in o:
        code = '  sod->constraint.%(code_constraint)s;\n'
        o['full_code_constraint'] = code % o
    else:
        o['full_code_constraint'] = ''

    return o

def genBuildOptions(options):
  print """
static
int find_string_in_list(SANE_String_Const str, const SANE_String_Const *list)
{
  int i;
  for (i = 0; list[i] && strcmp(str, list[i]) != 0; i++) {}
  return i;
}

static
int build_option_descriptors(struct pixma_sane_t *ss)
{
  SANE_Option_Descriptor *sod;
  option_descriptor_t *opt;

  memset(OPT_IN_CTX, 0, sizeof(OPT_IN_CTX));"""

  for o in options:
      o = ccode(o)
      otype = o['type']
      code = '\n  opt = &(OPT_IN_CTX[%(cname_opt)s]);\n' \
             '  sod = &opt->sod;\n' \
             '  sod->type = %(code_type)s;\n' \
             '  sod->title = %(code_title)s;\n' \
             '  sod->desc = %(code_desc)s;\n'
      if otype != 'SANE_TYPE_GROUP':
          code += '  sod->name = %(code_name)s;\n' \
                  '  sod->unit = %(code_unit)s;\n' \
                  '  sod->size = %(code_size)s;\n' \
                  '  sod->cap  = %(code_cap)s;\n' \
                  '  sod->constraint_type = %(code_constraint_type)s;\n' \
                  '%(full_code_constraint)s' \
                  '  OPT_IN_CTX[%(cname_opt)s].info = %(code_info)s;\n' \
                  '%(full_code_default)s'
      sys.stdout.write(code % o)
  print
  print '  return 0;\n'
  print '}'
  print

g = Struct()
g.ngroups = 0
opt_prefix = 'opt_'
con_prefix = 'constraint_'
cnameMap = createCNameMap()
options = parseFile(sys.stdin)
print "/* Automatically generated from pixma_sane.c */"
if (len(sys.argv) == 2) and (sys.argv[1] == 'h'):
    genHeader(options)
else:
    genConstraints(options)
    genBuildOptions(options)

