"""
    ***********************************************************
    qal.sql.sql holds the class structure representation of SQL
    ***********************************************************
    .. note:: 
        * The mySQL DDL implementation defaults to using innoDB, since stuff like foreign keys and other very important security features are lacking from myISAM.
        * All ParameterBase descendants property names are named in a specific way, so that one from that name can discern what types are allowed. For example: sources means that it is a list of ParameterSource.
        * Parameter_* means that it is some form of input, Verb_* means that this statement can be executed stand alone.
        * No parameters can be required in either Verb_* or Parameter_* classes. If they are, the classes cannot be inspected by qal.sql.meta.list_class_properties.


    :warning: Changes and new classes must satisfy both the import/export of data structures and schema generation.
    :copyright: Copyright 2010-2014 by Nicklas Boerjesson
    :license: BSD, see LICENSE for details.
"""

from qal.sql.types import DEFAULT_ROWSEP, expression_item_types, tabular_expression_item_types
from qal.dal.types import DB_POSTGRESQL, DB_MYSQL, DB_ORACLE, DB_DB2  # , DB_SQLSERVER

from qal.sql.base import ParameterBase, SqlList, ParameterExpressionItem
from qal.sql.remotable import ParameterRemotable

from qal.sql.utils import add_operator, parenthesise, oracle_add_escape, add_comma, make_operator, \
    check_for_param_content, \
    none_as_sql, error_on_blank, comma_separate, make_function, db_specific_operator, db_specific_object_reference, \
    citate, check_not_null, curr_user, db_specific_datatype, curr_datetime, add_comma_rs, oracle_create_auto_increment, \
    handle_temp_table_ref, datatype_to_parameter
from qal.sql.types import condition_part
from qal.dataset.flatfile import FlatfileDataset
from qal.dataset.xpath import XpathDataset
from qal.dataset.matrix import MatrixDataset
from qal.dal.types import DB_SQLSERVER
from qal.common.strings import make_path_absolute


class ParameterExpression(ParameterExpressionItem):
    """Holds an expression"""

    expressionitems = None

    def __init__(self, _expressionitems=None, _operator=None):
        super(ParameterExpression, self).__init__(_operator)

        if _expressionitems != None:
            self.expressionitems = _expressionitems
        else:
            self.expressionitems = SqlList()

    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""
        result = ''
        # Loop expressions
        for index, item in enumerate(self.expressionitems):
            # Some want || instead of + for concatenation
            if (_db_type == DB_POSTGRESQL or _db_type == DB_DB2):
                result += add_operator(index, make_operator(item.operator, True)) + item.as_sql(_db_type)
            else:
                result += add_operator(index, make_operator(item.operator, False)) + item.as_sql(_db_type)
        if len(self.expressionitems) > 1:
            return parenthesise(result)
        else:
            return result


class ParameterString(ParameterExpressionItem):
    """Holds a string parameter/value"""

    string_value = ''
    escape_character = ''

    def __init__(self, _string_value='', _operator=None, _escape_character=None):
        super(ParameterString, self).__init__(_operator)
        self.string_value = _string_value
        if (_escape_character != None):
            self.escape_character = _escape_character
        else:
            self.escape_character = ''


    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""
        if _db_type != DB_ORACLE:
            return "'" + self.string_value + "'"
        else:
            return oracle_add_escape("'" + self.string_value + "'", self.escape_character)


class ParameterNumeric(ParameterExpressionItem):
    """Holds a numeric parameter/value"""
    numeric_value = ''

    def __init__(self, _numeric_value='', _operator=None):
        super(ParameterNumeric, self).__init__(_operator)
        self.numeric_value = _numeric_value

    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""

        # Check if it is a valid float
        try:
            float(self.numeric_value)
        except ValueError:
            if (str(self.numeric_value).lower() != 'null') and (check_for_param_content(self.numeric_value) == False):
                raise Exception("A numeric parameter must be numeric. Value:" + self.numeric_value)
        return str(self.numeric_value)
        # TODO: Check if any flavors allow other decimal signs.


class ParameterParameter(ParameterExpressionItem):
    """Holds a parameter to be used in prepared statements."""

    datatype = ''

    def __init__(self, _datatype='', _operator=None):
        super(ParameterParameter, self).__init__(_operator)
        self.datatype = _datatype


    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""

        # This is temporary, awaiting proper PyMySQL support for %d.
        return datatype_to_parameter(_db_type, self.datatype)


