// 
// Copyright (c) Microsoft and contributors.  All rights reserved.
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// 
// See the License for the specific language governing permissions and
// limitations under the License.
// 

// Module dependencies.
var extend = require('extend');

var azureCommon = require('./../../common/common');
var SR = azureCommon.SR;
var validate = azureCommon.validate;
var Constants = azureCommon.Constants;
var TableConstants = Constants.TableConstants;

/**
* Creates a new TableBatch.
*
* @constructor
*/
function TableBatch() {
  this.operations = [];
  this.pk = null;
  this.retrieve = false;
}

/**
* Removes all of the operations from the batch.
*/
TableBatch.prototype.clear = function () {
  this.operations = [];
};

/**
* Returns a boolean value indicating weather there are operations in the batch.
*
* @return {Boolean} True if there are operations queued up; false otherwise.
*/
TableBatch.prototype.hasOperations = function () {
  return this.operations.length > 0;
};

/**
* Returns the number of operations in the batch.
*
* @return {number} The number of operations in the batch.
*/
TableBatch.prototype.size = function () {
  return this.operations.length;
};

/**
* Adds a retrieve operation to the batch. Note that this must be the only operation in the batch.
*
* @param {string}             partitionKey                                    The partition key.
* @param {string}             rowKey                                          The row key.
* @param {object}             [options]                                       The request options.
* @param {string}             [options.payloadFormat]                         The payload format to use for the request.
* @param {TableService~propertyResolver}  [options.propertyResolver]  The property resolver. Given the partition key, row key, property name, property value,
*                                                                             and the property Edm type if given by the service, returns the Edm type of the property.
* @param {Function(entity)} [options.entityResolver]                          The entity resolver. Given the single entity returned by the query, returns a modified object.
*/
TableBatch.prototype.retrieveEntity = function (partitionKey, rowKey, options) {
  var entity = { PartitionKey: {_: partitionKey, $: 'Edm.String'},
    RowKey: {_: rowKey, $: 'Edm.String'},
  };
  this.addOperation(TableConstants.Operations.RETRIEVE, entity, options);
};

/**
* Adds an insert operation to the batch.
*
* @param {object}             entity                                          The entity.
* @param {object}             [options]                                       The request options.
* @param {string}             [options.echoContent]                           Whether or not to return the entity upon a successful insert. Inserts only, default to false.
* @param {string}             [options.payloadFormat]                         The payload format to use for the request.
* @param {TableService~propertyResolver}  [options.propertyResolver]  The property resolver. Only applied if echoContent is true. Given the partition key, row key, property name, 
*                                                                             property value, and the property Edm type if given by the service, returns the Edm type of the property.
* @param {Function(entity)} [options.entityResolver]                          The entity resolver. Only applied if echoContent is true. Given the single entity returned by the insert, returns 
*                                                                             a modified object.
*/
TableBatch.prototype.insertEntity = function (entity, options) {
  this.addOperation(TableConstants.Operations.INSERT, entity, options);
};

/**
* Adds a delete operation to the batch.
*
* @param {object}             entity              The entity.
*/
TableBatch.prototype.deleteEntity = function (entity) {
  this.addOperation(TableConstants.Operations.DELETE, entity);
};

/**
* Adds a merge operation to the batch.
*
* @param {object}             entity              The entity.
*/
TableBatch.prototype.mergeEntity = function (entity) {
  this.addOperation(TableConstants.Operations.MERGE, entity);
};

/**
* Adds an replace operation to the batch.
*
* @param {object}             entity              The entity.
*/
TableBatch.prototype.replaceEntity = function (entity) {
  this.addOperation(TableConstants.Operations.REPLACE, entity);
};

/**
* Adds an insert or replace operation to the batch.
*
* @param {object}             entity              The entity.
*/
TableBatch.prototype.insertOrReplaceEntity = function (entity) {
  this.addOperation(TableConstants.Operations.INSERT_OR_REPLACE, entity);
};

/**
* Adds an insert or merge operation to the batch.
*
* @param {object}             entity              The entity.
*/
TableBatch.prototype.insertOrMergeEntity = function (entity) {
  this.addOperation(TableConstants.Operations.INSERT_OR_MERGE, entity);
};

/**
* Adds an operation to the batch after performing checks.
*
* @param {string}             operationType       The type of operation to perform. See Constants.TableConstants.Operations
* @param {object}             entity              The entity.
* @param {object}             [options]                                       The request options.
*/
TableBatch.prototype.addOperation = function (operationType, entity, options) {
  validate.validateArgs('addOperation', function (v) {
    v.object(entity, 'entity');
    v.object(entity.PartitionKey, 'entity.PartitionKey');
    v.object(entity.RowKey, 'entity.RowKey');
    v.stringAllowEmpty(entity.PartitionKey._, 'entity.PartitionKey._');
    v.stringAllowEmpty(entity.RowKey._, 'entity.RowKey._');
  });

  if(this.operations.length >= 100) {
    throw new Error(SR.BATCH_TOO_LARGE);
  }

  if (operationType === TableConstants.Operations.RETRIEVE) {
    if(this.hasOperations()) {
      throw new Error(SR.BATCH_ONE_RETRIEVE);
    } else {
      this.retrieve = true;
    }
  } else if (this.retrieve) {
    throw new Error(SR.BATCH_ONE_RETRIEVE);
  }

  if (!this.hasOperations()) {
    this.pk = entity.PartitionKey._;
  } else if (entity.PartitionKey._ !== this.pk) {
    throw new Error(SR.BATCH_ONE_PARTITION_KEY);
  }

  var copiedOptions = extend(true, {}, options);
  this.operations.push({type: operationType, entity: entity, options: copiedOptions});
};

/**
* Gets an operation from the batch. Returns null if the index does not exist.
*
* @param {number}             index           The index in the operations array at which to remove an element.
* @return {object}                            The removed operation.
*/
TableBatch.prototype.getOperation = function (index) {
  return this.operations[index];
};

/**
* Removes an operation from the batch. Returns null if the index does not exist.
*
* @param {number}             index           The index in the operations array at which to remove an element.
* @return {object}                            The removed operation.
*/
TableBatch.prototype.removeOperation = function (index) {
  var operation = this.operations.splice(index, 1)[0];

  // if the array is empty, unlock the partition key
  if (!this.hasOperations()) {
    this.pk = null;
    this.retrieve = false;
  }

  return operation;
};

module.exports = TableBatch;
