'use strict';

/* jshint -W110 */
var Utils = require('../../utils')
  , DataTypes = require('../../data-types')
  , Transaction = require('../../transaction')
  , _ = require('lodash');

var MySqlQueryGenerator = Utils._.extend(
  Utils._.clone(require('../abstract/query-generator')),
  Utils._.clone(require('../mysql/query-generator'))
);

var QueryGenerator = {
  options: {},
  dialect: 'sqlite',

  createSchema: function() {
    var query = "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
    return Utils._.template(query)({});
  },

  showSchemasQuery: function() {
    return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
  },

  versionQuery: function() {
    return 'SELECT sqlite_version() as `version`';
  },

  createTableQuery: function(tableName, attributes, options) {
    options = options || {};

    var query = 'CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>)'
      , primaryKeys = []
      , needsMultiplePrimaryKeys = (Utils._.values(attributes).filter(function(definition) {
          return Utils._.includes(definition, 'PRIMARY KEY');
        }).length > 1)
      , attrStr = [];

    for (var attr in attributes) {
      if (attributes.hasOwnProperty(attr)) {
        var dataType = attributes[attr];
        var containsAutoIncrement = Utils._.includes(dataType, 'AUTOINCREMENT');

        if (containsAutoIncrement) {
          dataType = dataType.replace(/BIGINT/, 'INTEGER');
        }

        var dataTypeString = dataType;
        if (Utils._.includes(dataType, 'PRIMARY KEY')) {
          if (Utils._.includes(dataType, 'INTEGER')) { // Only INTEGER is allowed for primary key, see https://github.com/sequelize/sequelize/issues/969 (no lenght, unsigned etc)
            dataTypeString = containsAutoIncrement ? 'INTEGER PRIMARY KEY AUTOINCREMENT' : 'INTEGER PRIMARY KEY';
          }

          if (needsMultiplePrimaryKeys) {
            primaryKeys.push(attr);
            dataTypeString = dataType.replace(/PRIMARY KEY/, 'NOT NULL');
          }
        }
        attrStr.push(this.quoteIdentifier(attr) + ' ' + dataTypeString);
      }
    }

    var values = {
      table: this.quoteTable(tableName),
      attributes: attrStr.join(', '),
      charset: (options.charset ? 'DEFAULT CHARSET=' + options.charset : '')
    }
    , pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk); }.bind(this)).join(', ');

    if (!!options.uniqueKeys) {
      Utils._.each(options.uniqueKeys, function(columns) {
        if (!columns.singleField) { // If it's a single field its handled in column def, not as an index
          values.attributes += ', UNIQUE (' + columns.fields.join(', ') + ')';
        }
      });
    }

    if (pkString.length > 0) {
      values.attributes += ', PRIMARY KEY (' + pkString + ')';
    }

    var sql = Utils._.template(query)(values).trim() + ';';
    return this.replaceBooleanDefaults(sql);
  },

  booleanValue: function(value){
    return !!value ? 1 : 0;
  },

  addLimitAndOffset: function(options){
    var fragment = '';
    if (options.offset && !options.limit) {
      fragment += ' LIMIT ' + options.offset + ', ' + 10000000000000;
    } else if (options.limit) {
      if (options.offset) {
        fragment += ' LIMIT ' + options.offset + ', ' + options.limit;
      } else {
        fragment += ' LIMIT ' + options.limit;
      }
    }

    return fragment;
  },

  addColumnQuery: function(table, key, dataType) {
    var query = 'ALTER TABLE <%= table %> ADD <%= attribute %>;'
      , attributes = {};

    attributes[key] = dataType;
    var fields = this.attributesToSQL(attributes, {
      context: 'addColumn'
    });
    var attribute = Utils._.template('<%= key %> <%= definition %>')({
        key: this.quoteIdentifier(key),
        definition: fields[key]
      });

    var sql =  Utils._.template(query)({
      table: this.quoteTable(table),
      attribute: attribute
    });

    return this.replaceBooleanDefaults(sql);
  },

  showTablesQuery: function() {
    return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
  },

  upsertQuery: function (tableName, insertValues, updateValues, where, rawAttributes, options) {
    options.ignore = true;
    var sql = this.insertQuery(tableName, insertValues, rawAttributes, options) + ' ' + this.updateQuery(tableName, updateValues, where, options, rawAttributes);

    return sql;
  },

  bulkInsertQuery: function(tableName, attrValueHashes, options) {
    var query = 'INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>;'
      , tuples = []
      , allAttributes = [];

    Utils._.forEach(attrValueHashes, function(attrValueHash) {
      Utils._.forOwn(attrValueHash, function(value, key) {
        if (allAttributes.indexOf(key) === -1) allAttributes.push(key);
      });
    });

    Utils._.forEach(attrValueHashes, function(attrValueHash) {
      tuples.push('(' +
        allAttributes.map(function (key) {
          return this.escape(attrValueHash[key]);
        }.bind(this)).join(',') +
      ')');
    }.bind(this));

    var replacements  = {
      ignoreDuplicates: options && options.ignoreDuplicates ? ' OR IGNORE' : '',
      table: this.quoteTable(tableName),
      attributes: allAttributes.map(function(attr) {
                    return this.quoteIdentifier(attr);
                  }.bind(this)).join(','),
      tuples: tuples
    };

    return Utils._.template(query)(replacements);
  },

  updateQuery: function(tableName, attrValueHash, where, options) {
    options = options || {};
    _.defaults(options, this.options);

    attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options);

    var query  = 'UPDATE <%= table %> SET <%= values %> <%= where %>'
      , values = [];

    for (var key in attrValueHash) {
      var value = attrValueHash[key];
      values.push(this.quoteIdentifier(key) + '=' + this.escape(value));
    }

    var replacements = {
      table: this.quoteTable(tableName),
      values: values.join(','),
      where: this.whereQuery(where)
    };

    return Utils._.template(query)(replacements).trim();
  },

  deleteQuery: function(tableName, where, options) {
    options = options || {};

    var query = 'DELETE FROM <%= table %><%= where %>';
    var replacements = {
      table: this.quoteTable(tableName),
      where: this.getWhereConditions(where)
    };

    if (replacements.where) {
      replacements.where = ' WHERE ' + replacements.where;
    }

    return Utils._.template(query)(replacements);
  },

  attributesToSQL: function(attributes) {
    var result = {};

    for (var name in attributes) {
      var dataType = attributes[name];
      var fieldName = dataType.field || name;

      if (Utils._.isObject(dataType)) {
        var template     = '<%= type %>'
          , replacements = { type: dataType.type };

        if (dataType.type instanceof DataTypes.ENUM) {
          replacements.type = 'TEXT';

          if (!(Array.isArray(dataType.values) && (dataType.values.length > 0))) {
            throw new Error("Values for ENUM haven't been defined.");
          }
        }

        if (dataType.hasOwnProperty('allowNull') && !dataType.allowNull) {
          template += ' NOT NULL';
        }

        if (Utils.defaultValueSchemable(dataType.defaultValue)) {
          // TODO thoroughly check that DataTypes.NOW will properly
          // get populated on all databases as DEFAULT value
          // i.e. mysql requires: DEFAULT CURRENT_TIMESTAMP
          template += ' DEFAULT <%= defaultValue %>';
          replacements.defaultValue = this.escape(dataType.defaultValue);
        }

        if (dataType.unique === true) {
          template += ' UNIQUE';
        }

        if (dataType.primaryKey) {
          template += ' PRIMARY KEY';

          if (dataType.autoIncrement) {
            template += ' AUTOINCREMENT';
          }
        }

        if(dataType.references) {
          dataType = Utils.formatReferences(dataType);
          template += ' REFERENCES <%= referencesTable %> (<%= referencesKey %>)';
          replacements.referencesTable = this.quoteTable(dataType.references.model);

          if(dataType.references.key) {
            replacements.referencesKey = this.quoteIdentifier(dataType.references.key);
          } else {
            replacements.referencesKey = this.quoteIdentifier('id');
          }

          if(dataType.onDelete) {
            template += ' ON DELETE <%= onDeleteAction %>';
            replacements.onDeleteAction = dataType.onDelete.toUpperCase();
          }

          if(dataType.onUpdate) {
            template += ' ON UPDATE <%= onUpdateAction %>';
            replacements.onUpdateAction = dataType.onUpdate.toUpperCase();
          }

        }

        result[fieldName] = Utils._.template(template)(replacements);
      } else {
        result[fieldName] = dataType;
      }
    }

    return result;
  },

  findAutoIncrementField: function(factory) {
    var fields = [];

    for (var name in factory.attributes) {
      if (factory.attributes.hasOwnProperty(name)) {
        var definition = factory.attributes[name];
        if (definition && definition.autoIncrement) {
          fields.push(name);
        }
      }
    }

    return fields;
  },

  showIndexesQuery: function(tableName) {
    var sql = 'PRAGMA INDEX_LIST(<%= tableName %>)';
    return Utils._.template(sql)({ tableName: this.quoteTable(tableName) });
  },

  removeIndexQuery: function(tableName, indexNameOrAttributes) {
    var sql = 'DROP INDEX IF EXISTS <%= indexName %>'
      , indexName = indexNameOrAttributes;

    if (typeof indexName !== 'string') {
      indexName = Utils.inflection.underscore(tableName + '_' + indexNameOrAttributes.join('_'));
    }

    return Utils._.template(sql)( { tableName: this.quoteIdentifiers(tableName), indexName: indexName });
  },

  describeTableQuery: function(tableName, schema, schemaDelimiter) {
    var options = {};
    options.schema = schema;
    options.schemaDelimiter = schemaDelimiter;
    options.quoted = false;

    var sql = 'PRAGMA TABLE_INFO(<%= tableName %>);';
    return Utils._.template(sql)({ tableName: this.addSchema({tableName: this.quoteIdentifiers(tableName), options: options})});
  },

  removeColumnQuery: function(tableName, attributes) {
    var backupTableName
      , query;

    attributes = this.attributesToSQL(attributes);

    if (typeof tableName === 'object') {
      backupTableName = {
        tableName: tableName.tableName + '_backup',
        schema: tableName.schema
      };
    } else {
      backupTableName = tableName + '_backup';
    }

    query = [
      this.createTableQuery(backupTableName, attributes).replace('CREATE TABLE', 'CREATE TEMPORARY TABLE'),
      'INSERT INTO <%= backupTableName %> SELECT <%= attributeNames %> FROM <%= tableName %>;',
      'DROP TABLE <%= tableName %>;',
      this.createTableQuery(tableName, attributes),
      'INSERT INTO <%= tableName %> SELECT <%= attributeNames %> FROM <%= backupTableName %>;',
      'DROP TABLE <%= backupTableName %>;'
    ].join('');

    return Utils._.template(query)({
      tableName: this.quoteTable(tableName),
      backupTableName: this.quoteTable(backupTableName),
      attributeNames: Utils._.keys(attributes).join(', ')
    });
  },

  renameColumnQuery: function(tableName, attrNameBefore, attrNameAfter, attributes) {
    var backupTableName
      , query;

    attributes = this.attributesToSQL(attributes);

    if (typeof tableName === 'object') {
      backupTableName = {
        tableName: tableName.tableName + '_backup',
        schema: tableName.schema
      };
    } else {
      backupTableName = tableName + '_backup';
    }

    query = [
      this.createTableQuery(backupTableName, attributes).replace('CREATE TABLE', 'CREATE TEMPORARY TABLE'),
      'INSERT INTO <%= backupTableName %> SELECT <%= attributeNamesImport %> FROM <%= tableName %>;',
      'DROP TABLE <%= tableName %>;',
      this.createTableQuery(tableName, attributes),
      'INSERT INTO <%= tableName %> SELECT <%= attributeNamesExport %> FROM <%= backupTableName %>;',
      'DROP TABLE <%= backupTableName %>;'
    ].join('');

    return Utils._.template(query)({
      tableName: this.quoteTable(tableName),
      backupTableName: this.quoteTable(backupTableName),
      attributeNamesImport: Utils._.keys(attributes).map(function(attr) {
        return (attrNameAfter === attr) ? this.quoteIdentifier(attrNameBefore) + ' AS ' + this.quoteIdentifier(attr) : this.quoteIdentifier(attr);
      }.bind(this)).join(', '),
      attributeNamesExport: Utils._.keys(attributes).map(function(attr) {
        return this.quoteIdentifier(attr);
      }.bind(this)).join(', ')
    });
  },

  startTransactionQuery: function(transaction, options) {
    if (options.parent) {
      return 'SAVEPOINT ' + this.quoteIdentifier(transaction.name) + ';';
    }

    return 'BEGIN TRANSACTION;';
  },

  setAutocommitQuery: function() {
    return '-- SQLite does not support SET autocommit.';
  },

  setIsolationLevelQuery: function(value) {
    switch (value) {
      case Transaction.ISOLATION_LEVELS.REPEATABLE_READ:
        return '-- SQLite is not able to choose the isolation level REPEATABLE READ.';
      case Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED:
        return 'PRAGMA read_uncommitted = ON;';
      case Transaction.ISOLATION_LEVELS.READ_COMMITTED:
        return 'PRAGMA read_uncommitted = OFF;';
      case Transaction.ISOLATION_LEVELS.SERIALIZABLE:
        return "-- SQLite's default isolation level is SERIALIZABLE. Nothing to do.";
      default:
        throw new Error('Unknown isolation level: ' + value);
    }
  },

  replaceBooleanDefaults: function(sql) {
    return sql.replace(/DEFAULT '?false'?/g, 'DEFAULT 0').replace(/DEFAULT '?true'?/g, 'DEFAULT 1');
  },

  quoteIdentifier: function(identifier) {
    if (identifier === '*') return identifier;
    return Utils.addTicks(identifier, '`');
  },

      /**
   * Generates an SQL query that returns all foreign keys of a table.
   *
   * @param  {String} tableName  The name of the table.
   * @param  {String} schemaName The name of the schema.
   * @return {String}            The generated sql query.
   */
  getForeignKeysQuery: function(tableName, schemaName) {
    var sql = 'PRAGMA foreign_key_list(<%= tableName %>)';
    return Utils._.template(sql)({ tableName: tableName });
  }
};

module.exports = Utils._.extend({}, MySqlQueryGenerator, QueryGenerator);