class ParameterIn(ParameterExpressionItem):
    """Hold an SQL IN-statement"""
    in_values = None

    def __init__(self, _in_values='', _operator=None):
        super(ParameterIn, self).__init__(_operator)
        self.in_values = _in_values

    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""

        # Handle strings separately. Somewhat non-pythonic, but the alternative would be far more complicated.
        if (type(self.in_values) is ParameterString):
            return "IN " + parenthesise(self.in_values.string_value)
        else:
            return "IN " + parenthesise(self.in_values.as_sql(_db_type))


class ParameterDataset(ParameterExpressionItem, ParameterRemotable):
    """Holds a dataset from an external, non-SQL source"""
    data_source = None


    def __init__(self, _data_source=None):


        if (_data_source != None):
            self.data_source = _data_source
        else:
            self.data_source = None

        self.resource_uuid = None

    def dataset_to_sql(self):
        """Generate SQL for specified database engine """

        # TODO: Is this used?
        if (self._dataset.loaded):

            _SQL = ''

            print("in dataset_to_sql" + str(self._dataset))

            return _SQL
        else:
            raise Exception('Dataset_To_SQL: Dataset not loaded.');

    def _generate_sql(self, _db_type):
        if (self.data_source):

            """Handle relative filename"""
            if hasattr(self.data_source, 'filename'):
                self.data_source.filename = self.data_source.filename
                self.data_source._base_path = self._base_path

            self.data_source.load()
            return "(" + self.data_source.as_sql(_db_type) + ")"
        else:
            raise Exception('ParameterDataset.as_sql : data_source not set.');


class ParameterIdentifier(ParameterExpressionItem):
    """Holds an identifier(column-, table or other reference)"""
    identifier = ''
    prefix = ''

    def __init__(self, _identifier='', _operator=None, _prefix=None):
        super(ParameterIdentifier, self).__init__(_operator)
        self.identifier = _identifier
        if _prefix != None:
            self.prefix = _prefix
        else:
            self.prefix = ''


    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""
        # Add prefix if set
        if self.prefix != '':
            _tmp_prefix = self.prefix + '.'
        else:
            _tmp_prefix = ''
        # Add quote if needed
        if _db_type in (DB_POSTGRESQL, DB_DB2, DB_ORACLE):
            return _tmp_prefix + '"' + str(handle_temp_table_ref(self.identifier, _db_type)) + '"'
        else:
            return _tmp_prefix + str(handle_temp_table_ref(self.identifier, _db_type))


class ParameterCast(ParameterExpressionItem):
    """A Cast() converts an expression to a specified datatype.
    Properties:
    * expression is a list of expression items(sql_types.expression_item_types()).
    * datatype is a string containing the datatype(as defined in sql_types.data_types())

    Example: http://192.168.0.210/mediawiki/index.php/Unified_BPM_DatabaseAbstractionLayer#ParameterCast.28ParameterExpressionItem.29"""

    expression = ''
    datatype = ''

    def make_cast(self, _value):
        """Generate the CAST syntax"""
        return str('CAST' + parenthesise(_value + ' AS ' + self.datatype))

    def __init__(self, _expression=None, _datatype=None, _operator=None):
        super(ParameterCast, self).__init__(_operator)
        if _expression != None:
            self.expression = _expression
        else:
            self.expression = SqlList(expression_item_types())

        self.datatype = _datatype

    def _generate_sql(self, _DB_Type):
        """Generate SQL for specified database engine"""
        return self.make_cast(self.expression.as_sql(_DB_Type))


class ParameterFunction(ParameterExpressionItem):
    """Holds an SQL function call"""
    parameters = None
    name = ''

    def __init__(self, _parameters=None, _name='', _operator=None):
        super(ParameterFunction, self).__init__(_operator)

        if _parameters != None:
            self.parameters = _parameters
        else:
            self.parameters = SqlList(expression_item_types())

        self.name = _name


    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""
        result = ''
        for index, item in enumerate(self.parameters):
            result += add_comma(index, item.as_sql(_db_type))
        return make_function(self.name, result)


class ParameterWhen(ParameterBase):
    """Holds a WHEN statement"""
    conditions = None
    result = None

    def __init__(self, _conditions=None, _result=None):
        super(ParameterWhen, self).__init__()
        self.conditions = _conditions
        self.result = _result

    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""
        return 'WHEN ' + self.conditions.as_sql(_db_type) + ' THEN ' + self.result.as_sql(_db_type)


