'use strict';

var Utils = require('./utils')
  , Mixin = require('./associations/mixin')
  , InstanceValidator = require('./instance-validator')
  , DataTypes = require('./data-types')
  , Promise = require("./promise")
  , _ = require('lodash')
  , defaultsOptions = { raw: true };

module.exports = (function() {
  /**
   * This class represents an single instance, a database row. You might see it referred to as both Instance and instance. You should not
   * instantiate the Instance class directly, instead you access it using the finder and creation methods on the model.
   *
   * Instance instances operate with the concept of a `dataValues` property, which stores the actual values represented by the instance.
   * By default, the values from dataValues can also be accessed directly from the Instance, that is:
   * ```js
   * instance.field
   * // is the same as
   * instance.get('field')
   * // is the same as
   * instance.getDataValue('field')
   * ```
   * However, if getters and/or setters are defined for `field` they will be invoked, instead of returning the value from `dataValues`.
   * Accessing properties directly or using `get` is preferred for regular use, `getDataValue` should only be used for custom getters.
   *
   * @see {Sequelize#define} for more information about getters and setters
   * @class Instance
   */
  var Instance = function(values, options) {
    this.dataValues = {};
    this._previousDataValues = {};
    this.__options = this.Model.options;
    this.options = options || {};
    this.hasPrimaryKeys = this.Model.options.hasPrimaryKeys;
    this.__eagerlyLoadedAssociations = [];
    /**
     * Returns true if this instance has not yet been persisted to the database
     * @property isNewRecord
     * @return {Boolean}
     */
    this.isNewRecord = options.isNewRecord;

    /**
     * Returns the Model the instance was created from.
     * @see {Model}
     * @property Model
     * @return Model
     */

    initValues.call(this, values, options);
  };

  /**
   * A reference to the sequelize instance
   * @see {Sequelize}
   * @property sequelize
   * @return {Sequelize}
   */
  Object.defineProperty(Instance.prototype, 'sequelize', {
    get: function() { return this.Model.modelManager.sequelize; }
  });

  Object.defineProperty(Instance.prototype, 'QueryInterface', {
    get: function() { return this.sequelize.getQueryInterface(); }
  });

  /**
   * If timestamps and paranoid are enabled, returns whether the deletedAt timestamp of this instance is set. Otherwise, always returns false.
   * @property isDeleted
   * @return {Boolean}
   */
  Object.defineProperty(Instance.prototype, 'isDeleted', {
    get: function() {
      return this.Model._timestampAttributes.deletedAt && this.dataValues[this.Model._timestampAttributes.deletedAt] !== null;
    }
  });

  /**
   * Get the values of this Instance. Proxies to `this.get`
   * @see {Instance#get}
   * @property values
   * @return {Object}
   */
  Object.defineProperty(Instance.prototype, 'values', {
    get: function() {
      return this.get();
    }
  });

  /**
   * A getter for `this.changed()`. Returns true if any keys have changed.
   *
   * @see {Instance#changed}
   * @property isDirty
   * @return {Boolean}
   */
  Object.defineProperty(Instance.prototype, 'isDirty', {
    get: function() {
      return !!this.changed();
    }
  });

  /**
   * Get the values of the primary keys of this instance.
   *
   * @property primaryKeyValues
   * @return {Object}
   */
  Object.defineProperty(Instance.prototype, 'primaryKeyValues', {
    get: function() {
      var result = {}
        , self = this;

      Utils._.each(this.Model.primaryKeys, function(_, attr) {
        result[attr] = self.dataValues[attr];
      });

      return result;
    }
  });

  Object.defineProperty(Instance.prototype, 'identifiers', {
    get: function() {
      var primaryKeys = Object.keys(this.Model.primaryKeys)
        , result = {}
        , self = this;
      primaryKeys.forEach(function(identifier) {
        result[identifier] = self.dataValues[identifier];
      });

      return result;
    }
  });

  Instance.prototype.toString = function () {
    return '[object SequelizeInstance]';
  };

  /**
   * Get the value of the underlying data value
   *
   * @param {String} key
   * @return {any}
   */
  Instance.prototype.getDataValue = function(key) {
    return this.dataValues[key];
  };

  /**
   * Update the underlying data value
   *
   * @param {String} key
   * @param {any} value
   */
  Instance.prototype.setDataValue = function(key, value) {
    this.dataValues[key] = value;
  };

  /**
   * If no key is given, returns all values of the instance, also invoking virtual getters.
   *
   * If key is given and a field or virtual getter is present for the key it will call that getter - else it will return the value for key.
   *
   * @param {String} [key]
   * @param {Object} [options]
   * @param {Boolean} [options.plain=false] If set to true, included instances will be returned as plain objects
   * @return {Object|any}
   */
  Instance.prototype.get = function(key, options) {
    if (options === undefined && typeof key === "object") {
      options = key;
      key = undefined;
    }

    if (key) {
      if (this._customGetters[key]) {
        return this._customGetters[key].call(this, key);
      }
      if (options && options.plain && this.options.include && this.options.includeNames.indexOf(key) !== -1) {
        if (Array.isArray(this.dataValues[key])) {
          return this.dataValues[key].map(function (instance) {
            return instance.get({plain: options.plain});
          });
        } else if (this.dataValues[key] instanceof Instance) {
          return this.dataValues[key].get({plain: options.plain});
        } else {
          return this.dataValues[key];
        }
      }
      return this.dataValues[key];
    }

    if (this._hasCustomGetters || (options && options.plain && this.options.include)) {
      var values = {}
        , _key;

      if (this._hasCustomGetters) {
        for (_key in this._customGetters) {
          if (this._customGetters.hasOwnProperty(_key)) {
            values[_key] = this.get(_key);
          }
        }
      }

      for (_key in this.dataValues) {
        if (!values.hasOwnProperty(_key) && this.dataValues.hasOwnProperty(_key)) {
          if (options && options.plain && this.options.include && this.options.includeNames.indexOf(_key) !== -1) {
            if (Array.isArray(this.dataValues[_key])) {
              values[_key] = this.dataValues[_key].map(function (instance) {
                return instance.get({plain: options.plain});
              });
            } else if (this.dataValues[_key] instanceof Instance) {
              values[_key] = this.dataValues[_key].get({plain: options.plain});
            } else {
              values[_key] = this.dataValues[_key];
            }
          } else {
            values[_key] = this.dataValues[_key];
          }
        }
      }
      return values;
    }
    return this.dataValues;
  };

  /**
   * Set is used to update values on the instance (the sequelize representation of the instance that is, remember that nothing will be persisted before you actually call `save`).
   * In its most basic form `set` will update a value stored in the underlying `dataValues` object. However, if a custom setter function is defined for the key, that function
   * will be called instead. To bypass the setter, you can pass `raw: true` in the options object.
   *
   * If set is called with an object, it will loop over the object, and call set recursively for each key, value pair. If you set raw to true, the underlying dataValues will either be
   * set directly to the object passed, or used to extend dataValues, if dataValues already contain values.
   *
   * When set is called, the previous value of the field is stored, so that you can later see which fields changed (see `changed`).
   *
   * Set can also be used to build instances for associations, if you have values for those. TODO - mick should probably write something here about how includes in set works - perhaps also even some tests?
   *
   * @see {Model#find} for more information about includes
   * @param {String|Object} key
   * @param {any} value
   * @param {Object} [options]
   * @param {Boolean} [options.raw=false] If set to true, field and virtual setters will be ignored
   * @param {Boolean} [options.reset=false] Clear all previously set data values
   * @param {Object}  [options.include]
   * @alias setAttributes
   */
  Instance.prototype.set = function(key, value, options) {
    var values
      , originalValue
      , keys
      , i
      , length;

    if (typeof key === 'object') {
      values = key;
      options = value;
      options || (options = {});

      if (options.reset) {
        this.dataValues = {};
      }

      // If raw, and we're not dealing with includes or special attributes, just set it straight on the dataValues object
      if (options.raw && !(this.options && this.options.include) && !(this.options && this.options.attributes) && !this.Model._hasBooleanAttributes && !this.Model._hasDateAttributes) {
        if (Object.keys(this.dataValues).length) {
          this.dataValues = _.extend(this.dataValues, values);
        } else {
          this.dataValues = values;
        }
        // If raw, .changed() shouldn't be true
        this._previousDataValues = _.clone(this.dataValues);
      } else {
        // Loop and call set

        if (this.options.attributes) {
          keys = this.options.attributes;
          if (this.Model._hasVirtualAttributes) {
            keys = keys.concat(this.Model._virtualAttributes);
          }

          if (this.options.includeNames) {
            keys = keys.concat(this.options.includeNames);
          }

          for (i = 0, length = keys.length; i < length; i++) {
            if (values[keys[i]] !== undefined) {
              this.set(keys[i], values[keys[i]], options);
            }
          }
        } else {
          for (key in values) {
            this.set(key, values[key], options);
          }
        }

        if (options.raw) {
          // If raw, .changed() shouldn't be true
          this._previousDataValues = _.clone(this.dataValues);
        }
      }
    } else {
      options || (options = {});
      if (!options.raw) {
        originalValue = this.dataValues[key];
      }

      // If not raw, and there's a customer setter
      if (!options.raw && this._customSetters[key]) {
        this._customSetters[key].call(this, value, key);
      } else {
        // Check if we have included models, and if this key matches the include model names/aliases

        if (this.options && this.options.include && this.options.includeNames.indexOf(key) !== -1 && value) {
          // Pass it on to the include handler
          this._setInclude(key, value, options);
          return;
        } else {
          // Bunch of stuff we won't do when its raw
          if (!options.raw) {
            // If attribute is not in model definition, return
            if (!this._isAttribute(key)) {
              return;
            }

            // If attempting to set primary key and primary key is already defined, return
            if (this.Model._hasPrimaryKeys && originalValue && this.Model._isPrimaryKey(key)) {
              return;
            }

            // If attempting to set read only attributes, return
            if (this.Model._hasReadOnlyAttributes && this.Model._isReadOnlyAttribute(key)) {
              return;
            }

            // Convert date fields to real date objects
            if (this.Model._hasDateAttributes && this.Model._isDateAttribute(key) && value !== null && value !== undefined && !(value instanceof Date) && !value._isSequelizeMethod) {
              value = new Date(value);
            }
          }

          // Convert boolean-ish values to booleans
          if (this.Model._hasBooleanAttributes && this.Model._isBooleanAttribute(key) && value !== null && value !== undefined && !value._isSequelizeMethod) {
            if (Buffer.isBuffer(value) && value.length === 1) {
              // Bit fields are returned as buffers
              value = value[0];
            }
            value = _.isString(value) ? !!parseInt(value, 10) : !!value;
          }

          if (!options.raw && originalValue !== value) {
            this._previousDataValues[key] = originalValue;
          }
          this.dataValues[key] = value;
        }
      }
    }

    return this;
  };

  /**
   * If changed is called with a string it will return a boolean indicating whether the value of that key in `dataValues` is different from the value in `_previousDataValues`.
   *
   * If changed is called without an argument, it will return an array of keys that have changed.
   *
   * @param {String} [key]
   * @return {Boolean|Array}
   */
  Instance.prototype.changed = function(key) {
    if (key) {
      if (this.Model._isDateAttribute(key) && this._previousDataValues[key] && this.dataValues[key]) {
        return this._previousDataValues[key].valueOf() !== this.dataValues[key].valueOf();
      }
      return this._previousDataValues[key] !== this.dataValues[key];
    }
    var changed = Object.keys(this.dataValues).filter(function(key) {
      return this.changed(key);
    }.bind(this));

    return changed.length ? changed : false;
  };

  /**
   * Returns the previous value for key from `_previousDataValues`.
   * @param {String} key
   * @return {any}
   */
  Instance.prototype.previous = function(key) {
    return this._previousDataValues[key];
  };

  Instance.prototype._setInclude = function(key, value, options) {
    if (!Array.isArray(value)) value = [value];
    if (value[0] instanceof Instance) {
      value = value.map(function(instance) {
        return instance.dataValues;
      });
    }

    var include = this.options.includeMap[key]
      , association = include.association
      , self = this
      , accessor = key
      , childOptions
      , primaryKeyAttribute  = include.model.primaryKeyAttribute
      , isEmpty;

    if (!isEmpty) {
      childOptions = {
        isNewRecord: false,
        isDirty: false,
        include: include.include,
        includeNames: include.includeNames,
        includeMap: include.includeMap,
        includeValidated: true,
        raw: options.raw,
        attributes: include.originalAttributes
      };
    }
    if (include.originalAttributes === undefined || include.originalAttributes.length) {
      if (association.isSingleAssociation) {
        if (Array.isArray(value)) {
          value = value[0];
        }

        isEmpty = value && value[primaryKeyAttribute] === null;
        self[accessor] = self.dataValues[accessor] = isEmpty ? null : include.model.build(value, childOptions);
      } else {
        isEmpty = value[0] && value[0][primaryKeyAttribute] === null;
        self[accessor] = self.dataValues[accessor] = isEmpty ? [] : include.model.bulkBuild(value, childOptions);
      }
    }
  };

  /**
   * Validate this instance, and if the validation passes, persist it to the database.
   *
   * On success, the callback will be called with this instance. On validation error, the callback will be called with an instance of `Sequelize.ValidationError`.
   * This error will have a property for each of the fields for which validation failed, with the error message for that field.
   *
   * @param {Array} [fields] An optional array of strings, representing database columns. If fields is provided, only those columns will be validation and saved.
   * @param {Object} [options]
   * @param {Object} [options.fields] An alternative way of setting which fields should be persisted
   * @param {Boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated.
   * @param {Transaction} [options.transaction]
   *
   * @return {Promise<this|Errors.ValidationError>}
   */
  Instance.prototype.save = function(fieldsOrOptions, options) {
    if (fieldsOrOptions instanceof Array) {
      fieldsOrOptions = { fields: fieldsOrOptions };
    }

    options = Utils._.extend({
      hooks: true
    }, options, fieldsOrOptions);

    if (!options.fields) {
      options.fields = Object.keys(this.Model.attributes);
    }

    if (options.returning === undefined) {
      if (options.association) {
        options.returning = false;
      } else {
        options.returning = true;
      }
    }

    var self = this
      , values = {}
      , updatedAtAttr = this.Model._timestampAttributes.updatedAt
      , createdAtAttr = this.Model._timestampAttributes.createdAt;

    if (updatedAtAttr && options.fields.indexOf(updatedAtAttr) === -1) {
      options.fields.push(updatedAtAttr);
    }

    if (options.silent === true) {
      // UpdateAtAttr might have been added as a result of Object.keys(Model.attributes). In that case we have to remove it again
      Utils._.remove(options.fields, function(val) {
        return val === updatedAtAttr;
      });
      updatedAtAttr = false;
    }

    if (this.isNewRecord === true && createdAtAttr && options.fields.indexOf(createdAtAttr) === -1) {
      options.fields.push(createdAtAttr);
    }

    return Promise.try(function() {
      // Validate
      if (options.hooks) {
        return self.hookValidate({skip: _.difference(Object.keys(self.rawAttributes), options.fields)});
      }
    }).then(function() {
      options.fields.forEach(function(field) {
        if (self.dataValues[field] !== undefined) {
          values[field] = self.dataValues[field];
        }
      });

      for (var attrName in self.Model.rawAttributes) {
        if (self.Model.rawAttributes.hasOwnProperty(attrName)) {
          var definition = self.Model.rawAttributes[attrName]
            , isEnum = definition.type.toString() === DataTypes.ENUM.toString()
            , isMySQL = ['mysql', 'mariadb'].indexOf(self.Model.modelManager.sequelize.options.dialect) !== -1
            , ciCollation = !!self.Model.options.collate && self.Model.options.collate.match(/_ci$/i)
            , valueOutOfScope;

          // Unfortunately for MySQL CI collation we need to map/lowercase values again
          if (isEnum && isMySQL && ciCollation && (attrName in values) && values[attrName]) {
            var scopeIndex = (definition.values || []).map(function(d) { return d.toLowerCase(); }).indexOf(values[attrName].toLowerCase());
            valueOutOfScope = scopeIndex === -1;

            // We'll return what the actual case will be, since a simple SELECT query would do the same...
            if (!valueOutOfScope) {
              values[attrName] = definition.values[scopeIndex];
            }
          }
        }
      }

      if (updatedAtAttr) {
        values[updatedAtAttr] = (
          (
            self.isNewRecord
            && !!self.Model.rawAttributes[updatedAtAttr]
            && !!self.Model.rawAttributes[updatedAtAttr].defaultValue
          )
          ? self.Model.rawAttributes[updatedAtAttr].defaultValue
          : Utils.now(self.sequelize.options.dialect)
        );
      }

      if (self.isNewRecord && createdAtAttr && !values[createdAtAttr]) {
        values[createdAtAttr] = (
          (
            !!self.Model.rawAttributes[createdAtAttr]
            && !!self.Model.rawAttributes[createdAtAttr].defaultValue
          )
          ? self.Model.rawAttributes[createdAtAttr].defaultValue
          : Utils.now(self.sequelize.options.dialect)
        );
      }

      var query = null
        , args = []
        , hook = '';

      if (self.isNewRecord) {
        query = 'insert';
        args = [self, self.QueryInterface.QueryGenerator.addSchema(self.Model), values, options];
        hook = 'Create';
      } else {
        var identifier = self.primaryKeyValues;

        if (identifier) {
          for (attrName in identifier) {
            // Field name mapping
            if (self.Model.rawAttributes[attrName].field) {
              identifier[self.Model.rawAttributes[attrName].field] = identifier[attrName];
              delete identifier[attrName];
            }
          }
        }

        if (identifier === null && self.__options.whereCollection !== null) {
          identifier = self.__options.whereCollection;
        }

        query = 'update';
        args = [self, self.QueryInterface.QueryGenerator.addSchema(self.Model), values, identifier, options];
        hook = 'Update';
      }

      // Add the values to the Instance
      self.dataValues = _.extend(self.dataValues, values);

      return Promise.try(function() {
        // Run before hook
        if (options.hooks) {
          return self.Model.runHooks('before' + hook, self).then(function() {
            // dataValues might have changed inside the hook, rebuild the values hash
            values = {};

            options.fields.forEach(function(attr) {
              if (self.dataValues[attr] !== undefined && !self.Model._isVirtualAttribute(attr)) {
                values[attr] = self.dataValues[attr];
              }

              // Field name mapping
              if (self.Model.rawAttributes[attr].field && self.Model.rawAttributes[attr].field !== attr) {
                values[self.Model.rawAttributes[attr].field] = values[attr];
                delete values[attr];
              }
            });

            args[2] = values;
          });
        }
      }).then(function() {
        return self.QueryInterface[query].apply(self.QueryInterface, args).catch(self.sequelize.UniqueConstraintError, function(err) {
          if (!!self.__options.uniqueKeys && self.QueryInterface.QueryGenerator.uniqueConstraintMapping.code === err.parent.code) {
            var index = self.QueryInterface.QueryGenerator.uniqueConstraintMapping.map(err.parent.toString());

            if (index !== false) {
              var fields = index.fields.filter(function(f) { return f !== self.Model.tableName; });

              Utils._.each(self.__options.uniqueKeys, function(uniqueKey) {
                if (!!uniqueKey.msg && (Utils._.isEqual(uniqueKey.fields, fields)) || uniqueKey.name === index.indexName) {
                  err = new self.sequelize.UniqueConstraintError({
                    message: uniqueKey.msg,
                    fields: fields,
                    index: index.indexName,
                    parent: err.parent
                  });
                }
              });
            }
          }
          throw err;
        }).tap(function(result) {
          // Transfer database generated values (defaults, autoincrement, etc)
          values = _.extend(values, result.dataValues);

          // Ensure new values are on Instance, and reset previousDataValues
          result.dataValues = _.extend(result.dataValues, values);
          result._previousDataValues = _.clone(result.dataValues);
        }).tap(function(result) {
          // Run after hook
          if (options.hooks) {
            return self.Model.runHooks('after' + hook, result);
          }
        }).then(function(result) {
          return result;
        });
      });
    });
  };

 /*
  * Refresh the current instance in-place, i.e. update the object with current data from the DB and return the same object.
  * This is different from doing a `find(Instance.id)`, because that would create and return a new instance. With this method,
  * all references to the Instance are updated with the new data and no new objects are created.
  *
  * @see {Model#find}
  * @param {Object} [options] Options that are passed on to `Model.find`
  * @return {Promise<this>}
  */
  Instance.prototype.reload = function(options) {
    var self = this
      , where = [
        this.QueryInterface.quoteTable(this.Model.name) + '.' + this.QueryInterface.quoteIdentifier(this.Model.primaryKeyAttribute) + '=?',
        this.get(this.Model.primaryKeyAttribute, {raw: true})
      ];

    return this.Model.find({
      where: where,
      limit: 1,
      include: this.options.include || null
    }, options).then(function(reload) {
      self.set(reload.dataValues, {raw: true, reset: true});
    }).return(self);
  };

  /*
   * Validate the attribute of this instance according to validation rules set in the model definition.
   *
   * Emits null if and only if validation successful; otherwise an Error instance containing { field name : [error msgs] } entries.
   *
   * @param {Object} [options] Options that are passed to the validator
   * @param {Array} [options.skip] An array of strings. All properties that are in this array will not be validated
   * @see {InstanceValidator}
   *
   * @return {Promise<undefined|Errors.ValidationError}
   */
  Instance.prototype.validate = function(options) {
    return new InstanceValidator(this, options).validate();
  };

  Instance.prototype.hookValidate = function(object) {
    var validator = new InstanceValidator(this, object);

    return validator.hookValidate();
  };

  /**
   * This is the same as calling `setAttributes`, then calling `save`.
   *
   * @see {Instance#setAttributes}
   * @see {Instance#save}
   * @param {Object} updates See `setAttributes`
   * @param {Object} options See `save`
   *
   * @return {Promise<this>}
   */
  Instance.prototype.updateAttributes = function(updates, options) {
    if (options instanceof Array) {
      options = { fields: options };
    }

    this.set(updates);
    return this.save(options);
  };

  Instance.prototype.setAttributes = function(updates) {
    return this.set(updates);
  };

  /**
   * Destroy the row corresponding to this instance. Depending on your setting for paranoid, the row will either be completely deleted, or have its deletedAt timestamp set to the current time.
   *
   * @param {Object}  [options={}]
   * @param {Boolean} [options.force=false] If set to true, paranoid models will actually be deleted
   *
   * @return {Promise<undefined>}
   */
  Instance.prototype.destroy = function(options) {
    options = Utils._.extend({
      hooks: true,
      force: false
    }, options || {});

    var self = this;

    // This semi awkward syntax where we can't return the chain directly but have to return the last .then() call is to allow sql proxying
    return Promise.try(function() {
      // Run before hook
      if (options.hooks) {
        return self.Model.runHooks('beforeDestroy', self);
      }
    }).then(function() {
      var identifier;

      if (self.Model._timestampAttributes.deletedAt && options.force === false) {
        self.dataValues[self.Model._timestampAttributes.deletedAt] = new Date();
        return self.save(_.extend(_.clone(options), {hooks : false}));
      } else {
        identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id };
        return self.QueryInterface.delete(self, self.QueryInterface.QueryGenerator.addSchema(self.Model), identifier, options);
      }
    }).tap(function(result) {
      // Run after hook
      if (options.hooks) {
        return self.Model.runHooks('afterDestroy', self);
      }
    }).then(function(result) {
      return result;
    });
  };

  /**
   * Increment the value of one or more columns. This is done in the database, which means it does not use the values currently stored on the Instance. The increment is done using a
   * ```sql
   * SET column = column + X
   * ```
   * query. To get the correct value after an increment into the Instance you should do a reload.
   *
   *```js
   * instance.increment('number') // increment number by 1
   * instance.increment(['number', 'count'], { by: 2 }) // increment number and count by 2
   * instance.increment({ answer: 42, tries: 1}, { by: 2 }) // increment answer by 42, and tries by 1.
   *                                                        // `by` is ignored, since each column has its own value
   * ```
   *
   * @see {Instance#reload}
   * @param {String|Array|Object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given
   * @param {Object} [options]
   * @param {Integer} [options.by=1] The number to increment by
   * @param {Transaction} [options.transaction=null]
   *
   * @return {Promise}
   */
  Instance.prototype.increment = function(fields, countOrOptions) {
    Utils.validateParameter(countOrOptions, Object, {
      optional: true,
      deprecated: Number,
      deprecationWarning: 'Increment expects an object as second parameter. Please pass the incrementor as option! ~> instance.increment(' + JSON.stringify(fields) + ', { by: ' + countOrOptions + ' })'
    });

    var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id }
      , updatedAtAttr = this.Model._timestampAttributes.updatedAt
      , values = {}
      , where;

    if (countOrOptions === undefined) {
      countOrOptions = { by: 1, transaction: null };
    } else if (typeof countOrOptions === 'number') {
      countOrOptions = { by: countOrOptions, transaction: null };
    }

    countOrOptions = Utils._.extend({
      by: 1,
      attributes: {},
      where: {}
    }, countOrOptions);

    where = _.extend(countOrOptions.where, identifier);

    if (Utils._.isString(fields)) {
      values[fields] = countOrOptions.by;
    } else if (Utils._.isArray(fields)) {
      Utils._.each(fields, function(field) {
        values[field] = countOrOptions.by;
      });
    } else { // Assume fields is key-value pairs
      values = fields;
    }

    if (updatedAtAttr && !values[updatedAtAttr]) {
      countOrOptions.attributes[updatedAtAttr] = Utils.now(this.Model.modelManager.sequelize.options.dialect);
    }

    return this.QueryInterface.increment(this, this.QueryInterface.QueryGenerator.addSchema(this.Model.tableName, this.Model.options.schema), values, where, countOrOptions);
  };

  /**
   * Decrement the value of one or more columns. This is done in the database, which means it does not use the values currently stored on the Instance. The decrement is done using a
   * ```sql
   * SET column = column - X
   * ```
   * query. To get the correct value after an decrement into the Instance you should do a reload.
   *
   * ```js
   * instance.decrement('number') // decrement number by 1
   * instance.decrement(['number', 'count'], { by: 2 }) // decrement number and count by 2
   * instance.decrement({ answer: 42, tries: 1}, { by: 2 }) // decrement answer by 42, and tries by 1.
   *                                                        // `by` is ignored, since each column has its own value
   * ```
   *
   * @see {Instance#reload}
   * @param {String|Array|Object} fields If a string is provided, that column is decremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is decremented by the value given
   * @param {Object} [options]
   * @param {Integer} [options.by=1] The number to decrement by
   * @param {Transaction} [options.transaction=null]
   *
   * @return {Promise}
   */
  Instance.prototype.decrement = function(fields, countOrOptions) {
    Utils.validateParameter(countOrOptions, Object, {
      optional: true,
      deprecated: Number,
      deprecationWarning: 'Decrement expects an object as second parameter. Please pass the decrementor as option! ~> instance.decrement(' + JSON.stringify(fields) + ', { by: ' + countOrOptions + ' })'
    });

    if (countOrOptions === undefined) {
      countOrOptions = { by: 1, transaction: null };
    } else if (typeof countOrOptions === 'number') {
      countOrOptions = { by: countOrOptions, transaction: null };
    }

    if (countOrOptions.by === undefined) {
      countOrOptions.by = 1;
    }

    if (!Utils._.isString(fields) && !Utils._.isArray(fields)) { // Assume fields is key-value pairs
      Utils._.each(fields, function(value, field) {
        fields[field] = -value;
      });
    }

    countOrOptions.by = 0 - countOrOptions.by;

    return this.increment(fields, countOrOptions);
  };

  /**
   * Check whether all values of this and `other` Instance are the same
   *
   * @param {Instance} other
   * @return {Boolean}
   */
  Instance.prototype.equals = function(other) {
    var result = true;

    Utils._.each(this.dataValues, function(value, key) {
      if (Utils._.isDate(value) && Utils._.isDate(other[key])) {
        result = result && (value.getTime() === other[key].getTime());
      } else {
        result = result && (value === other[key]);
      }
    });

    return result;
  };

  /**
   * Check if this is eqaul to one of `others` by calling equals
   *
   * @param {Array} others
   * @return {Boolean}
   */
  Instance.prototype.equalsOneOf = function(others) {
    var self = this;

    return _.any(others, function(other) {
      return self.equals(other);
    });
  };

  Instance.prototype.setValidators = function(attribute, validators) {
    this.validators[attribute] = validators;
  };

  /**
   * Convert the instance to a JSON representation. Proxies to calling `get` with no keys. This means get all values gotten from the DB, and apply all custom getters.
   *
   * @see {Instance#get}
   * @return {object}
   */
  Instance.prototype.toJSON = function() {
    return this.get({
      plain: true
    });
  };

  // private
  var initValues = function(values, options) {
    var defaults
      , key;

    values = values && _.clone(values) || {};

    if (options.isNewRecord) {
      defaults = {};

      if (this.Model._hasDefaultValues) {
        Utils._.each(this.Model._defaultValues, function(valueFn, key) {
          if (!defaults.hasOwnProperty(key)) {
            defaults[key] = valueFn();
          }
        });
      }

      // set id to null if not passed as value, a newly created dao has no id
      // removing this breaks bulkCreate
      // do after default values since it might have UUID as a default value
      if (!defaults.hasOwnProperty(this.Model.primaryKeyAttribute)) {
        defaults[this.Model.primaryKeyAttribute] = null;
      }

      if (this.Model._timestampAttributes.createdAt && defaults[this.Model._timestampAttributes.createdAt]) {
        this.dataValues[this.Model._timestampAttributes.createdAt] = Utils.toDefaultValue(defaults[this.Model._timestampAttributes.createdAt]);
        delete defaults[this.Model._timestampAttributes.createdAt];
      }

      if (this.Model._timestampAttributes.updatedAt && defaults[this.Model._timestampAttributes.updatedAt]) {
        this.dataValues[this.Model._timestampAttributes.updatedAt] = Utils.toDefaultValue(defaults[this.Model._timestampAttributes.updatedAt]);
        delete defaults[this.Model._timestampAttributes.updatedAt];
      }

      if (this.Model._timestampAttributes.deletedAt && defaults[this.Model._timestampAttributes.deletedAt]) {
        this.dataValues[this.Model._timestampAttributes.deletedAt] = Utils.toDefaultValue(defaults[this.Model._timestampAttributes.deletedAt]);
        delete defaults[this.Model._timestampAttributes.deletedAt];
      }

      if (Object.keys(defaults).length) {
        for (key in defaults) {
          if (!values.hasOwnProperty(key)) {
            this.set(key, Utils.toDefaultValue(defaults[key]), defaultsOptions);
          }
        }
      }
    }

    this.set(values, options);
  };

  return Instance;
})();


