import re
from .regex import REGEX_SEARCH_PATTERN, REGEX_SQUARE_BRACKETS
from .._compat import long

def to_num(num):
    result = 0
    try:
        result = long(num)
    except NameError as e:
        result = int(num)
    return result

class RestParser(object):
    def __init__(self, db):
        self.db = db

    def auto_table(self, table, base='', depth=0):
        patterns = []
        for field in self.db[table].fields:
            if base:
                tag = '%s/%s' % (base, field.replace('_', '-'))
            else:
                tag = '/%s/%s' % (
                    table.replace('_', '-'), field.replace('_', '-'))
            f = self.db[table][field]
            if not f.readable:
                continue
            if f.type == 'id' or 'slug' in field or \
               f.type.startswith('reference'):
                tag += '/{%s.%s}' % (table, field)
                patterns.append(tag)
                patterns.append(tag+'/:field')
            elif f.type.startswith('boolean'):
                tag += '/{%s.%s}' % (table, field)
                patterns.append(tag)
                patterns.append(tag+'/:field')
            elif f.type in ('float', 'double', 'integer', 'bigint'):
                tag += '/{%s.%s.ge}/{%s.%s.lt}' % (table, field, table, field)
                patterns.append(tag)
                patterns.append(tag+'/:field')
            elif f.type.startswith('list:'):
                tag += '/{%s.%s.contains}' % (table, field)
                patterns.append(tag)
                patterns.append(tag+'/:field')
            elif f.type in ('date', 'datetime'):
                tag += '/{%s.%s.year}' % (table, field)
                patterns.append(tag)
                patterns.append(tag+'/:field')
                tag += '/{%s.%s.month}' % (table, field)
                patterns.append(tag)
                patterns.append(tag+'/:field')
                tag += '/{%s.%s.day}' % (table, field)
                patterns.append(tag)
                patterns.append(tag+'/:field')
            if f.type in ('datetime', 'time'):
                tag += '/{%s.%s.hour}' % (table, field)
                patterns.append(tag)
                patterns.append(tag+'/:field')
                tag += '/{%s.%s.minute}' % (table, field)
                patterns.append(tag)
                patterns.append(tag+'/:field')
                tag += '/{%s.%s.second}' % (table, field)
                patterns.append(tag)
                patterns.append(tag+'/:field')
            if depth > 0:
                for f in self.db[table]._referenced_by:
                    tag += '/%s[%s.%s]' % (table, f.tablename, f.name)
                    patterns.append(tag)
                    patterns += self.auto_table(table, base=tag, depth=depth-1)
        return patterns

    def parse(self, patterns, args, vars, queries=None, nested_select=True):
        """
        Example:
            Use as::

                db.define_table('person',Field('name'),Field('info'))
                db.define_table('pet',
                    Field('ownedby',db.person),
                    Field('name'),Field('info')
                )

                @request.restful()
                def index():
                    def GET(*args,**vars):
                        patterns = [
                            "/friends[person]",
                            "/{person.name}/:field",
                            "/{person.name}/pets[pet.ownedby]",
                            "/{person.name}/pets[pet.ownedby]/{pet.name}",
                            "/{person.name}/pets[pet.ownedby]/{pet.name}/:field",
                            ("/dogs[pet]", db.pet.info=='dog'),
                            ("/dogs[pet]/{pet.name.startswith}", db.pet.info=='dog'),
                            ]
                        parser = db.parse_as_rest(patterns,args,vars)
                        if parser.status == 200:
                            return dict(content=parser.response)
                        else:
                            raise HTTP(parser.status,parser.error)

                    def POST(table_name,**vars):
                        if table_name == 'person':
                            return db.person.validate_and_insert(**vars)
                        elif table_name == 'pet':
                            return db.pet.validate_and_insert(**vars)
                        else:
                            raise HTTP(400)
                    return locals()
        """

        if patterns == 'auto':
            patterns = []
            for table in self.db.tables:
                if not table.startswith('auth_'):
                    patterns.append('/%s[%s]' % (table, table))
                    patterns += self.auto_table(table, base='', depth=1)
        else:
            i = 0
            while i < len(patterns):
                pattern = patterns[i]
                if not isinstance(pattern, str):
                    pattern = pattern[0]
                tokens = pattern.split('/')
                if tokens[-1].startswith(':auto') and \
                        re.match(REGEX_SQUARE_BRACKETS, tokens[-1]):
                    new_patterns = self.auto_table(
                        tokens[-1][tokens[-1].find('[')+1:-1],
                        '/'.join(tokens[:-1]))
                    patterns = patterns[:i]+new_patterns+patterns[i+1:]
                    i += len(new_patterns)
                else:
                    i += 1
        if '/'.join(args) == 'patterns':
            return self.db.Row({
                'status': 200, 'pattern': 'list', 'error': None,
                'response': patterns})
        for pattern in patterns:
            basequery, exposedfields = None, []
            if isinstance(pattern, tuple):
                if len(pattern) == 2:
                    pattern, basequery = pattern
                elif len(pattern) > 2:
                    pattern, basequery, exposedfields = pattern[0:3]
            otable = table = None
            if not isinstance(queries, dict):
                dbset = self.db(queries)
                if basequery is not None:
                    dbset = dbset(basequery)
            i = 0
            tags = pattern[1:].split('/')
            if len(tags) != len(args):
                continue
            for tag in tags:
                if re.match(REGEX_SEARCH_PATTERN, tag):
                    tokens = tag[1:-1].split('.')
                    table, field = tokens[0], tokens[1]
                    if not otable or table == otable:
                        if len(tokens) == 2 or tokens[2] == 'eq':
                            query = self.db[table][field] == args[i]
                        elif tokens[2] == 'ne':
                            query = self.db[table][field] != args[i]
                        elif tokens[2] == 'lt':
                            query = self.db[table][field] < args[i]
                        elif tokens[2] == 'gt':
                            query = self.db[table][field] > args[i]
                        elif tokens[2] == 'ge':
                            query = self.db[table][field] >= args[i]
                        elif tokens[2] == 'le':
                            query = self.db[table][field] <= args[i]
                        elif tokens[2] == 'year':
                            query = self.db[table][field].year() == args[i]
                        elif tokens[2] == 'month':
                            query = self.db[table][field].month() == args[i]
                        elif tokens[2] == 'day':
                            query = self.db[table][field].day() == args[i]
                        elif tokens[2] == 'hour':
                            query = self.db[table][field].hour() == args[i]
                        elif tokens[2] == 'minute':
                            query = self.db[table][field].minutes() == args[i]
                        elif tokens[2] == 'second':
                            query = self.db[table][field].seconds() == args[i]
                        elif tokens[2] == 'startswith':
                            query = self.db[table][field].startswith(args[i])
                        elif tokens[2] == 'contains':
                            query = self.db[table][field].contains(args[i])
                        else:
                            raise RuntimeError("invalid pattern: %s" % pattern)
                        if len(tokens) == 4 and tokens[3] == 'not':
                            query = ~query
                        elif len(tokens) >= 4:
                            raise RuntimeError("invalid pattern: %s" % pattern)
                        if not otable and isinstance(queries, dict):
                            dbset = self.db(queries[table])
                            if basequery is not None:
                                dbset = dbset(basequery)
                        dbset = dbset(query)
                    else:
                        raise RuntimeError(
                            "missing relation in pattern: %s" % pattern)
                elif re.match(REGEX_SQUARE_BRACKETS, tag) and \
                        args[i] == tag[:tag.find('[')]:
                    ref = tag[tag.find('[')+1:-1]
                    if '.' in ref and otable:
                        table, field = ref.split('.')
                        selfld = '_id'
                        if self.db[table][field].type.startswith('reference '):
                            refs = [
                                x.name for x in self.db[otable]
                                if x.type == self.db[table][field].type]
                        else:
                            refs = [
                                x.name for x in self.db[table]._referenced_by
                                if x.tablename == otable]
                        if refs:
                            selfld = refs[0]
                        if nested_select:
                            try:
                                dbset = self.db(self.db[table][field].belongs(
                                    dbset._select(self.db[otable][selfld])))
                            except ValueError:
                                return self.db.Row({
                                    'status': 400, 'pattern': pattern,
                                    'error': 'invalid path', 'response': None})
                        else:
                            items = [
                                item.id for item in dbset.select(
                                    self.db[otable][selfld])
                            ]
                            dbset = self.db(
                                self.db[table][field].belongs(items))
                    else:
                        table = ref
                        if not otable and isinstance(queries, dict):
                            dbset = self.db(queries[table])
                        dbset = dbset(self.db[table])
                elif tag == ':field' and table:
                    # print 're3:'+tag
                    field = args[i]
                    if field not in self.db[table]:
                        break
                    # hand-built patterns should respect .readable=False as well
                    if not self.db[table][field].readable:
                        return self.db.Row({
                            'status': 418, 'pattern': pattern,
                            'error': 'I\'m a teapot', 'response': None})
                    try:
                        distinct = vars.get('distinct', False) == 'True'
                        offset = to_num(vars.get('offset', None) or 0)
                        limits = (
                            offset,
                            to_num(vars.get('limit', None) or 1000) + offset)
                    except ValueError:
                        return self.db.Row({
                            'status': 400, 'error': 'invalid limits',
                            'response': None})
                    items = dbset.select(
                        self.db[table][field], distinct=distinct,
                        limitby=limits)
                    if items:
                        return self.db.Row({
                            'status': 200, 'response': items,
                            'pattern': pattern})
                    else:
                        return self.db.Row({
                            'status': 404, 'pattern': pattern,
                            'error': 'no record found', ' response': None})
                elif tag != args[i]:
                    break
                otable = table
                i += 1
                if i == len(tags) and table:
                    if hasattr(self.db[table], '_id'):
                        ofields = vars.get(
                            'order', self.db[table]._id.name).split('|')
                    else:
                        ofields = vars.get(
                            'order', self.db[table]._primarykey[0]).split('|')
                    try:
                        orderby = [
                            self.db[table][f] if not f.startswith('~')
                            else ~self.db[table][f[1:]] for f in ofields]
                    except (KeyError, AttributeError):
                        return self.db.Row({
                            'status': 400, 'error': 'invalid orderby',
                            'response': None})
                    if exposedfields:
                        fields = [
                            field for field in self.db[table]
                            if str(field).split('.')[-1] in exposedfields and
                            field.readable]
                    else:
                        fields = [
                            field for field in self.db[table]
                            if field.readable]
                    count = dbset.count()
                    try:
                        offset = to_num(vars.get('offset', None) or 0)
                        limits = (
                            offset,
                            to_num(vars.get('limit', None) or 1000) + offset)
                    except ValueError:
                        return self.db.Row({
                            'status': 400, 'error': ' invalid limits',
                            'response': None})
                    try:
                        response = dbset.select(
                            limitby=limits, orderby=orderby, *fields)
                    except ValueError:
                        return self.db.Row({
                            'status': 400, 'pattern': pattern,
                            'error': 'invalid path', 'response': None})
                    return self.db.Row({
                        'status': 200, 'response': response,
                        'pattern': pattern, 'count': count})
        return self.db.Row({
            'status': 400, 'error': 'no matching pattern', 'response': None})