class ParameterCase(ParameterExpressionItem):
    """Holds a CASE statement (see ParameterWhen)"""
    when_statements = None
    else_statement = None

    def __init__(self, _when_statements=None, _else_statement=None, _operator=None):
        super(ParameterCase, self).__init__(_operator)

        if _when_statements != None:
            self.when_statements = _when_statements
        else:
            self.when_statements = SqlList(expression_item_types())

        self.else_statement = _else_statement

    def _generate_sql(self, _DB_Type):
        """Generate SQL for specified database engine"""
        result = 'CASE'
        for item in self.when_statements:
            result += ' ' + item.as_sql(_DB_Type)
        if self.else_statement != None:
            result += ' else_statement ' + self.else_statement.as_sql(_DB_Type)
        result += ' END'
        return result

        return result


class ParameterSet(ParameterBase):
    """This class holds a set.
    In SQL, that means more than one tabular datasets is combined using a set operator like UNION."""
    # expression could be any table-valued expression
    subsets = None
    set_operator = None

    def __init__(self, _subsets=None, _set_operator=None):
        super(ParameterBase, self).__init__()

        # One can either have tabular source data, or an SQL expression 

        if _subsets != None:
            self.subsets = _subsets
        else:
            self.subsets = SqlList(tabular_expression_item_types())
        if _set_operator != None:
            self.set_operator = _set_operator
        else:
            self.set_operator = None

    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""
        _sqls = []
        # Loop all expressions into a list. 
        [_sqls.append(none_as_sql(x, _db_type, '')) for x in self.subsets]
        # Separate them with operators.
        return ('\n' + self.set_operator + '\n').join(_sqls)


class ParameterSource(ParameterBase, ParameterRemotable):
    """This class hold a source of data that can be used with a FROM or JOIN-statement."""
    # expression could be any table-valued expression
    expression = None

    conditions = None
    alias = ''
    join_type = None

    def __init__(self, _expression=None, _conditions=None, _alias='', _join_type=None):
        super(ParameterSource, self).__init__()

        # One can either have tabular source data i union:ed sets, a sub select, an SQL identifier or a noSQL data set. 

        if _expression != None:
            self.expression = _expression
        else:
            self.expression = SqlList(expression_item_types())

        if _conditions != None:
            self.conditions = _conditions
        else:
            self.conditions = ParameterConditions()

        self.alias = _alias

        self.join_type = _join_type
        self.resource_uuid = None

    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""
        _sql = none_as_sql(self.expression, _db_type, '')

        return _sql


class ParameterOrderByItem(ParameterExpression):
    """This class holds an order by-statement"""
    # Sort direction, ASC or DESC
    direction = None

    def __init__(self, _expressionitems=None, _direction=None):
        super(ParameterOrderByItem, self).__init__(_expressionitems)
        self.direction = _direction

    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""
        return super(ParameterOrderByItem, self)._generate_sql(_db_type) + " " + self.direction


