# -*- coding: utf-8 -*-

import re
from itertools import chain
from collections import OrderedDict
from .expression import QueryExpression
from .join_clause import JoinClause
from ..pagination import Paginator, LengthAwarePaginator
from ..utils import basestring, Null
from ..exceptions import ArgumentError


class QueryBuilder(object):

    _operators = [
        '=', '<', '>', '<=', '>=', '<>', '!=',
        'like', 'like binary', 'not like', 'between', 'ilike',
        '&', '|', '^', '<<', '>>',
        'rlike', 'regexp', 'not regexp',
        '~', '~*', '!~', '!~*', 'similar to',
        'not similar to',
    ]

    def __init__(self, connection, grammar, processor):
        """
        Constructor

        :param connection: A Connection instance
        :type connection: Connection

        :param grammar: A QueryGrammar instance
        :type grammar: QueryGrammar

        :param processor: A QueryProcessor instance
        :type processor: QueryProcessor
        """
        self._grammar = grammar
        self._processor = processor
        self._connection = connection
        self._bindings = OrderedDict()
        for type in ['select', 'join', 'where', 'having', 'order']:
            self._bindings[type] = []

        self.aggregate_ = None
        self.columns = []
        self.distinct_ = False
        self.from__ = ''
        self.joins = []
        self.wheres = []
        self.groups = []
        self.havings = []
        self.orders = []
        self.limit_ = None
        self.offset_ = None
        self.unions = []
        self.union_limit = None
        self.union_offset = None
        self.union_orders = []
        self.lock_ = None

        self._backups = {}

        self._use_write_connection = False

    def select(self, *columns):
        """
        Set the columns to be selected

        :param columns: The columns to be selected
        :type columns: tuple

        :return: The current QueryBuilder instance
        :rtype: QueryBuilder
        """
        if not columns:
            columns = ['*']

        self.columns = columns

        return self

    def select_raw(self, expression, bindings=None):
        """
        Add a new raw select expression to the query

        :param expression: The raw expression
        :type expression: str

        :param bindings: The expression bindings
        :type bindings: list

        :return: The current QueryBuilder instance
        :rtype: QueryBuilder
        """
        self.add_select(QueryExpression(expression))

        if bindings:
            self.add_binding(bindings, 'select')

        return self

    def select_sub(self, query, as_):
        """
        Add a subselect expression to the query

        :param query: A QueryBuilder instance
        :type query: QueryBuilder

        :param as_: The subselect alias
        :type as_: str

        :return: The current QueryBuilder instance
        :rtype: QueryBuilder
        """
        if isinstance(query, QueryBuilder):
            bindings = query.get_bindings()

            query = query.to_sql()
        elif isinstance(query, basestring):
            bindings = []
        else:
            raise ArgumentError('Invalid subselect')

        return self.select_raw('(%s) AS %s' % (query, self._grammar.wrap(as_)), bindings)

    def add_select(self, *column):
        """
        Add a new select column to query

        :param column: The column to add
        :type column: str

        :return: The current QueryBuilder instance
        :rtype: QueryBuilder
        """
        if not column:
            column = []

        self.columns += column

        return self

    def distinct(self):
        """
        Force the query to return only distinct result

        :return: The current QueryBuilder instance
        :rtype: QueryBuilder
        """
        self.distinct_ = True

        return self

    def from_(self, table):
        """
        Set the query target table

        :param table: The table to target
        :type table: str

        :return: The current QueryBuilder instance
        :rtype: QueryBuilder
        """
        self.from__ = table

        return self

    def join(self, table, one=None,
             operator=None, two=None, type='inner', where=False):
        """
        Add a join clause to the query

        :param table: The table to join with, can also be a JoinClause instance