from copy import deepcopy

from django.db import models
from django.db.models.sql.expressions import SQLEvaluator
from django.utils import tree

class FExpression(object):
    '''
    FExpression is the simplest way to reach the code path we need
    and should not inherit from tree.Node or any subclass thereof.

    FExpression currently only supports the integer comparison.
    Partial string comparison and such do not work because of the
    way these are defined in connection.operators. For those you
    should use the regular filter methods django provides.
    '''
    def __init__(self, left_expression, lookup_type, right_expression):
        self.left_expression, self.lookup_type, self.right_expression = \
                left_expression, lookup_type, right_expression

    def as_sql(self, qn, connection):
        cast_sql = connection.ops.lookup_cast(self.lookup_type)
        field = models.Field()
        # expression is either a smart expression or a user parameter
        if hasattr(self.left_expression, 'as_sql'):
            left_sql, left_params = self.left_expression.as_sql(qn, connection)
            left_sql = cast_sql % left_sql
        else:
            left_sql, left_params = cast_sql, field.get_db_prep_lookup(self.lookup_type, self.left_expression, connection=connection)
        # right hand side is cast by the operator sql
        if hasattr(self.right_expression, 'as_sql'):
            right_sql, right_params = self.right_expression.as_sql(qn, connection)
        else:
            right_sql, right_params = '%s', field.get_db_prep_lookup(self.lookup_type, self.right_expression, connection=connection)

        format = '%%s %s' % connection.operators[self.lookup_type]
        return format % (left_sql, right_sql), list(left_params) + list(right_params)


class FNode(tree.Node):
    '''
    FNode inherits tree.Node to pass tests for the code path we need
    This allows FNode to work without using complex_filter
    '''
    def __init__(self, left_expression, lookup_type, right_expression):
        if not lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
            raise ValueError('Invalid operator, operator %r is not supported.' % lookup_type)
        super(FNode, self).__init__()
        self.left_expression, self.lookup_type, self.right_expression = \
                left_expression, lookup_type, right_expression

    def __deepcopy__(self, memodict):
        obj = super(FNode, self).__deepcopy__(memodict)
        obj.left_expression = deepcopy(self.left_expression, memodict)
        obj.right_expression = deepcopy(self.right_expression, memodict)
        obj.lookup_type = self.lookup_type
        return obj

    def add_to_query(self, query, aliases):
        # evaluate if it is a query expression
        if hasattr(self.left_expression, 'evaluate'):
            self.left_expression = SQLEvaluator(self.left_expression, query)
        if hasattr(self.right_expression, 'evaluate'):
            self.right_expression = SQLEvaluator(self.right_expression, query)
        query.where.add(FExpression(self.left_expression, self.lookup_type, self.right_expression), self.connector)


def FQ(*args, **kwargs):
    '''
    Wrap FNode in a Q object for it's logical operators
    '''
    return models.Q(FNode(*args, **kwargs))