class VerbSelect(ParameterExpressionItem, ParameterRemotable):
    """This class holds a SELECT statement. """
    fields = None
    sources = None
    order_by = None
    top_limit = None

    # Internal variables
    _post_verb = ''
    _post_sql = ''

    # TODO: Implement GROUP BY and HAVING-support.
    # GROUP_BY = ''
    #HAVING = ''

    def __init__(self, _fields=None, _sources=None, _operator=None, _order_by=None):
        super(VerbSelect, self).__init__(_operator)

        if _fields != None:
            self.fields = _fields
        else:
            self.fields = SqlList("ParameterIdentifier")

        if _sources != None:
            self.sources = _sources
        else:
            self.sources = SqlList("ParameterSource")

        if _order_by != None:
            self.order_by = _order_by
        else:
            self.order_by = SqlList("ParameterExpressionItem")

        self.top_limit = None
        self.resource_uuid = None

    def add_limit(self, _db_type):
        """Generate SQL for specified database engine for limits on number of rows (TOP/LIMIT/FETCH FIRST)"""
        if self.top_limit != None and int(self.top_limit) > 0:
            if (_db_type in [DB_MYSQL, DB_POSTGRESQL]):
                self._post_sql = 'LIMIT ' + str(int(self.top_limit))
            elif (_db_type in [DB_DB2]):
                self._post_sql = 'FETCH FIRST ' + str(int(self.top_limit)) + ' ROWS ONLY '
                # Oracles' solution is not handled here, but in the as_sql-WHERE handling.
            elif (_db_type != DB_ORACLE):
                self._post_verb = 'TOP ' + str(int(self.top_limit)) + ' '


    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""

        if len(self.sources) > 0:
            self.add_limit(_db_type)

        # Add select and its fields
        result = 'SELECT ' + self._post_verb
        if (self.fields != None) and (len(self.fields) > 0):
            for index, item in enumerate(self.fields):
                result += add_comma(index, item.as_sql(_db_type))
        else:
            result += '*'


        # Add FROM
        if len(self.sources) > 0:
            result += ' FROM '
            result += self.sources[0].as_sql(_db_type)
            if self.sources[0].alias != None and self.sources[0].alias != '':
                if (_db_type != DB_ORACLE):
                    result += ' AS ' + self.sources[0].alias
                else:
                    result += ' ' + self.sources[0].alias

        # DB2 needs a dummy FROM, even if there is no source table. 
        elif (_db_type == DB_DB2):
            result += ' FROM sysibm.sysdummy1 '
        # Oracle, too, needs a dummy FROM, even if there is no source table. 
        elif (_db_type == DB_ORACLE):
            result += ' FROM dual '

        # Add joins
        if len(self.sources) > 1:
            for index, item in enumerate(self.sources):

                if index > 0:
                    """As as_sql is not called, as_sql has to be called explicitly."""
                    item.as_sql(_db_type)
                    if item.join_type:
                        result += ' ' + item.join_type
                    result += ' JOIN ' + none_as_sql(item.expression, _db_type,
                                                     _error='VerbSelect: Joins must contain a statement or a reference to a table.')
                    result += ' AS ' + error_on_blank(item.alias, 'VerbSelect: Joins must have aliases.')
                    if item.join_type != "CROSS":
                        result += ' ON ' + none_as_sql(item.conditions, _db_type,
                                                       _error='VerbSelect: Joins must have conditions.')

        # Add WHERE 
        if len(self.sources) > 0:
            _num_conds = len(self.sources[0].conditions)
            if _num_conds > 0:
                result += ' WHERE ' + self.sources[0].conditions.as_sql(_db_type)

            # Add Oracles TOP/LIMIT solution
            # TODO: Check out: Would this ever be applicable without sources?
            if (_db_type == DB_ORACLE):
                _num_conds = len(self.sources[0].conditions)
                if _num_conds > 0:
                    result += ' AND '
                else:
                    result += ' WHERE '
                #This is a *very* ugly solution, but the only way I could find out.
                if (self.top_limit > 0):
                    result += '(ROWNUM < ' + str(int(self.top_limit) + 1) + ')'

        if len(self.order_by) > 0:
            orderresult = ''
            for currItem in self.order_by:
                if orderresult == '':
                    orderresult = ' ORDER BY '
                else:
                    orderresult += ', '

                orderresult += currItem.as_sql(_db_type)
            result += orderresult

        if self._post_sql != '':
            result += DEFAULT_ROWSEP + self._post_sql

        self._post_sql = ''
        self._post_verb = ''

        return result

    def append_field_identifier(self, _identifier):
        """Helper to append field identifier classes to field list only using names"""
        _ident = ParameterIdentifier(_identifier)

        self.fields.append(_ident)


class ParameterCondition(ParameterBase):
    """This class holds a condition, that is a comparison (IF A=B)"""
    left = None
    right = None
    operator = ''
    and_or = ''

    def __init__(self, _left=None, _right=None, _operator='', _and_or=''):
        super(ParameterCondition, self).__init__()
        if _left != None:
            self.left = _left
        else:
            self.left = SqlList(condition_part())
        if _right != None:
            self.right = _right
        else:
            self.right = SqlList(condition_part())

        self.operator = _operator
        self.and_or = _and_or

    def as_sql(self, _db_type, _index=0):
        """Generate SQL for specified database engine (index if for handling when it is the first in a list of conditions)"""
        """Note that the ParameterBase.as_sql is not called, this is because 1) Conditions are in context 2) Needs _index argument"""
        # TODO: Handle ILIKE for PostgreSQL.
        if (_index != 0):
            error_on_blank(self.and_or,
                           'ParameterCondition: Conditions must be separated by a logical operator. "and_or" is not set.')
            _result = ' ' + self.and_or + ' '

        else:
            _result = ''

        _result += parenthesise(
            self.left.as_sql(_db_type) + ' ' + db_specific_operator(self.operator, _db_type) + ' ' + self.right.as_sql(
                _db_type))
        return _result