'use strict';

var Utils = require('../../utils')
  , AbstractQuery = require('../abstract/query')
  , DataTypes = require('../../data-types')
  , hstore = require('./hstore')
  , QueryTypes = require('../../query-types')
  , Promise = require('../../promise')
  , sequelizeErrors = require('../../errors.js');

// Parses hstore fields if the model has any hstore fields.
// This cannot be done in the 'pg' lib because hstore is a UDT.
var parseHstoreFields = function(model, row) {
  Utils._.forEach(row, function(value, key) {
    if (model._isHstoreAttribute(key) || (model.attributes[key] && model.attributes[key].type === DataTypes.ARRAY(DataTypes.HSTORE))) {
      row[key] = hstore.parse(value);

      return;
    }

    row[key] = value;
  });
};

module.exports = (function() {
  var Query = function(client, sequelize, callee, options) {
    this.client = client;
    this.sequelize = sequelize;
    this.callee = callee;
    this.options = Utils._.extend({
      logging: console.log,
      plain: false,
      raw: false
    }, options || {});

    this.checkLoggingOption();
  };
  Utils.inherit(Query, AbstractQuery);

  Query.prototype.run = function(sql) {
    /* jshint -W027 */
    this.sql = sql;

    var self = this
      , receivedError = false
      , query = this.client.query(sql)
      , rows = [];

    if (this.options.logging !== false) {
      this.sequelize.log('Executing (' + (this.client.uuid || 'default') + '): ' + this.sql);
    }

    return new Promise(function(resolve, reject) {
      var promise = this;

      query.on('row', function(row) {
        rows.push(row);
      });

      query.on('error', function(err) {
        receivedError = true;
        err.sql = sql;
        promise.emit('sql', sql, self.client.uuid);
        reject(self.formatError(err));
      });

      query.on('end', function(result) {
        if (receivedError) {
          return;
        }

        promise.emit('sql', self.sql, self.client.uuid);
        resolve([rows, sql, result]);
      });
    }).spread(function(rows, sql, result) {
      var results = rows
        , isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
        , isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0);

      if (isTableNameQuery || isRelNameQuery) {
        if (isRelNameQuery) {
          results = rows.map(function(row) {
            return {
              name: row.relname,
              tableName: row.relname.split('_')[0]
            };
          });
        } else {
          results = rows.map(function(row) { return Utils._.values(row); });
        }
        return results;
      }

      if (self.isShowIndexesQuery()) {
        results.forEach(function (result) {
          var attributes = /ON .*? (?:USING .*?\s)?\((.*)\)/gi.exec(result.definition)[1].split(',')
            , field
            , attribute
            , columns;

          // Map column index in table to column name
          columns = Utils._.zipObject(
            result.column_indexes,
            self.sequelize.queryInterface.QueryGenerator.fromArray(result.column_names)
          );
          delete result.column_indexes;
          delete result.column_names;

          // Indkey is the order of attributes in the index, specified by a string of attribute indexes
          result.fields = result.indkey.split(' ').map(function (indKey, index) {
            field = columns[indKey];
            attribute = attributes[index];
            return {
              attribute: field,
              collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : undefined,
              order: attribute.indexOf('DESC') !== -1 ? 'DESC' : attribute.indexOf('ASC') !== -1 ? 'ASC': undefined,
              length: undefined,
            };
          });
          delete result.columns;
        });
        return results;
      } else if (self.isSelectQuery()) {
        if (self.sql.toLowerCase().indexOf('select c.column_name') === 0) {
          result = {};

          rows.forEach(function(_result) {
            result[_result.Field] = {
              type: _result.Type.toUpperCase(),
              allowNull: (_result.Null === 'YES'),
              defaultValue: _result.Default,
              special: (!!_result.special ? self.sequelize.queryInterface.QueryGenerator.fromArray(_result.special) : [])
            };

            if (result[_result.Field].type === 'BOOLEAN') {
              result[_result.Field].defaultValue = { 'false': false, 'true': true }[result[_result.Field].defaultValue];

              if (result[_result.Field].defaultValue === undefined) {
                result[_result.Field].defaultValue = null;
              }
            }

            if (typeof result[_result.Field].defaultValue === 'string') {
              result[_result.Field].defaultValue = result[_result.Field].defaultValue.replace(/'/g, '');

              if (result[_result.Field].defaultValue.indexOf('::') > -1) {
                var split = result[_result.Field].defaultValue.split('::');
                if (split[1].toLowerCase() !== 'regclass)') {
                  result[_result.Field].defaultValue = split[0];
                }
              }
            }
          });

          return result;
        } else {
          // Postgres will treat tables as case-insensitive, so fix the case
          // of the returned values to match attributes
          if (self.options.raw === false && self.sequelize.options.quoteIdentifiers === false) {
            var attrsMap = Utils._.reduce(self.callee.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m; }, {});
            rows.forEach(function(row) {
              Utils._.keys(row).forEach(function(key) {
                var targetAttr = attrsMap[key];
                if (targetAttr !== key) {
                  row[targetAttr] = row[key];
                  delete row[key];
                }
              });
            });
          }

          if (!!self.callee && !!self.callee._hasHstoreAttributes) {
            rows.forEach(function(row) {
              parseHstoreFields(self.callee, row);
            });
          }

          return self.handleSelectQuery(rows);
        }
      } else if (self.isShowOrDescribeQuery()) {
        return results;
      } else if (QueryTypes.BULKUPDATE === self.options.type) {
        if (!self.options.returning) {
          return result.rowCount;
        }

        if (!!self.callee && !!self.callee._hasHstoreAttributes) {
          rows.forEach(function(row) {
            parseHstoreFields(self.callee, row);
          });
        }

        return self.handleSelectQuery(rows);
      } else if (QueryTypes.BULKDELETE === self.options.type) {
        return result.rowCount;
      } else if (self.isInsertQuery() || self.isUpdateQuery()) {
        if (!!self.callee && self.callee.dataValues) {
          if (!!self.callee.Model && !!self.callee.Model._hasHstoreAttributes) {
            parseHstoreFields(self.callee.Model, rows[0]);
          }

          for (var key in rows[0]) {
            if (rows[0].hasOwnProperty(key)) {
              var record = rows[0][key];

              var attr = Utils._.find(self.callee.Model.rawAttributes, function (attribute) {
                return attribute.fieldName === key || attribute.field === key;
              });

              self.callee.dataValues[attr && attr.fieldName || key] = record;
            }
          }
        }

        return self.callee;
      } else {
        return results;
      }
    });

    return this;
  };

  Query.prototype.formatError = function (err) {
    var match;

    switch (err.code) {
      case '23505':
        match = err.detail.match(/Key \((.*?)\)=\((.*?)\) already exists/);

        return new sequelizeErrors.UniqueConstraintError({
          fields: match[1].split(', '),
          value: match[2].split(', '),
          index: null,
          parent: err
        });

      default:
        return new sequelizeErrors.DatabaseError(err);
    }
  };

  Query.prototype.isShowIndexesQuery = function () {
    return this.sql.indexOf('pg_get_indexdef') !== -1;
  };

  Query.prototype.getInsertIdField = function() {
    return 'id';
  };

  return Query;
})();


