/**
 * @license
 * Copyright 2016 Google Inc. 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.
 */

var Validate = exports;

function InvalidValueError(message) {
  this.message = message;
  this.name = 'InvalidValueError';
  Error.captureStackTrace(this, InvalidValueError);
}
InvalidValueError.prototype = Object.create(Error.prototype);
InvalidValueError.prototype.constructor = InvalidValueError;

InvalidValueError.prepend = function(message, error) {
  if (error instanceof InvalidValueError) {
    return new InvalidValueError(message + ': ' + error.message);
  }
  return error;
};

Validate.InvalidValueError = InvalidValueError;

Validate.acceptAll = function(value) {
  return value;
};

Validate.optional = function(validator) {
  return function(value) {
    return (value == undefined) ? value : validator(value);
  };
};

Validate.that = function(predicate, message) {
  return function(value) {
    if (predicate(value)) return value;
    throw new InvalidValueError(message);
  };
};

Validate.boolean = Validate.that(function(value) {
  return typeof value === 'boolean';
}, 'not a boolean');

Validate.number = Validate.that(function(value) {
  return typeof value === 'number';
}, 'not a number');

Validate.string = Validate.that(function(value) {
  return typeof value === 'string';
}, 'not a string');

Validate.object = function(propertyValidators) {
  return function(object) {
    var result = {};

    if (!object || typeof object !== 'object') {
      throw new InvalidValueError('not an Object');
    }

    // Validate all properties.
    for (key in propertyValidators) {
      var validator = propertyValidators[key];
      try {
        var valid = validator(object[key]);
      } catch (error) {
        if (key in object) {
          throw InvalidValueError.prepend('in property "' + key + '"', error);
        } else {
          throw new InvalidValueError('missing property "' + key + '"');
        }
      }
      if (key in object || valid !== undefined) {
        result[key] = valid;
      }
    }

    // Check for unexpected properties.
    for (var key in object) {
      if (!propertyValidators[key]) {
        throw new InvalidValueError('unexpected property "' + key + '"');
      }
    }

    return result;
  };
};

Validate.array = function(validator) {
  return function(array) {
    var result = [];

    if (Object.prototype.toString.call(array) !== '[object Array]') {
      throw new InvalidValueError('not an Array');
    }

    for (var i = 0; i < array.length; ++i) {
      try {
        result[i] = validator(array[i]);
      } catch (error) {
        throw InvalidValueError.prepend('at index ' + i, error);
      }
    }

    return result;
  };
};

Validate.oneOf = function(names) {
  var myObject = {};
  var quotedNames = [];
  names.forEach(function(name) {
    myObject[name] = true;
    quotedNames.push('"' + name + '"');
  });

  return function(value) {
    if (myObject[value]) return value;
    throw new InvalidValueError('not one of ' + quotedNames.join(', '));
  };
};

Validate.mutuallyExclusiveProperties = function(names) {
  return function(value) {
    if (!value) return value;

    var present = [];
    names.forEach(function(name) {
      if (name in value) {
        present.push('"' + name + '"');
      }
    });

    if (present.length > 1) {
      throw new InvalidValueError(
          'cannot specify properties '
          + present.slice(0, -1).join(', ')
          + ' and '
          + present.slice(-1)
          + ' together');
    }

    return value;
  };
};

Validate.compose = function(validators) {
  return function(value) {
    validators.forEach(function(validate) {
      value = validate(value);
    });
    return value;
  };
};

@@ -1529,118 +1529,8 @@
 %7D;%0A%0A
-Validate.boolean = Validate.that(function(value) %7B%0A  return typeof value === 'boolean';%0A%7D, 'not a boolean');%0A%0A
 Vali
@@ -2383,18 +2383,18 @@
  object 
-%7C%7C
+&&
  valid !
@@ -4131,28 +4131,228 @@
 ;%0A    return value;%0A  %7D;%0A%7D;%0A
+%0AValidate.boolean = Validate.compose(%5B%0A  Validate.that(function(value) %7B%0A    return typeof value === 'boolean';%0A  %7D, 'not a boolean'),%0A  function(value) %7B%0A    return value ? value : undefined;%0A  %7D%0A%5D);

Exclude false values from params since each API treats existence of a value as true