class ParameterAssignment(ParameterBase):
    """This class holds a assignment, that is assign the result of the expression to the right to the identifier on the left."""
    left = None
    right = None


    def __init__(self, _left=None, _right=None):
        super(ParameterAssignment, self).__init__()
        if _left != None:
            self.left = _left
        else:
            self.left = None
        if _right != None:
            self.right = _right
        else:
            self.right = None

    def as_sql(self, _db_type):
        """Generate SQL for specified database engine"""
        # TODO: Handle ILIKE for PostgreSQL.

        _result = self.left.as_sql(_db_type) + ' = ' + self.right.as_sql(_db_type)
        return _result


class ParameterConditions(SqlList):
    """This class holds a list of condition or list of conditions ((A=B AND C=D) OR (E=F))"""

    def __init__(self):
        super(ParameterConditions, self).__init__(["ParameterCondition", "ParameterConditions"])


    def get_first_and_or(self):
        """Return the first and/or to know if it should add its own
        (If it is the first condition there is no point in adding its or, but rather the parent conditions' operator."""
        if (len(self)) > 0:
            _first_item = self[0]
            if (hasattr(_first_item, "get_first_and_or")):
                return _first_item.get_first_and_or()
            else:
                return ' ' + _first_item.and_or + ' '
        else:
            raise "ParameterConditions: Invalid structure - Cannot get and_or operator from empty list of conditions."


    def as_sql(self, _db_type, _parent_index=0):
        """Generate SQL for specified database engine"""
        _result = ''

        for _index, _item in enumerate(self):
            _result += _item.as_sql(_db_type, _index)

        _result = parenthesise(_result)

        if (_parent_index != 0):
            _result = self.get_first_and_or() + _result

        return _result


class ParameterField(ParameterBase):
    """Holds a field definition (SELECT _FIELD1 AS _FIELD) """
    expression = None
    alias = ''

    def __init__(self, _expression=None, _alias=''):
        super(ParameterField, self).__init__()
        if _expression == None:
            self.expression = SqlList(expression_item_types())
        else:
            self.expression = _expression
        self.alias = _alias

    def _generate_sql(self, _DB_Type):
        """Generate SQL for specified database engine"""
        if self.alias != '':
            return self.expression.as_sql(_DB_Type) + ' AS ' + self.alias
        else:
            return self.expression.as_sql(_DB_Type)


class ParameterConstraint(ParameterBase):
    """Hold a key constraint declaration"""
    name = ''
    constraint_type = None
    references = None  # First key is considered local in a FK.
    checkconditions = None

    def __init__(self, _name='', _constraint_type=None, _references=None, _checkconditions=None):
        super(ParameterConstraint, self).__init__()
        self.name = _name
        self.constraint_type = _constraint_type
        if _references != None:
            self.references = _references
        else:
            self.references = SqlList()

        if _checkconditions != None:
            self.checkconditions = _checkconditions
        else:
            self.checkconditions = SqlList(["ParameterCondition", "ParameterConditions"])


    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""
        result = 'CONSTRAINT ' + db_specific_object_reference(self.name, _db_type) + ' ' + self.constraint_type
        if (self.constraint_type == "CHECK"):
            result += ' ' + self.checkconditions.as_sql(_db_type)
        if (self.constraint_type == "FOREIGN KEY"):
            result += ' ' + parenthesise(self.references[0].as_sql(_db_type)) + ' REFERENCES ' + citate(
                self.references[1].identifier, _db_type) + parenthesise(self.references[2].as_sql(_db_type))
        if (self.constraint_type == "PRIMARY KEY"):
            result += ' ' + parenthesise(comma_separate(self.references, _db_type))
        if (self.constraint_type == "UNIQUE"):
            result += ' ' + parenthesise(comma_separate(self.references, _db_type))
        if (self.constraint_type == "DEFAULT"):
            result += ' ' + parenthesise(self.references[0].as_sql(_db_type))
        return result