var Sequelize = require(__dirname + "/../../index")
  , config    = require(__dirname + "/../../spec/config/config")
  , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
  , Project   = sequelize.import(__dirname + "/Project")
  , Task      = sequelize.import(__dirname + "/Task")

Project.hasMany(Task)
Task.belongsTo(Project)

sequelize.sync({force: true}).on('success', function() {
  Project
    .create({ name: 'Sequelize', description: 'A nice MySQL ORM for NodeJS' })
    .on('success', function(project) {
      Task.create({ name: 'Choose a nice MySQL connector', deadline: new Date(), importance: 10 })
        .on('success', function(task1) {
          Task.create({ name: 'Build the rest', deadline: new Date(), importance: 90 })
            .on('success', function(task2) {
              project.setTasks([task1, task2]).on('success', function(tasks) {
                console.log(project)
                console.log(tasks)
              })
            })
        })
    })
})


var chai      = require('chai')
  , sinonChai = require("sinon-chai")
  , sinon     = require('sinon')
  , fs        = require('fs')
  , path      = require('path')
  , expect    = chai.expect
  , assert    = chai.assert
  , Support   = require(__dirname + '/../support')

chai.use(sinonChai)
chai.config.includeStack = true

describe(Support.getTestDialectTeaser("Sequelize"), function () {
  describe('log', function() {
    beforeEach(function() {
      this.spy = sinon.spy(console, 'log')
    })

    afterEach(function() {
      console.log.restore()
    })

    describe("with disabled logging", function() {
      beforeEach(function() {
        this.sequelize = new Support.Sequelize('db', 'user', 'pw', { logging: false })
      })

      it("does not call the log method of the logger", function() {
        this.sequelize.log()
        expect(this.spy.calledOnce).to.be.false
      })
    })

    describe('with default logging options', function() {
      beforeEach(function() {
        this.sequelize = new Support.Sequelize('db', 'user', 'pw')
      })

      describe("called with no arguments", function() {
        it('calls the log method', function() {
          this.sequelize.log()
          expect(this.spy.calledOnce).to.be.true
        })

        it('logs an empty string as info event', function() {
          this.sequelize.log()
          expect(this.spy.calledOnce).to.be.true
        })
      })

      describe("called with one argument", function() {
        it('logs the passed string as info event', function() {
          this.sequelize.log('my message')
          expect(this.spy.withArgs('my message').calledOnce).to.be.true
        })
      })

      describe("called with more than two arguments", function() {
        it("passes the arguments to the logger", function() {
          this.sequelize.log('error', 'my message', 1, { a: 1 })
          expect(this.spy.withArgs('error', 'my message', 1, { a: 1 }).calledOnce).to.be.true
        })
      })
    })

    describe("with a custom function for logging", function() {
      beforeEach(function() {
        this.spy       = sinon.spy()
        this.sequelize = new Support.Sequelize('db', 'user', 'pw', { logging: this.spy })
      })

      it("calls the custom logger method", function() {
        this.sequelize.log('om nom')
        expect(this.spy.calledOnce).to.be.true
      })
    })
  })
})


