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}
        )