class VerbCreateIndex(ParameterBase):
    """Holds a statement for creating database indices"""
    name = ''
    index_type = None
    tablename = ''
    columnnames = None


    def __init__(self, _name='', _index_type=None, _tablename='', _columnnames=None):
        super(VerbCreateIndex, self).__init__()
        self.name = _name
        self.index_type = _index_type
        self.tablename = _tablename
        if _columnnames != None:
            self.columnnames = _columnnames
        else:
            self.columnnames = SqlList('string')

    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""
        check_not_null("VerbCreateIndex",
                       [[self.name, "name"], [self.index_type, "index_type"], [self.tablename, "tablename"]])
        # Handle DB2s strange deviation #1
        if (_db_type == DB_DB2 and (self.index_type == "CLUSTERED" or self.index_type == "NONCLUSTERED")):
            result = 'CREATE INDEX ' + db_specific_object_reference(self.name, _db_type) + chr(13)
        else:
            result = 'CREATE ' + self.index_type + ' INDEX ' + db_specific_object_reference(self.name, _db_type) + chr(
                13)

        result += 'ON ' + self.tablename + '('
        for index, item in enumerate(self.columnnames):
            result += add_comma(index, db_specific_object_reference(item, _db_type))
        result += ')'
        # Handle DB2s strange deviation #2
        if (_db_type == DB_DB2 and self.index_type == "CLUSTERED"):
            result += chr(13) + 'CLUSTER'
        return result


class ParameterColumndefinition(ParameterBase):
    """Holds a physical table column definition (not to confused with ParameterField which is a reference to one) """
    name = ''
    datatype = ''
    notnull = None
    default = ''

    def __init__(self, _name='', _datatype='', _notnull=None, _default=''):
        super(ParameterColumndefinition, self).__init__()
        self.name = _name
        self.datatype = _datatype
        self.notnull = _notnull
        self.default = _default


    def _generate_sql(self, _db_type, _mysql_pk=False):
        """Generate SQL for specified database engine"""
        result = db_specific_object_reference(self.name, _db_type) + ' ' + db_specific_datatype(self.datatype, _db_type)
        if (_db_type == DB_MYSQL and _mysql_pk == True and self.datatype.lower() == 'serial'):
            result += ' PRIMARY KEY'

        if (self.notnull == True and _db_type != DB_ORACLE ):
            result += ' NOT NULL'

        if (self.default != ''):

            tmp_default = self.default
            tmp_default = tmp_default.replace('::curruser::', curr_user(_db_type))
            tmp_default = tmp_default.replace('::currdatetime::', curr_datetime(_db_type))
            if _db_type not in (DB_MYSQL, DB_DB2):
                result += ' DEFAULT ' + parenthesise(tmp_default)
            else:
                result += ' DEFAULT ' + tmp_default

        if (self.notnull == True and _db_type == DB_ORACLE ):
            result += ' NOT NULL'

        if (self.notnull == False):
            result += ' NULL'

        return result


class ParameterDDL(ParameterBase):
    """Parent class for SQL DDL(Data Definition Language) statement classes"""
    _post_sql = ''

    def __init__(self, _operator=None):
        super(ParameterDDL, self).__init__()
        self._post_sql = ''


class VerbCreateTable(ParameterDDL):
    """Holds a CREATE TABLE statement"""
    name = ''
    columns = None
    constraints = None
    _post_statements = None

    def __init__(self, _name=None, _columns=None, _constraints=None):
        super(VerbCreateTable, self).__init__()
        if _name != None:
            self.name = _name
        else:
            self.name = ''

        if _columns != None:
            self.columns = _columns
        else:
            self.columns = SqlList("ParameterColumndefinition")

        if _constraints != None:
            self.constraints = _constraints
        else:
            self.constraints = SqlList(["ParameterConstraint", "ParameterConstraints"])

        self._post_statements = list()


    def get_post_statements(self):
        """Return post statements. That is statements run after the main SQL"""
        return self._post_statements

    def make_columns(self, _db_type):
        """Generate DDL for all columns"""
        result = ''
        for index, item in enumerate(self.columns):
            if (item.datatype.lower() == 'serial'):
                if (_db_type == DB_ORACLE):
                    self._post_statements.append(oracle_create_auto_increment(self, item))
                if (_db_type == DB_MYSQL and len(
                        self.constraints) == 0):  # TODO: This might have to really look for primary key constraints to be safe.
                    result += add_comma_rs(index, item._generate_sql(_db_type, True), self._row_separator)
                else:
                    result += add_comma_rs(index, item.as_sql(_db_type), self._row_separator)
            else:
                result += add_comma_rs(index, item.as_sql(_db_type), self._row_separator)
        return result

    def make_constraints(self, _db_type):
        """Generate SQL for all constraints"""
        result = ''
        for index, item in enumerate(self.constraints):
            result += add_comma_rs(index, item.as_sql(_db_type), self._row_separator)
        return result


    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""
        self._post_statements = []
        if self.name[0] == "#":
            if _db_type in [DB_POSTGRESQL, DB_MYSQL]:
                _result = "CREATE TEMPORARY TABLE "
            elif _db_type == [DB_DB2]:
                _result = "DECLARE GLOBAL TEMPORARY TABLE "
            elif _db_type == [DB_ORACLE]:
                _result = "CREATE GLOBAL TEMPORARY TABLE "
            else:
                _result = "CREATE TABLE "

            _result += citate(self.name[1:len(self.name)], _db_type) + " (" + self._row_separator
        else:
            _result = "CREATE TABLE "
            _result += citate(self.name, _db_type) + " (" + self._row_separator

        _result += self.make_columns(_db_type)
        if (len(self.constraints) > 0):
            _result += ',' + self._row_separator + self.make_constraints(_db_type)
        _result += self._row_separator + ')'

        if _db_type == DB_MYSQL:
            _result += ' ENGINE=InnoDB'

        return _result