module.exports = {
  up: function(migration, DataTypes, done) {
    migration.dropFunction('get_the_answer', []).complete(done);
  },
  down: function(migration, DataTypes, done) {
    migration.createFunction('get_the_answer', 'int', 'plpgsql',
            'RETURN 42;'
    ).complete(done);
  }
}


* and returning the cached result when the same inputs occur again

* This function is a first class objects,

* which lets us use it as [Higher-Order Function](https://eloquentjavascript.net/05_higher_order.html)

* @param {Function} func Original function

* @param {Map} cache - it's receive any cache DS which have get, set & has method

import { memoize } from '../Memoize'
import { union } from './cacheTest'
import { fibonacci } from '../../Dynamic-Programming/FibonacciNumber'
import { factorial } from '../../Recursive/Factorial'

const multipleFactorials = (arr) => arr.map(factorial)

describe('Testing Memoize', () => {
  it('expects the fibonacci function to use the cache on the second call', () => {
    const memoFibonacci = memoize(fibonacci)

    expect(memoFibonacci(5)).toEqual(fibonacci(5))
    expect(memoFibonacci(5)).toEqual(5)
    expect(memoFibonacci(10)).toEqual(fibonacci(10))
    expect(memoFibonacci(10)).toEqual(55)
  })

  it('expects the factorial function to use the cache on the second call', () => {
    const memoFactorial = memoize(factorial)

    expect(memoFactorial(5)).toEqual(factorial(5))
    expect(memoFactorial(5)).toEqual(120)
    expect(memoFactorial(10)).toEqual(factorial(10))
    expect(memoFactorial(10)).toEqual(3628800)
  })

  it('expects the multipleFactorials function to use the cache on the second call', () => {
    const memoMultipleFactorials = memoize(multipleFactorials)
    const input = [2, 3, 4, 5]

    expect(memoMultipleFactorials(input)).toEqual([2, 6, 24, 120])
    expect(memoMultipleFactorials(input)).toEqual(multipleFactorials(input))
  })

  it('expects the multipleFactorials function to use the cache on the second call', () => {
    const memoMultipleFactorials = memoize(multipleFactorials)
    const input = [2, 3, 4, 5]

    expect(memoMultipleFactorials(input)).toEqual([2, 6, 24, 120])
    expect(memoMultipleFactorials(input)).toEqual(multipleFactorials(input))
  })

  it('expects the union function to use the cache on the second call', () => {
    const memoUnion = memoize(union)
    const inputs = [
      new Set([1, 2, 3]),
      new Set([4, 3, 2]),
      new Set([5, 3, 6])
    ]

    expect(memoUnion(...inputs)).toEqual(new Set([1, 2, 3, 4, 5, 6]))
    expect(memoUnion(...inputs)).toEqual(union(...inputs))
  })
})


/**
  *  @function Timsort is a hybrid stable sorting algorithm, derived from merge sort and insertion sort,
  *  designed to perform well on many kinds of real-world data.
  *  It was implemented by Tim Peters in 2002 for use in the Python programming language.
  *  It is also used to sort arrays of non-primitive type in Java SE 7,
  *  on the Android platform, in GNU Octave, on V8, Swift and Rust.
  *  1) It sorts small partitions using Insertion Sort.
  *  2) Merges the partition using Merge Sort.
  *  @see [Timsort](https://en.wikipedia.org/wiki/Timsort)
  *  @param {Array} array
  */

const Timsort = (array) => {
  // Default size of a partition
  const RUN = 32
  const n = array.length
  // Sorting the partitions using Insertion Sort
  for (let i = 0; i < n; i += RUN) {
    InsertionSort(array, i, Math.min(i + RUN - 1, n - 1))
  }
  for (let size = RUN; size < n; size *= 2) {
    for (let left = 0; left < n; left += 2 * size) {
      const mid = left + size - 1
      const right = Math.min(left + 2 * size - 1, n - 1)
      Merge(array, left, mid, right)
    }
  }
}

/**
 * @function performs insertion sort on the partition
 * @param {Array} array array to be sorted
 * @param {Number} left left index of partition
 * @param {Number} right right index of partition
 */

const InsertionSort = (array, left, right) => {
  for (let i = left + 1; i <= right; i++) {
    const key = array[i]
    let j = i - 1
    while (j >= left && array[j] > key) {
      array[j + 1] = array[j]
      j--
    }
    array[j + 1] = key
  }
}

/**
 * @function merges two sorted partitions
 * @param {Array} array array to be sorted
 * @param {Number} left left index of partition
 * @param {Number} mid mid index of partition
 * @param {Number} right right index of partition
 */

const Merge = (array, left, mid, right) => {
  if (mid >= right) return
  const len1 = mid - left + 1
  const len2 = right - mid
  const larr = Array(len1)
  const rarr = Array(len2)
  for (let i = 0; i < len1; i++) {
    larr[i] = array[left + i]
  }
  for (let i = 0; i < len2; i++) {
    rarr[i] = array[mid + 1 + i]
  }
  let i = 0; let j = 0; let k = left
  while (i < larr.length && j < rarr.length) {
    if (larr[i] < rarr[j]) {
      array[k++] = larr[i++]
    } else {
      array[k++] = rarr[j++]
    }
  }
  while (i < larr.length) {
    array[k++] = larr[i++]
  }
  while (j < rarr.length) {
    array[k++] = rarr[j++]
  }
}

/**
 * @example Test of Timsort functions.
 * Data is randomly generated.
 * Return "RIGHT" if it works as expected,
 * otherwise "FAULTY"
 */
const demo = () => {
  const size = 1000000
  const data = Array(size)
  for (let i = 0; i < size; i++) {
    data[i] = Math.random() * Number.MAX_SAFE_INTEGER
  }
  const isSorted = function (array) {
    const n = array.length
    for (let i = 0; i < n - 1; i++) {
      if (array[i] > array[i + 1]) return false
    }
    return true
  }
  Timsort(data)
  if (isSorted(data)) {
    return 'RIGHT'
  } else {
    return 'FAULTY'
  }
}

export { Timsort, demo }


/* The selection sort algorithm sorts an array by repeatedly finding the minimum element
 *(considering ascending order) from unsorted part and putting it at the beginning. The
 *algorithm maintains two subarrays in a given array.
 *1) The subarray which is already sorted.
 *2) Remaining subarray which is unsorted.
 *
 *In every iteration of selection sort, the minimum element (considering ascending order)
 *from the unsorted subarray is picked and moved to the sorted subarray.
 */

export const selectionSort = (list) => {
  if (!Array.isArray(list)) {
    throw new TypeError('Given input is not an array')
  }
  const items = [...list] // We don't want to modify the original array
  const length = items.length
  for (let i = 0; i < length - 1; i++) {
    if (typeof items[i] !== 'number') {
      throw new TypeError('One of the items in your array is not a number')
    }
    // Number of passes
    let min = i // min holds the current minimum number position for each pass; i holds the Initial min number
    for (let j = i + 1; j < length; j++) { // Note that j = i + 1 as we only need to go through unsorted array
      if (items[j] < items[min]) { // Compare the numbers
        min = j // Change the current min number position if a smaller num is found
      }
    }
    if (min !== i) {
      // After each pass, if the current min num != initial min num, exchange the position.
      // Swap the numbers
      [items[i], items[min]] = [items[min], items[i]]
    }
  }
  return items
}


const sieveOfEratosthenes = (n) => {
  /*
   * Calculates prime numbers till a number n
   * :param n: Number up to which to calculate primes
   * :return: A boolean list containing only primes
   */
  const primes = new Array(n + 1)
  primes.fill(true) // set all as true initially
  primes[0] = primes[1] = false // Handling case for 0 and 1
  const sqrtn = Math.ceil(Math.sqrt(n))
  for (let i = 2; i <= sqrtn; i++) {
    if (primes[i]) {
      for (let j = i * i; j <= n; j += i) {
        /*
        Optimization.
        Let j start from i * i, not 2 * i, because smaller multiples of i have been marked false.

        For example, let i = 4.
        We do not have to check from 8(4 * 2) to 12(4 * 3)
        because they have been already marked false when i=2 and i=3.
        */
        primes[j] = false
      }
    }
  }
  return primes
}

export { sieveOfEratosthenes }


keep keep replace replace replace replace keep keep keep keep

const memoize = (func, cache = new Map()) => {

/**
 * @function memoize
 * @description ->
 * From [Wikipedia](https://en.wikipedia.org/wiki/Memoization),
 * memoization is an optimization technique
 * used primarily to speed up computer programs,
 * by storing the results of expensive function calls
 * and returning the cached result when the same inputs occur again
 * This function is a first class objects,
 * which lets us use it as [Higher-Order Function](https://eloquentjavascript.net/05_higher_order.html)
 * and return another function
 * @param {Function} func Original function
 * @returns {Function} Memoized function
 */
const memoize = (func) => {
  // Initialization of a slot to store the function result by arguments as a key in Hash Map
  const cache = new Map()

  const jsonReplacer = (_, value) => {
    if (value instanceof Set) { // if the value is Set it's converted to Array cause JSON.stringify can't convert Set
      return [...value]
    }

    if (value instanceof Map) { // if the value is Map it's converted to Object cause JSON.stringify can't convert Map
      return Object.fromEntries(value)
    }

    return value
  }

  return (...args) => {
    /**
     * Arguments converted to JSON string for use as a key of Map - it's easy to detect collections like -> Object and Array
     * If the args input is -> [new Set([1, 2, 3, 4]), {name: 'myName', age: 23}]
     * Then the agrsKey generate to -> '[[1,2,3,4],{"name":"myName","age":23}]' which is JSON mean string
     * Now it's ready to be a perfect key for Map
     */
    const argsKey = JSON.stringify(args, jsonReplacer)

    /**
     * Checks if the argument is already present in the cache,
     * then return the associated value / result
     */
    if (cache.has(argsKey)) {
      return cache.get(argsKey)
    }

    /**
     * If the argument is not yet present in the cache,
     * execute original function and save its value / result in cache,
     * finally return it
     */
    const result = func(...args) // spread all args
    cache.set(argsKey, result)

    return result
  }
}

export { memoize }


class Node {
  constructor (element) {
    this.element = element
    this.next = null
    this.prev = null
  }
}

class DoubleLinkedList {
  constructor () {
    this.length = 0
    this.head = null
    this.tail = null
  }

  // Add new element
  append (element) {
    const node = new Node(element)

    if (!this.head) {
      this.head = node
      this.tail = node
    } else {
      node.prev = this.tail
      this.tail.next = node
      this.tail = node
    }

    this.length++
  }

  // Add element
  insert (position, element) {
    // Check of out-of-bound values
    if (position >= 0 && position <= this.length) {
      const node = new Node(element)
      let current = this.head
      let previous = 0
      let index = 0

      if (position === 0) {
        if (!this.head) {
          this.head = node
          this.tail = node
        } else {
          node.next = current
          current.prev = node
          this.head = node
        }
      } else if (position === this.length) {
        current = this.tail
        current.next = node
        node.prev = current
        this.tail = node
      } else {
        while (index++ < position) {
          previous = current
          current = current.next
        }

        node.next = current
        previous.next = node

        // New
        current.prev = node
        node.prev = previous
      }

      this.length++
      return true
    } else {
      return false
    }
  }

  // Remove element at any position
  removeAt (position) {
    // look for out-of-bounds value
    if (position > -1 && position < this.length) {
      let current = this.head
      let previous = 0
      let index = 0

      // Removing first item
      if (position === 0) {
        this.head = current.next

        // if there is only one item, update this.tail //NEW
        if (this.length === 1) {
          this.tail = null
        } else {
          this.head.prev = null
        }
      } else if (position === this.length - 1) {
        current = this.tail
        this.tail = current.prev
        this.tail.next = null
      } else {
        while (index++ < position) {
          previous = current
          current = current.next
        }

        // link previous with current's next - skip it
        previous.next = current.next
        current.next.prev = previous
      }

      this.length--
      return current.element
    } else {
      return null
    }
  }

  // Get the indexOf item
  indexOf (elm) {
    let current = this.head
    let index = -1

    // If element found then return its position
    while (current) {
      if (elm === current.element) {
        return ++index
      }

      index++
      current = current.next
    }

    // Else return -1
    return -1
  }

  // Find the item in the list
  isPresent (elm) {
    return this.indexOf(elm) !== -1
  }

  // Delete an item from the list
  delete (elm) {
    return this.removeAt(this.indexOf(elm))
  }

  // Delete first item from the list
  deleteHead () {
    this.removeAt(0)
  }

  // Delete last item from the list
  deleteTail () {
    this.removeAt(this.length - 1)
  }

  // Print item of the string
  toString () {
    let current = this.head
    let string = ''

    while (current) {
      string += current.element + (current.next ? '\n' : '')
      current = current.next
    }

    return string
  }

  // Convert list to array
  toArray () {
    const arr = []
    let current = this.head

    while (current) {
      arr.push(current.element)
      current = current.next
    }

    return arr
  }

  // Check if list is empty
  isEmpty () {
    return this.length === 0
  }

  // Get the size of the list
  size () {
    return this.length
  }

  // Get the this.head
  getHead () {
    return this.head
  }

  // Get the this.tail
  getTail () {
    return this.tail
  }

  // Method to iterate over the LinkedList
  iterator () {
    let currentNode = this.getHead()
    if (currentNode === null) return -1

    const iterate = function * () {
      while (currentNode) {
        yield currentNode.element
        currentNode = currentNode.next
      }
    }
    return iterate()
  }

  // Method to log the LinkedList, for debugging
  // it' a circular structure, so can't use stringify to debug the whole structure
  log () {
    let currentNode = this.getHead()
    while (currentNode) {
      console.log(currentNode.element)
      currentNode = currentNode.next
    }
  }
}

// Example

// const newDoubleLinkedList = new DoubleLinkedList()
// newDoubleLinkedList.append(1)
// newDoubleLinkedList.append(2)
// newDoubleLinkedList.size() // returns 2
// const iterate = newDoubleLinkedList.iterator()
// console.log(iterate.next().value) // 1
// console.log(iterate.next().value) // 2
// console.log(newDoubleLinkedList.log())

export { DoubleLinkedList }


/*
 * Odd Number: https://simple.wikipedia.org/wiki/Odd_number
 * function to check if number is odd.
 * return true if number is odd.
 * else false
 */

/**
 * @function isOdd
 * @description -> Checking if number is odd using not divisibility by 2
 * If number is not divisible by 2 i.e remainder = 1, then it is odd
 * therefore, the function will return true
 *
 * If number is divisible by 2 i.e remainder != 1, then it is even
 * therefore, the function will return false
 * @param {number} number
 * @returns {boolean}
 */
const isOdd = (number) => Boolean(number % 2) // 1 -> true, 0 -> false
/**
 * @function isOddBitwise
 * @description -> Checking if number is even using bitwise operator
 * Bitwise AND (&) compares the bits of the 32
 * bit binary representations of the number and
 * returns a number after comparing each bit:
 *
 * 0 & 0 -> 0
 * 0 & 1 -> 0
 * 1 & 0 -> 0
 * 1 & 1 -> 1
 *
 * For every odd numbers, the last binary bit will be 1
 * and for even numbers, the last binary bit will be 0.
 *
 * As the number is compared with one, all the
 * other bits except the last will become 0. The
 * last bit will be 0 for even numbers and 1 for
 * odd numbers.
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND
 * @param {number} number
 * @returns {boolean}
*/
const isOddBitwise = (number) => Boolean(number & 1) // 1 -> true, 0 -> false

export { isOdd, isOddBitwise }


/*
 * Wiggle sort sorts the array into a wave like array.
 * An array ‘arr[0..n-1]’ is sorted in wave form if arr[0] >= arr[1] <= arr[2] >= arr[3] <= arr[4] >= …..
 *
 */

export const wiggleSort = function (arr) {
  for (let i = 0; i < arr.length; ++i) {
    const shouldNotBeLessThan = i % 2
    const isLessThan = arr[i] < arr[i + 1]
    if (shouldNotBeLessThan && isLessThan) {
      [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]
    }
  }
  return arr
}

// Implementation of wiggle sort

// > wiggleSort([3, 5, 2, 1, 6, 4])
// [ 3, 5, 2, 6, 1, 4 ]


import { fibonacci } from '../../Dynamic-Programming/FibonacciNumber'

import { factorial } from '../../Recursive/Factorial'

const multipleFactorials = (arr) => arr.map(factorial)

it('expects the fibonacci function to use the cache on the second call', () => {


import * as volume from '../Volume'

test('Testing on volCuboid', () => {
  const volCuboid = volume.volCuboid(2.0, 5.0, 3)
  expect(volCuboid).toBe(30.0)
})

test('Testing on volCube', () => {
  const volCube = volume.volCube(2.0)
  expect(volCube).toBe(8.0)
})

test('Testing on volCone', () => {
  const volCone = volume.volCone(3.0, 8.0)
  expect(volCone).toBe(75.39822368615503)
})

test('Testing on volPyramid', () => {
  const volPyramid = volume.volPyramid(2.0, 3.0, 8.0)
  expect(volPyramid).toBe(16.0)
})

test('Testing on volCylinder', () => {
  const volCylinder = volume.volCylinder(3.0, 8.0)
  expect(volCylinder).toBe(226.1946710584651)
})

test('Testing on volTriangularPrism', () => {
  const volTriangularPrism = volume.volTriangularPrism(3.0, 6.0, 8.0)
  expect(volTriangularPrism).toBe(72.0)
})

test('Testing on volPentagonalPrism', () => {
  const volPentagonalPrism = volume.volPentagonalPrism(1.0, 4.0, 8.0)
  expect(volPentagonalPrism).toBe(80.0)
})

test('Testing on volSphere', () => {
  const volSphere = volume.volSphere(4.0)
  expect(volSphere).toBe(268.082573106329)
})

test('Testing on volHemisphere', () => {
  const volHemisphere = volume.volHemisphere(4.0)
  expect(volHemisphere).toBe(134.0412865531645)
})


import { LocalMaximomPoint } from '../LocalMaximomPoint'

describe('LocalMaximumPoint tests', () => {
  it('test boundary maximum points - last element', () => {
    const Array = [1, 2, 3, 4, 5, 6, 12]
    expect(LocalMaximomPoint(Array)).toEqual(6)
  })

  it('test boundary maximum points - first element', () => {
    const Array2 = [13, 6, 5, 4, 3, 2, 1]
    expect(LocalMaximomPoint(Array2)).toEqual(0)
  })

  it('test boundary maximum points - should find first maximom point from the top', () => {
    // Test a mix of number types (i.e., positive/negative, numbers with decimals, fractions)
    const Array = [13, 2, 3, 4, 5, 6, 12]
    expect(LocalMaximomPoint(Array)).toEqual(6)
  })

  it('test inner points - second element', () => {
    const Array2 = [13, 16, 5, 4, 3, 2, 1]
    expect(LocalMaximomPoint(Array2)).toEqual(1)
  })

  it('test inner points - element some where in the middle', () => {
    const Array2 = [13, 16, 5, 41, 3, 2, 1]
    expect(LocalMaximomPoint(Array2)).toEqual(3)
  })
})


// Alternative arrange the two given strings in one string in O(n) time complexity.

// Problem Source & Explanation: https://www.geeksforgeeks.org/alternatively-merge-two-strings-in-java/

/**
 * Alternative arrange the two given strings in one string in O(n) time complexity.
 * @param {String} str1 first input string
 * @param {String} str2 second input string
 * @returns `String` return one alternative arrange string.
 */
const AlternativeStringArrange = (str1, str2) => {
  // firstly, check that both inputs are strings.
  if (typeof str1 !== 'string' || typeof str2 !== 'string') {
    return 'Not string(s)'
  }

  // output string value.
  let outStr = ''

  // get first string length.
  const firstStringLength = str1.length
  // get second string length.
  const secondStringLength = str2.length
  // absolute length for operation.
  const absLength = firstStringLength > secondStringLength ? firstStringLength : secondStringLength

  // Iterate the character count until the absolute count is reached.
  for (let charCount = 0; charCount < absLength; charCount++) {
    // If firstStringLength is lesser than the charCount it means they are able to re-arrange.
    if (charCount < firstStringLength) {
      outStr += str1[charCount]
    }

    // If secondStringLength is lesser than the charCount it means they are able to re-arrange.
    if (charCount < secondStringLength) {
      outStr += str2[charCount]
    }
  }

  // return the output string.
  return outStr
}

export { AlternativeStringArrange }


expect(memoUnion(...inputs)).toEqual(new Set([1, 2, 3, 4, 5, 6]))

it('Testing with explicit cache -> LFUCache', () => {

const memoizeFibonacci = memoize(fibonacci, LFU) // added LFU cache explicitly

const fibOfFiveHundred = memoizeFibonacci(500)

const fibOfOneHundred = memoizeFibonacci(100)


/*
Generates a UUID/GUID in Node.Js.
The script uses `Math.random` in combination with the timestamp for better randomness.
The function generate an RFC4122 (https://www.ietf.org/rfc/rfc4122.txt) version 4 UUID/GUID
*/

export const Guid = () => {
  const pattern = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
  let currentDateMilliseconds = new Date().getTime()
  return pattern.replace(/[xy]/g, currentChar => {
    const randomChar = (currentDateMilliseconds + Math.random() * 16) % 16 | 0
    currentDateMilliseconds = Math.floor(currentDateMilliseconds / 16)
    return (currentChar === 'x' ? randomChar : (randomChar & 0x7 | 0x8)).toString(16)
  })
}

// > Guid()
// 'edc848db-3478-1760-8b55-7986003d895f'


import { Graph } from '../Graph2'

describe('Test Graph2', () => {
  const vertices = ['A', 'B', 'C', 'D', 'E', 'F']
  const graph = new Graph(vertices.length)

  // adding vertices
  for (let i = 0; i < vertices.length; i++) {
    graph.addVertex(vertices[i])
  }

  // adding edges
  graph.addEdge('A', 'B')
  graph.addEdge('A', 'D')
  graph.addEdge('A', 'E')
  graph.addEdge('B', 'C')
  graph.addEdge('D', 'E')
  graph.addEdge('E', 'F')
  graph.addEdge('E', 'C')
  graph.addEdge('C', 'F')

  it('Check adjacency lists', () => {
    const mockFn = jest.fn()
    graph.printGraph(mockFn)

    // Expect one call per vertex
    expect(mockFn.mock.calls.length).toBe(vertices.length)

    // Collect adjacency lists from output (call args)
    const adjListArr = mockFn.mock.calls.map(v => v[0])

    expect(adjListArr).toEqual([
      'A -> B D E ',
      'B -> A C ',
      'C -> B E F ',
      'D -> A E ',
      'E -> A D F C ',
      'F -> E C '
    ])
  })
})


/*
 * String Search
 */

function makeTable (str) {
  // create a table of size equal to the length of `str`
  // table[i] will store the prefix of the longest prefix of the substring str[0..i]
  const table = new Array(str.length)
  let maxPrefix = 0
  // the longest prefix of the substring str[0] has length
  table[0] = 0

  // for the substrings the following substrings, we have two cases
  for (let i = 1; i < str.length; i++) {
    // case 1. the current character doesn't match the last character of the longest prefix
    while (maxPrefix > 0 && str.charAt(i) !== str.charAt(maxPrefix)) {
      // if that is the case, we have to backtrack, and try find a character  that will be equal to the current character
      // if we reach 0, then we couldn't find a character
      maxPrefix = table[maxPrefix - 1]
    }
    // case 2. The last character of the longest prefix matches the current character in `str`
    if (str.charAt(maxPrefix) === str.charAt(i)) {
      // if that is the case, we know that the longest prefix at position i has one more character.
      // for example consider `.` be any character not contained in the set [a.c]
      // str = abc....abc
      // consider `i` to be the last character `c` in `str`
      // maxPrefix = will be 2 (the first `c` in `str`)
      // maxPrefix now will be 3
      maxPrefix++
      // so the max prefix for table[9] is 3
    }
    table[i] = maxPrefix
  }
  return table
}

// Find all the words that matches in a given string `str`
export function stringSearch (str, word) {
  // find the prefix table in O(n)
  const prefixes = makeTable(word)
  const matches = []

  // `j` is the index in `P`
  let j = 0
  // `i` is the index in `S`
  let i = 0
  while (i < str.length) {
    // Case 1.  S[i] == P[j] so we move to the next index in `S` and `P`
    if (str.charAt(i) === word.charAt(j)) {
      i++
      j++
    }
    // Case 2.  `j` is equal to the length of `P`
    // that means that we reached the end of `P` and thus we found a match
    // Next we have to update `j` because we want to save some time
    // instead of updating to j = 0 , we can jump to the last character of the longest prefix well known so far.
    // j-1 means the last character of `P` because j is actually `P.length`
    // e.g.
    // S =  a b a b d e
    // P = `a b`a b
    // we will jump to `a b` and we will compare d and a in the next iteration
    // a b a b `d` e
    //     a b `a` b
    if (j === word.length) {
      matches.push(i - j)
      j = prefixes[j - 1]
      // Case 3.
      // S[i] != P[j] There's a mismatch!
    } else if (str.charAt(i) !== word.charAt(j)) {
      // if we  found at least a character in common, do the same thing as in case 2
      if (j !== 0) {
        j = prefixes[j - 1]
      } else {
        // else j = 0, and we can move to the next character S[i+1]
        i++
      }
    }
  }

  return matches
}

// stringSearch('Hello search the position of me', 'pos')


// Use loadModule to allow users to customize their ESLint dependency version.

const { loadModule } = require('@vue/cli-shared-utils')

const eslintPkg = loadModule('eslint/package.json', cwd, true)

// eslint-loader doesn't bust cache when eslint config changes

// so we have to manually generate a cache identifier that takes the config

keep keep keep keep keep replace keep keep keep keep

loadModule('eslint/package.json', cwd, true) ||

const globby = require('globby')

const renamedArrayArgs = {
  ext: 'extensions',
  env: 'envs',
  global: 'globals',
  rulesdir: 'rulePaths',
  plugin: 'plugins',
  'ignore-pattern': 'ignorePattern'
}

const renamedArgs = {
  'inline-config': 'allowInlineConfig',
  rule: 'rules',
  eslintrc: 'useEslintrc',
  c: 'configFile',
  config: 'configFile'
}

module.exports = function lint (args = {}, api) {
  const path = require('path')
  const cwd = api.resolve('.')
  const { log, done, exit, chalk, loadModule } = require('@vue/cli-shared-utils')
  const { CLIEngine } = loadModule('eslint', cwd, true)
  const extensions = require('./eslintOptions').extensions(api)

  const argsConfig = normalizeConfig(args)
  const config = Object.assign({
    extensions,
    fix: true,
    cwd
  }, argsConfig)

  const defaultFilesToLint = [
    'src',
    'tests',
    // root config files
    '*.js',
    // .eslintrc files (ignored by default)
    '.*.js',
    '{src,tests}/**/.*.js'
  ].filter(pattern => globby.sync(path.join(cwd, pattern)).length)

  const engine = new CLIEngine(config)
  const files = args._ && args._.length
    ? args._
    : defaultFilesToLint

  const report = engine.executeOnFiles(files)
  const formatter = engine.getFormatter(args.format || 'codeframe')

  if (config.fix) {
    CLIEngine.outputFixes(report)
  }

  const maxErrors = argsConfig.maxErrors || 0
  const maxWarnings = typeof argsConfig.maxWarnings === 'number' ? argsConfig.maxWarnings : Infinity
  const isErrorsExceeded = report.errorCount > maxErrors
  const isWarningsExceeded = report.warningCount > maxWarnings

  if (!isErrorsExceeded && !isWarningsExceeded) {
    if (!args.silent) {
      const hasFixed = report.results.some(f => f.output)
      if (hasFixed) {
        log(`The following files have been auto-fixed:`)
        log()
        report.results.forEach(f => {
          if (f.output) {
            log(`  ${chalk.blue(path.relative(cwd, f.filePath))}`)
          }
        })
        log()
      }
      if (report.warningCount || report.errorCount) {
        console.log(formatter(report.results))
      } else {
        done(hasFixed ? `All lint errors auto-fixed.` : `No lint errors found!`)
      }
    }
  } else {
    console.log(formatter(report.results))
    if (isErrorsExceeded && typeof argsConfig.maxErrors === 'number') {
      log(`Eslint found too many errors (maximum: ${argsConfig.maxErrors}).`)
    }
    if (isWarningsExceeded) {
      log(`Eslint found too many warnings (maximum: ${argsConfig.maxWarnings}).`)
    }
    exit(1)
  }
}

function normalizeConfig (args) {
  const config = {}
  for (const key in args) {
    if (renamedArrayArgs[key]) {
      config[renamedArrayArgs[key]] = args[key].split(',')
    } else if (renamedArgs[key]) {
      config[renamedArgs[key]] = args[key]
    } else if (key !== '_') {
      config[camelize(key)] = args[key]
    }
  }
  return config
}

function camelize (str) {
  return str.replace(/-(\w)/g, (_, c) => c ? c.toUpperCase() : '')
}


# HTML 和静态资源

## HTML

### Index 文件

`public/index.html` 文件是一个会被 [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin) 处理的模板。在构建过程中，资源链接会被自动注入。另外，Vue CLI 也会自动注入 resource hint (`preload/prefetch`、manifest 和图标链接 (当用到 PWA 插件时) 以及构建过程中处理的 JavaScript 和 CSS 文件的资源链接。

### 插值

因为 index 文件被用作模板，所以你可以使用 [lodash template](https://lodash.com/docs/4.17.10#template) 语法插入内容：

- `<%= VALUE %>` 用来做不转义插值；
- `<%- VALUE %>` 用来做 HTML 转义插值；
- `<% expression %>` 用来描述 JavaScript 流程控制。

除了[被 `html-webpack-plugin` 暴露的默认值](https://github.com/jantimon/html-webpack-plugin#writing-your-own-templates)之外，所有[客户端环境变量](./mode-and-env.md#using-env-variables-in-client-side-code)也可以直接使用。例如，`BASE_URL` 的用法：

``` html
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
```

更多内容可以查阅：
- [baseUrl](../config/#baseurl)

### Preload

[`<link rel="preload">`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Preloading_content) 是一种 resource hint，用来指定页面加载后很快会被用到的资源，所以在页面加载的过程中，我们希望在浏览器开始主体渲染之前尽早 preload。

默认情况下，一个 Vue CLI 应用会为所有初始化渲染需要的文件自动生成 preload 提示。

这些提示会被 [@vue/preload-webpack-plugin](https://github.com/vuejs/preload-webpack-plugin) 注入，并且可以通过 `chainWebpack` 的 `config.plugin('preload')` 进行修改和删除。

### Prefetch

[`<link rel="prefetch">`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Link_prefetching_FAQ) 是一种 resource hint，用来告诉浏览器在页面加载完成后，利用空闲时间提前获取用户未来可能会访问的内容。

默认情况下，一个 Vue CLI 应用会为所有作为 async chunk 生成的 JavaScript 文件 ([通过动态 `import()` 按需 code splitting](https://webpack.js.org/guides/code-splitting/#dynamic-imports) 的产物) 自动生成 prefetch 提示。

这些提示会被 [@vue/preload-webpack-plugin](https://github.com/vuejs/preload-webpack-plugin) 注入，并且可以通过 `chainWebpack` 的 `config.plugin('prefetch')` 进行修改和删除。

示例：

``` js
// vue.config.js
module.exports = {
  chainWebpack: config => {
    // 移除 prefetch 插件
    config.plugins.delete('prefetch')

    // 或者
    // 修改它的选项：
    config.plugin('prefetch').tap(options => {
      options[0].fileBlacklist = options[0].fileBlacklist || []
      options[0].fileBlacklist.push(/myasyncRoute(.)+?\.js$/)
      return options
    })
  }
}
```

当 prefetch 插件被禁用时，你可以通过 webpack 的内联注释手动选定要提前获取的代码区块：

``` js
import(/* webpackPrefetch: true */ './someAsyncComponent.vue')
```

webpack 的运行时会在父级区块被加载之后注入 prefetch 链接。

::: tip 提示
Prefetch 链接将会消耗带宽。如果你的应用很大且有很多 async chunk，而用户主要使用的是对带宽较敏感的移动端，那么你可能需要关掉 prefetch 链接并手动选择要提前获取的代码区块。
:::

### 不生成 index

当基于已有的后端使用 Vue CLI 时，你可能不需要生成 `index.html`，这样生成的资源可以用于一个服务端渲染的页面。这时可以向 [`vue.config.js`](../config/#vue-config-js) 加入下列代码：

``` js
// vue.config.js
module.exports = {
  // 去掉文件名中的 hash
  filenameHashing: false,
  // 删除 HTML 相关的 webpack 插件
  chainWebpack: config => {
    config.plugins.delete('html')
    config.plugins.delete('preload')
    config.plugins.delete('prefetch')
  }
}
```

然而这样做并不是很推荐，因为：

- 硬编码的文件名不利于实现高效率的缓存控制。
- 硬编码的文件名也无法很好的进行 code-splitting (代码分段)，因为无法用变化的文件名生成额外的 JavaScript 文件。
- 硬编码的文件名无法在[现代模式](../guide/browser-compatibility.md#现代模式)下工作。

你应该考虑换用 [indexPath](../config/#indexpath) 选项将生成的 HTML 用作一个服务端框架的视图模板。

### 构建一个多页应用

不是每个应用都需要是一个单页应用。Vue CLI 支持使用 [`vue.config.js` 中的 `pages` 选项](../config/#pages)构建一个多页面的应用。构建好的应用将会在不同的入口之间高效共享通用的 chunk 以获得最佳的加载性能。

## 处理静态资源

静态资源可以通过两种方式进行处理：

- 在 JavaScript 被导入或在 template/CSS 中通过相对路径被引用。这类引用会被 webpack 处理。

- 放置在 `public` 目录下或通过绝对路径被引用。这类资源将会直接被拷贝，而不会经过 webpack 的处理。

### 从相对路径导入

当你在 JavaScript、CSS 或 `*.vue` 文件中使用相对路径 (必须以 `.` 开头) 引用一个静态资源时，该资源将会被包含进入 webpack 的依赖图中。在其编译过程中，所有诸如 `<img src="...">`、`background: url(...)` 和 CSS `@import` 的资源 URL **都会被解析为一个模块依赖**。

例如，`url(./image.png)` 会被翻译为 `require('./image.png')`，而：

``` html
<img src="./image.png">
```

将会被编译到：

``` js
h('img', { attrs: { src: require('./image.png') }})
```

在其内部，我们通过 `file-loader` 用版本哈希值和正确的公共基础路径来决定最终的文件路径，再用 `url-loader` 将小于 4kb 的资源内联，以减少 HTTP 请求的数量。

你可以通过 [chainWebpack](../config/#chainwebpack) 调整内联文件的大小限制。例如，下列代码会将其限制设置为 10kb：

``` js
// vue.config.js
module.exports = {
  chainWebpack: config => {
    config.module
      .rule('images')
        .use('url-loader')
          .loader('url-loader')
          .tap(options => Object.assign(options, { limit: 10240 }))
  }
}
```

### URL 转换规则

- 如果 URL 是一个绝对路径 (例如 `/images/foo.png`)，它将会被保留不变。

- 如果 URL 以 `.` 开头，它会作为一个相对模块请求被解释且基于你的文件系统中的目录结构进行解析。

- 如果 URL 以 `~` 开头，其后的任何内容都会作为一个模块请求被解析。这意味着你甚至可以引用 Node 模块中的资源：

  ``` html
  <img src="~some-npm-package/foo.png">
  ```

- 如果 URL 以 `@` 开头，它也会作为一个模块请求被解析。它的用处在于 Vue CLI 默认会设置一个指向 `<projectRoot>/src` 的别名 `@`。**(仅作用于模版中)**

### `public` 文件夹

任何放置在 `public` 文件夹的静态资源都会被简单的复制，而不经过 webpack。你需要通过绝对路径来引用它们。

注意我们推荐将资源作为你的模块依赖图的一部分导入，这样它们会通过 webpack 的处理并获得如下好处：

- 脚本和样式表会被压缩且打包在一起，从而避免额外的网络请求。
- 文件丢失会直接在编译时报错，而不是到了用户端才产生 404 错误。
- 最终生成的文件名包含了内容哈希，因此你不必担心浏览器会缓存它们的老版本。

`public` 目录提供的是一个**应急手段**，当你通过绝对路径引用它时，留意应用将会部署到哪里。如果你的应用没有部署在域名的根部，那么你需要为你的 URL 配置 [baseUrl](../config/#baseurl) 前缀：

- 在 `public/index.html` 或其它通过 `html-webpack-plugin` 用作模板的 HTML 文件中，你需要通过 `<%= BASE_URL %>` 设置链接前缀：

  ``` html
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  ```

- 在模板中，你首先需要向你的组件传入基础 URL：

  ``` js
  data () {
    return {
      baseUrl: process.env.BASE_URL
    }
  }
  ```

  然后：

  ``` html
  <img :src="`${baseUrl}my-image.png`">
  ```

### 何时使用 `public` 文件夹

- 你需要在构建输出中指定一个文件的名字。
- 你有上千个图片，需要动态引用它们的路径。
- 有些库可能和 webpack 不兼容，这时你除了将其用一个独立的 `<script>` 标签引入没有别的选择。


<template>

      <VueIcon icon="warning" class="big"/>
      {{ $t(error.message) }}

</template>


module.exports = class PromptModuleAPI {
  constructor (creator) {
    this.creator = creator
  }

  injectFeature (feature) {
    this.creator.featurePrompt.choices.push(feature)
  }

  injectPrompt (prompt) {
    this.creator.injectedPrompts.push(prompt)
  }

  injectOptionForPrompt (name, option) {
    this.creator.injectedPrompts.find(f => {
      return f.name === name
    }).choices.push(option)
  }

  onPromptComplete (cb) {
    this.creator.promptCompleteCbs.push(cb)
  }
}


keep keep keep keep replace keep keep keep keep

loadModule('eslint/lib/formatters/codeframe', cwd, true) ||

const path = require('path')

module.exports = (api, options) => {
  if (options.lintOnSave) {
    const extensions = require('./eslintOptions').extensions(api)
    // Use loadModule to allow users to customize their ESLint dependency version.
    const { loadModule } = require('@vue/cli-shared-utils')
    const cwd = api.getCwd()
    const eslintPkg = loadModule('eslint/package.json', cwd, true)

    // eslint-loader doesn't bust cache when eslint config changes
    // so we have to manually generate a cache identifier that takes the config
    // into account.
    const { cacheIdentifier } = api.genCacheConfig(
      'eslint-loader',
      {
        'eslint-loader': require('eslint-loader/package.json').version,
        'eslint': eslintPkg.version
      },
      [
        '.eslintrc.js',
        '.eslintrc.yaml',
        '.eslintrc.yml',
        '.eslintrc.json',
        '.eslintrc',
        'package.json'
      ]
    )

    api.chainWebpack(webpackConfig => {
      webpackConfig.resolveLoader.modules.prepend(path.join(__dirname, 'node_modules'))

      webpackConfig.module
        .rule('eslint')
          .pre()
          .exclude
            .add(/node_modules/)
            .add(require('path').dirname(require.resolve('@vue/cli-service')))
            .end()
          .test(/\.(vue|(j|t)sx?)$/)
          .use('eslint-loader')
            .loader('eslint-loader')
            .options({
              extensions,
              cache: true,
              cacheIdentifier,
              emitWarning: options.lintOnSave !== 'error',
              emitError: options.lintOnSave === 'error',
              formatter: loadModule('eslint/lib/formatters/codeframe', cwd, true)
            })
    })
  }

  api.registerCommand('lint', {
    description: 'lint and fix source files',
    usage: 'vue-cli-service lint [options] [...files]',
    options: {
      '--format [formatter]': 'specify formatter (default: codeframe)',
      '--no-fix': 'do not fix errors',
      '--max-errors [limit]': 'specify number of errors to make build failed (default: 0)',
      '--max-warnings [limit]': 'specify number of warnings to make build failed (default: Infinity)'
    },
    details: 'For more options, see https://eslint.org/docs/user-guide/command-line-interface#options'
  }, args => {
    require('./lint')(args, api)
  })
}


/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file at
 * https://github.com/facebookincubator/create-react-app/blob/master/LICENSE
 */

const opn = require('opn')
const execa = require('execa')
const chalk = require('chalk')
const execSync = require('child_process').execSync

// https://github.com/sindresorhus/opn#app
const OSX_CHROME = 'google chrome'

const Actions = Object.freeze({
  NONE: 0,
  BROWSER: 1,
  SCRIPT: 2
})

function getBrowserEnv () {
  // Attempt to honor this environment variable.
  // It is specific to the operating system.
  // See https://github.com/sindresorhus/opn#app for documentation.
  const value = process.env.BROWSER
  let action
  if (!value) {
    // Default.
    action = Actions.BROWSER
  } else if (value.toLowerCase().endsWith('.js')) {
    action = Actions.SCRIPT
  } else if (value.toLowerCase() === 'none') {
    action = Actions.NONE
  } else {
    action = Actions.BROWSER
  }
  return { action, value }
}

function executeNodeScript (scriptPath, url) {
  const extraArgs = process.argv.slice(2)
  const child = execa('node', [scriptPath, ...extraArgs, url], {
    stdio: 'inherit'
  })
  child.on('close', code => {
    if (code !== 0) {
      console.log()
      console.log(
        chalk.red(
          'The script specified as BROWSER environment variable failed.'
        )
      )
      console.log(chalk.cyan(scriptPath) + ' exited with code ' + code + '.')
      console.log()
      return
    }
  })
  return true
}

function startBrowserProcess (browser, url) {
  // If we're on OS X, the user hasn't specifically
  // requested a different browser, we can try opening
  // Chrome with AppleScript. This lets us reuse an
  // existing tab when possible instead of creating a new one.
  const shouldTryOpenChromeWithAppleScript =
    process.platform === 'darwin' &&
    (typeof browser !== 'string' || browser === OSX_CHROME)

  if (shouldTryOpenChromeWithAppleScript) {
    try {
      // Try our best to reuse existing tab
      // on OS X Google Chrome with AppleScript
      execSync('ps cax | grep "Google Chrome"')
      execSync('osascript openChrome.applescript "' + encodeURI(url) + '"', {
        cwd: __dirname,
        stdio: 'ignore'
      })
      return true
    } catch (err) {
      // Ignore errors.
    }
  }

  // Another special case: on OS X, check if BROWSER has been set to "open".
  // In this case, instead of passing `open` to `opn` (which won't work),
  // just ignore it (thus ensuring the intended behavior, i.e. opening the system browser):
  // https://github.com/facebookincubator/create-react-app/pull/1690#issuecomment-283518768
  if (process.platform === 'darwin' && browser === 'open') {
    browser = undefined
  }

  // Fallback to opn
  // (It will always open new tab)
  try {
    var options = { app: browser }
    opn(url, options).catch(() => {}) // Prevent `unhandledRejection` error.
    return true
  } catch (err) {
    return false
  }
}

/**
 * Reads the BROWSER evironment variable and decides what to do with it. Returns
 * true if it opened a browser or ran a node.js script, otherwise false.
 */
exports.openBrowser = function (url) {
  const { action, value } = getBrowserEnv()
  switch (action) {
  case Actions.NONE:
    // Special case: BROWSER="none" will prevent opening completely.
    return false
  case Actions.SCRIPT:
    return executeNodeScript(value, url)
  case Actions.BROWSER:
    return startBrowserProcess(value, url)
  default:
    throw new Error('Not implemented.')
  }
}


#import "./suggestionFragment.gql"

subscription suggestionAdded {
  suggestionAdded {
    ...suggestion
  }
}


# Создание проекта

## vue create

Для создания нового проекта запустите команду:

``` bash
vue create hello-world
```

::: warning Предупреждение
Если используете Git Bash с minTTY на Windows, то интерактивные подсказки не будут работать. Запускайте команды таким образом `winpty vue.cmd create hello-world`.
:::

Вам будет предложено выбрать пресет настроек. Можно выбрать пресет по умолчанию (default), который добавляет Babel + ESLint, или настройку вручную («Manually select features») для выбора требуемых возможностей в новом проекте.

![скриншот CLI](/cli-new-project.png)

Настройки по умолчанию отлично подходят для быстрого прототипирования нового проекта, в то время как настройка вручную предоставляет больше опций, которые могут потребоваться.

![скриншот CLI](/cli-select-features.png)

При выборе настройки вручную, в самом конце будет также предложено сохранить ваш выбор в качестве нового пресета настроек, чтобы воспользоваться им в будущем. Подробнее пресеты настроек и плагины мы обсудим в следующем разделе.

::: tip ~/.vuerc
Создаваемые пресеты сохраняются в JSON-файле `.vuerc` в домашнем каталоге вашего пользователя. Если вы захотите изменить сохранённые пресеты / настройки, можете это сделать отредактировав этот файл.

В процессе создания проекта, также может быть предложено выбрать предпочитаемый менеджер пакетов или использовать [Taobao зеркало для npm регистра](https://npm.taobao.org/), чтобы ускорить установку зависимостей. Этот выбор также будет сохранён в `~/.vuerc`.
:::

Команда `vue create` предоставляет множество опций — вы можете изучить их все выполнив:

``` bash
vue create --help
```

```
Использование: create [options] <app-name>

создание нового проекта с помощью vue-cli-service

Опции:

  -p, --preset <presetName>       Пропустить подсказки и использовать сохранённый или сторонний пресет настроек
  -d, --default                   Пропустить подсказки и использовать пресет настроек по умолчанию
  -i, --inlinePreset <json>       Пропустить подсказки и использовать вставленную строку JSON в качестве пресета настроек
  -m, --packageManager <command>  Использовать указанный npm клиент при установке зависимостей
  -r, --registry <url>            Использовать указанный npm регистр при установке зависимостей
  -g, --git [message|false]       Форсировать / пропустить инициализацию git, опционально указать сообщение к первому коммиту
  -n, --no-git                    Пропустить инициализацию git
  -f, --force                     Перезаписать целевой каталог, если такой уже есть
  -c, --clone                     Использовать git clone при загрузке стороннего пресета настроек
  -x, --proxy                     Использовать указанный прокси при создании проекта
  -b, --bare                      Развернуть проект не показывая инструкции для новичков
  -h, --help                      Вывод информации об использовании команды
```

## Использование GUI

Вы можете создавать и управлять проектами через графический интерфейс командой `vue ui`:

``` bash
vue ui
```

Выполнив команду откроется окно браузера с графическим интерфейсом, в котором можно пройти те же шаги создания проекта.

![UI preview](/ui-new-project.png)

## Шаблоны для версии 2.x (старое поведение)

Vue CLI 3 использует команду `vue`, поэтому он перезаписывает Vue CLI 2 (`vue-cli`). Если вам по-прежнему необходимо старое поведение и функциональность команды `vue init`, нужно лишь установить глобально дополнительный плагин `@vue/cli-init`:

``` bash
npm install -g @vue/cli-init
# vue init теперь работает точно также, как и в vue-cli@2.x
vue init webpack my-project
```


module.exports = function lint (args = {}, api) {

const { log, done, exit, chalk, loadModule } = require('@vue/cli-shared-utils')

const argsConfig = normalizeConfig(args)

keep keep keep keep replace keep keep keep

const { CLIEngine } = loadModule('eslint', cwd, true) || require('eslint')

@vue/cli-service-global

vue-cli-service global addon for vue-cli


mutation gitCommit ($message: String!) {
  gitCommit (message: $message)
}


#import "./pluginInstallationFragment.gql"

mutation pluginInstall ($id: ID!) {
  pluginInstall (id: $id) {
    ...pluginInstallation
  }
}


// make sure generators are using the latest version of plugins,
// and plugins are using the latest version of deps

const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const request = require('request-promise-native')
const semver = require('semver')
const globby = require('globby')
const { execSync } = require('child_process')
const inquirer = require('inquirer')
const readline = require('readline')

const externalVueScopedPackages = {
  '@vue/test-utils': true,
  '@vue/eslint-config': true
}
const localPackageRE = /'(@vue\/(?:cli|eslint|babel)[\w-]+)': '\^([\w-.]+)'/g

const versionCache = {}

const getRemoteVersion = async (pkg) => {
  if (versionCache[pkg]) {
    return versionCache[pkg]
  }
  let res
  try {
    res = await request(`http://registry.npmjs.org/${pkg}/latest`, { json: true })
  } catch (e) {
    return
  }
  versionCache[pkg] = res.version
  return res.version
}

const getRemoteVersionSync = pkg => {
  if (versionCache[pkg]) {
    return versionCache[pkg]
  }
  const version = execSync(`npm view ${pkg} version`).toString().trim()
  versionCache[pkg] = version
  return version
}

const checkUpdate = (pkg, filePath, local, remote) => {
  if (remote !== local) {
    const isNewer = semver.gt(remote, local)
    if (!isNewer) {
      return false
    }
    const maybeBreaking = !semver.intersects(`^${local}`, `^${remote}`)
    console.log(genUpdateString(pkg, filePath, local, remote, maybeBreaking))
    return true
  }
}

const checkUpdateAsync = async (pkg, filePath, local, remote) => {
  if (remote !== local) {
    const isNewer = semver.gt(remote, local)
    if (!isNewer) {
      return false
    }
    const maybeBreaking = !semver.intersects(`^${local}`, `^${remote}`)
    if (!maybeBreaking) {
      return true
    }
    const { shouldUpdate } = await inquirer.prompt([{
      name: 'shouldUpdate',
      type: 'confirm',
      message: genUpdateString(pkg, filePath, local, remote, maybeBreaking) + `\n` +
        `Update this dependency?`
    }])
    return shouldUpdate
  }
}

function genUpdateString (pkg, filePath, local, remote, maybeBreaking) {
  return `${chalk.cyan(pkg)}: ${local} => ${remote} ` +
    (maybeBreaking ? chalk.red.bold(`maybe breaking `) : ``) +
    chalk.gray(`(${path.relative(process.cwd(), filePath)})`)
}

const writeCache = {}
const bufferWrite = (file, content) => {
  writeCache[file] = content
}
const flushWrite = () => {
  for (const file in writeCache) {
    fs.writeFileSync(file, writeCache[file])
  }
}

async function syncDeps ({ local, version, skipPrompt }) {
  // 1. update all package deps
  const updatedDeps = new Set()

  if (!local) {
    console.log('Syncing remote deps...')
    const packages = await globby(['packages/@vue/*/package.json'])
    const resolvedPackages = (await Promise.all(packages.filter(filePath => {
      return filePath.match(/cli-service|cli-plugin|babel-preset|eslint-config/)
    }).concat('package.json').map(async (filePath) => {
      const pkg = require(path.resolve(__dirname, '../', filePath))
      if (!pkg.dependencies) {
        return
      }
      const deps = pkg.dependencies
      const resolvedDeps = []
      for (const dep in deps) {
        if (dep.match(/^@vue/) && !externalVueScopedPackages[dep]) {
          continue
        }
        let local = deps[dep]
        if (local.charAt(0) !== '^') {
          continue
        }
        local = local.replace(/^\^/, '')
        readline.clearLine(process.stdout)
        readline.cursorTo(process.stdout, 0)
        process.stdout.write(dep)
        const remote = await getRemoteVersion(dep)
        resolvedDeps.push({
          dep,
          local,
          remote
        })
      }
      return {
        pkg,
        filePath,
        resolvedDeps
      }
    }))).filter(_ => _)

    for (const { pkg, filePath, resolvedDeps } of resolvedPackages) {
      let isUpdated = false
      for (const { dep, local, remote } of resolvedDeps) {
        if (remote && await checkUpdateAsync(dep, filePath, local, remote)) {
          pkg.dependencies[dep] = `^${remote}`
          updatedDeps.add(dep)
          isUpdated = true
        }
      }
      if (isUpdated) {
        bufferWrite(filePath, JSON.stringify(pkg, null, 2) + '\n')
      }
    }
  }

  console.log('Syncing local deps...')
  const updatedRE = new RegExp(`'(${Array.from(updatedDeps).join('|')})': '\\^(\\d+\\.\\d+\\.\\d+[^']*)'`)
  const paths = await globby(['packages/@vue/**/*.js'])
  paths
    .filter(p => !/\/files\//.test(p) && !/\/node_modules/.test(p))
    .forEach(filePath => {
      let isUpdated = false
      const makeReplacer = versionGetter => (_, pkg, curVersion) => {
        const targetVersion = versionGetter(pkg)
        if (!targetVersion) return _
        if (checkUpdate(pkg, filePath, curVersion, targetVersion)) {
          isUpdated = true
        }
        return `'${pkg}': '^${targetVersion}'`
      }

      const localReplacer = makeReplacer(
        pkg => {
          try {
            // inline version takes priority
            return version || require(`../packages/${pkg}/package.json`).version
          } catch (e) {}
        }
      )

      const remoteReplacer = makeReplacer(getRemoteVersionSync)

      const updated = fs.readFileSync(filePath, 'utf-8')
        // update @vue packages in this repo
        .replace(localPackageRE, localReplacer)
        // also update vue, vue-template-compiler, vuex, vue-router
        .replace(updatedRE, remoteReplacer)

      if (isUpdated) {
        bufferWrite(filePath, updated)
      }
    })

  if (!Object.keys(writeCache).length) {
    return console.log(`All packages up-to-date.`)
  }

  if (skipPrompt) {
    flushWrite()
    return
  }

  const { yes } = await inquirer.prompt([{
    name: 'yes',
    type: 'confirm',
    message: 'Commit above updates?'
  }])

  if (yes) {
    flushWrite()
  }
}

exports.syncDeps = syncDeps

if (!process.env.VUE_CLI_RELEASE) {
  const args = require('minimist')(process.argv.slice(2))
  syncDeps(args).catch(err => {
    console.log(err)
    process.exit(1)
  })
}


keep keep keep keep replace keep keep keep keep keep


@vue/eslint-config-prettier

eslint-config-prettier for vue-cli