class VerbInsert(ParameterBase):
    """This class holds an INSERT statement"""

    destination_identifier = None
    column_identifiers = None
    data = None

    def __init__(self, _destination_identifier=None, _column_identifiers=None, _select=None):
        super(VerbInsert, self).__init__()
        if _destination_identifier != None:
            self.destination_identifier = _destination_identifier
        else:
            self.destination_identifier = None

        if _column_identifiers != None:
            self.column_identifiers = _column_identifiers
        else:
            self.column_identifiers = SqlList("ParameterIdentifier")

        if _select != None:
            self.data = _select
        else:
            self.data = None

    def makeIdentifiers(self, _db_type):
        """Generate SQL for the identifiers"""
        result = ''
        for currIndex, currIdent in enumerate(self.column_identifiers):
            if currIndex > 0:
                result = result + ', '
            result = result + currIdent.as_sql(_db_type)
        return result

    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""

        if len(self.column_identifiers) > 0:
            result = 'INSERT INTO ' + self.destination_identifier.as_sql(_db_type) + ' (' + self.makeIdentifiers(
                _db_type) + ')' + DEFAULT_ROWSEP
            # Add data and remove parenthesis. Removing them here could certainly be seen as ugly but the reason 
            # is that MySQL cannot handle them. It would break everything else any other way.
            if self.data:
                tmpSQL = self.data.as_sql(_db_type)
                if isinstance(self.data, ParameterSet):
                    result = result + tmpSQL
                else:
                    result = result + tmpSQL.lstrip('(').rstrip(')')
        else:
            raise Exception('VerbInsert.as_sql: No column_identifiers specified!')

        return result


class VerbUpdate(ParameterBase):
    """This class holds an INSERT statement"""

    table_identifier = None
    assignments = None
    conditions = None

    def __init__(self, _table_identifier=None, _assignments=None, _conditions=None):
        super(VerbUpdate, self).__init__()
        if _table_identifier != None:
            self.table_identifier = _table_identifier
        else:
            self.table_identifier = None

        if _assignments != None:
            self.assignments = _assignments
        else:
            self.assignments = SqlList("ParameterAssignment")

        if _conditions != None:
            self.conditions = _conditions
        else:
            self.conditions = ParameterConditions()

    def _generate_assignments_sql(self, _db_type):
        _results = []
        for _curr_assignment in self.assignments:
            _results.append(_curr_assignment.as_sql(_db_type))

        return ", ".join(_results)

    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""

        if len(self.assignments) > 0:
            _result = 'UPDATE ' + self.table_identifier.as_sql(_db_type) + DEFAULT_ROWSEP
            _result += 'SET' + DEFAULT_ROWSEP + self._generate_assignments_sql(_db_type) + DEFAULT_ROWSEP
            _result += 'WHERE ' + self.conditions.as_sql(_db_type)
        else:
            raise Exception('.as_sql(_db_type) + DEFAULT_ROWSEP.as_sql: No column_identifiers specified!')

        return _result


class VerbDropTable(ParameterBase):
    """This class holds a DELETE statement"""
    name = None
    """
       The name of the table to be dropped.
    """


    def __init__(self, _name=None):
        super(VerbDropTable, self).__init__()

        if _name != None:
            self.name = _name
        else:
            self.name = None


    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""
        return 'DROP TABLE ' + citate(self.name, _db_type)


class VerbDelete(ParameterBase):
    """This class holds a DELETE statement"""
    sources = None
    """
       @warning: Important! The first source must have a ParameterIdentifier as expression that specifies the target table.
    """


    def __init__(self, _sources=None, _operator=None):
        super(VerbDelete, self).__init__()

        if _sources != None:
            self.sources = _sources
        else:
            self.sources = SqlList("ParameterSource")


    def _generate_sql(self, _db_type):
        """Generate SQL for specified database engine"""

        result = ''
        # Add FROM
        if len(self.sources) > 0:
            # Extract target table from expression in first source.
            if (len(self.sources[0].expression) > 0 and
                    isinstance(self.sources[0].expression[0], ParameterIdentifier)):
                if (_db_type == DB_POSTGRESQL):
                    result += 'DELETE FROM ' + self.sources[0].expression[0].as_sql(_db_type) + ' ' + self.sources[
                        0].alias + DEFAULT_ROWSEP
                else:
                    result += 'DELETE ' + self.sources[0].expression[0].as_sql(_db_type) + DEFAULT_ROWSEP

            else:
                raise Exception("Error from VerbDelete.as_sql: Could not find identifier in first source expression.")

            if (_db_type != DB_POSTGRESQL):
                result += 'FROM  '
                result += self.sources[0].as_sql(_db_type)
                if self.sources[0].alias != '':
                    if (_db_type != DB_ORACLE):
                        result += ' AS ' + self.sources[0].alias
                    else:
                        result += ' ' + self.sources[0].alias

        # Add JOINS/USING
        if len(self.sources) > 1:
            for index, item in enumerate(self.sources):
                if index > 0:
                    if (_db_type != DB_POSTGRESQL):
                        result += ' JOIN ' + none_as_sql(item.expression, _db_type,
                                                         _error='VerbDelete: Joins must contain a statement or a reference to a table.')
                    else:
                        result += ' USING ' + none_as_sql(item.expression, _db_type,
                                                          _error='VerbDelete(for Postgresql): Joins must contain a statement or a reference to a table.')
                    result += ' AS ' + error_on_blank(item.alias, 'VerbDelete: Joins must have aliases.')
                    if (_db_type != DB_POSTGRESQL):
                        result += ' ON ' + none_as_sql(item.conditions, _db_type,
                                                       _error='VerbDelete: Joins must have conditions.')

        # Add WHERE 
        if len(self.sources) > 0:
            _num_conds = len(self.sources[0].conditions)
            if (_db_type == DB_POSTGRESQL) and (len(self.sources) > 1):
                _num_conds += len(self.sources[1].conditions)
            if (_num_conds > 0):
                result += ' WHERE '
                if len(self.sources[0].conditions) > 0:
                    result += self.sources[0].conditions.as_sql(_db_type)
                if (_db_type == DB_POSTGRESQL) and (len(self.sources) > 1) and (len(self.sources[1].conditions) > 0):
                    result += ' ' + self.sources[1].conditions.as_sql(_db_type)
                    if len(self.sources) > 2:
                        raise Exception(
                            'VerbDelete: To be able to generalize functionality, only one join is allowed due to Postgresql proprietary DELETE FROM .. USING syntax.')

        return result

    def append_field_identifier(self, _identifier):
        """Helper function to add ParameterIdentifier instances using just the field names"""
        _ident = ParameterIdentifier(_identifier)

        self.fields.append(_ident)


class VerbCustom(ParameterDDL):
    """This class holds custom statements (written, not generated) for all platforms.
    This is for when what is currenctly implementet do not suffice"""

    sql_mysql = ''
    sql_postgresql = ''
    sql_oracle = ''
    sql_db2 = ''
    sql_sqlserver = ''

    def _generate_sql(self, _db_type):
        """Return the specified SQLs for each database engine"""
        return [self.sql_mysql, self.sql_postgresql, self.sql_oracle, self.sql_db2, self.sql_sqlserver][_db_type]

    def __init__(self):
        super(VerbCustom, self).__init__()
        self.sql_mysql = ''
        self.sql_postgresql = ''
        self.sql_oracle = ''
        self.sql_db2 = ''
        self.sql_sqlserver = ''
    
        