"use strict";

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Various utility functions - parsing RGB from string etc.
 *
 * @author smrceant (Antonin Smrcek)
 *
 * @constructor
 */
var Util = function()
{
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Checks whether the variable (property) is initialized to some non-null value and if it even exists.
 *
 * @param   {*}       variable
 * @returns {boolean}
 */
Util.isInitialized = function (variable)
{
  return ((variable !== null) && Util.isPropertyDefined (variable));
};

/**
 * Checks if the property exists.
 *
 * @param   {*}       property
 * @returns {boolean}
 */
Util.isPropertyDefined = function (property)
{
  return (typeof property !== "undefined");
};

/**
 * Parses string which is representation of the RGB color.
 *
 * @param   {String}                  rgbString       string in the form "120,3,264", "rgb(120,30,264)" and similar
 * @param   {boolean}                 outputAsArray   FALSE if output should be normal array, otherwise as THREE.Vector3 (default)
 * @returns {THREE.Vector3|number[]}                  parsed RGB color vector
 */
Util.parseRGB = function (rgbString, outputAsArray)
{
  var colors = rgbString.split(",");

  // remove non-numeric characters (happens when we feed string like "rgb(10,20,30)", e.g. the first token will be "rgb(10" and thus would give us problems when parsing)
  for (var i = 0; i < colors.length; i++)
  {
    colors[i] = colors[i].replace(/\D/g,'');
  }

  if (Util.isInitialized(outputAsArray) && outputAsArray)
  {
    return [parseInt(colors[0]), parseInt(colors[1]), parseInt(colors[2])];
  }
  else
  {
    return new THREE.Vector3 (parseInt(colors[0]), parseInt(colors[1]), parseInt(colors[2]));
  }
};

/**
 * Converts an array representing RGB value to the CSS representation.
 *
 * @param     {THREE.Vector3}     rgb   array with RGB color
 * @returns   {string}            CSS string representing given RGB value, e.g. "rgb(120,20,46)"
 */
Util.toCssRGB = function (rgb)
{
  return ("rgb("+rgb.x+","+rgb.y+","+rgb.z+")");
};

/**
 * Makes a RGB color lighter. Modifies the color in-place.
 *
 * @param   {THREE.Vector3}     rgb           RGB color
 * @param   {number}            tintFactor    how much lighter the color should be
 * @returns {THREE.Vector3}                   tinted RGB color
 */
Util.tintRGB = function (rgb, tintFactor)
{
  rgb.x = Math.round (rgb.x + (255 - rgb.x) * tintFactor);
  rgb.y = Math.round (rgb.y + (255 - rgb.y) * tintFactor);
  rgb.z = Math.round (rgb.z + (255 - rgb.z) * tintFactor);

  // make sure we stay in the allowed RGB range
  Util.clamp (rgb, 0, 255);

  return rgb;
};

/**
 * Ensures that the value stays in the interval between <lowLimit, highLimit>.
 *
 * @param {number|number[]}     value       value which will be clamped if needed
 * @param {number}              lowLimit    minimum allowable value
 * @param {number}              highLimit   maximum allowable value
 * @returns {number|number[]}               clamped number (in case of array, the values are modified in-place)
 */
Util.clamp = function (value, lowLimit, highLimit)
{
  if (typeof value === "number")
  {
    if (value < lowLimit)  value = lowLimit;
    if (value > highLimit) value = highLimit;
  }
  // array
  else
  {
    for (var i = 0; i < value.length; i++)
    {
      if (value[i] < lowLimit)  value[i] = lowLimit;
      if (value[i] > highLimit) value[i] = highLimit;
    }
  }

  return value;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Collects list of keys in the object, e.g. if object is defined as {Name: 0, Surname: 1, getNameLength: function (... blabla ...)},
 * it will return ["Name", "Surname"].
 *
 * @param   {*}       enumObject    object for which we want to collect its keys as strings
 * @returns {Array}                 array with keys of the object (only string keys)
 */
Util.getEnumKeys = function (enumObject)
{
  var keys = [];

  var keysCandidates = Object.keys ( enumObject );
  //console.log(keysCandidates);
  for (var i = 0; i < keysCandidates.length; i++)
  {
    var currentName = keysCandidates[i];
    var currentObject = enumObject[ currentName ];

    // throw out functions which are in the obtained array too, they do not interest us
    // also throw out all variables starting with a prefix "__" (e.g. "__ACTIVE"), since they are not really keys, but some flags
    if ((typeof currentObject !== "function") && (currentName.indexOf("__") !== 0))
    {
      keys.push (keysCandidates[i]);
    }
  }

  //console.log(enumObject);

  return keys;
};

/**
 * Returns array containing values of keys from given object. Example:
 *
 * Person
 * {
 *    age: 26
 *    name: "Tonda"
 * }
 *
 * will return: [26, "Tonda"]
 *
 * @param {Object}    enumObject
 * @param {string[]}  [keys]      name of keys in the enumObject, will retrieve automatically if not supplied
 */
Util.keyValuesIntoArray = function (enumObject, keys)
{
  if (!Util.isInitialized(keys))
  {
    var keys = Util.getEnumKeys (enumObject);
  }

  var arrayWithKeyValues = [];

  for (var i = 0; i < keys.length; i++)
  {
    arrayWithKeyValues.push( enumObject[ keys[i] ] );
  }

  return arrayWithKeyValues;
};

/**
 * Capitalizes first letter of the string.
 *
 * @param   {string} str    e.g. "walrus"
 * @returns {string}        e.g. "Walrus"
 */
Util.makeFirstLetterCapital = function (str)
{
  return str.charAt(0).toUpperCase() + str.slice(1);
};

/**
 * Example: str = "something_exciting", prefix = "something_, result will be "exciting".
 *
 * @param   {string} str      full string
 * @param   {string} prefix   this will be cut from the start of the string
 * @returns {string}
 */
Util.cutPrefixFromString = function (str, prefix)
{
  return str.substring (prefix.length);
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Extracts filename from its full path without directories and suffix.
 *
 * @param   {string} fullFilenamePath   e.g. "data/yard/model.obj", "model", "model.obj", "../model.obj"
 * @returns {string}
 */
Util.getFilenameFromPath = function (fullFilenamePath)
{
  var extensionDelimeterPos = fullFilenamePath.lastIndexOf(".");
  if (extensionDelimeterPos === -1)
  {
    extensionDelimeterPos = fullFilenamePath.length;
  }

  return fullFilenamePath.substring (fullFilenamePath.lastIndexOf("/") + 1, extensionDelimeterPos);
};

/**
 * Finds out file extension of the given filename.
 *
 * @param   {string} filename   e.g. "model.obj", "model.OBJ"
 * @returns {string}            lowercased extension name, e.g. "obj", or NULL if the filename has no extension
 */
Util.getFileExtension = function (filename)
{
  var extensionDelimeterPos = filename.lastIndexOf(".");

  if (extensionDelimeterPos === -1)
  {
    return null;
  }

  return filename.substring (extensionDelimeterPos + 1).toLowerCase();
};

/**
 * Prints array content.
 *
 * @param {*[]} array
 */
Util.printArray = function (array)
{
  var str = "";
  for (var i = 0; i < array.length; i++)
  {
    str += array[i] + " ";
  }

  console.log(str);
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// GETTERS, SETTERS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Generates __variables for each normal variable in the objectWithKeysAsGetters. For example, for object:
 *
 * Object: =
 * {
 *    firstGetter: 57
 *    secondGetter: "something"
 *
 *    someFunction: function(){... whatever ...}
 * }
 *
 * it will create variables Object.__firstGetter and Object.__secondGetter
 *
 * @param {Object}    objectWithKeysAsGetters   object containing getters as variables
 * @param {string[]}  [keyNames]                array with key names
 */
Util.generatePrivateVariablesForGetters = function (objectWithKeysAsGetters, keyNames)
{
  // no array holding array names were passed, so we will generate it
  if (!Util.isInitialized(keyNames))
  {
    var keyNames = Util.getEnumKeys(objectWithKeysAsGetters);

    objectWithKeysAsGetters.__enumNames = keyNames;
  }

  for (var i = 0; i < keyNames.length; i++)
  {
    var presetName = keyNames[i];

    var weHaveToAddAnotherVariableForGetterToWork = "__"+presetName;

    // copy value of effect flag
    objectWithKeysAsGetters[weHaveToAddAnotherVariableForGetterToWork] = objectWithKeysAsGetters[presetName];
  }
};

/**
 * Getter for object with private getter variables (i.e. we have "variable" and "__variable").
 *
 * @param   {Object} objectWithGetter
 * @param   {string} getterName
 * @returns {*}
 */
Util.getVariable = function (objectWithGetter, getterName)
{
  return objectWithGetter["__"+getterName];
};

/**
 * Setter for object with private getter variables (i.e. we have "variable" and "__variable").
 *
 * @param   {Object}  objectWithSetter
 * @param   {string}  setterName
 * @param   {*}       value             new value to be set
 */
Util.setVariable = function (objectWithSetter, setterName, value)
{
  objectWithSetter["__"+setterName] = value;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Makes a copy of an object. The copy has already some properties defined and it will inherit each property from original
 * that is not yet defined in the copy.
 *
 * @param   {Object}  originalObject    all properties from this object which have not been redefined in inheritingObject will be copied into it
 * @param   {Object}  inheritingObject  contains its own content, and also content from originalObject will be copied into it
 * @returns {Object}                    new version of inheritingObject
 */
Util.inheritUnchangedKeys = function (originalObject, inheritingObject)
{
  var changedKeys = Object.keys (inheritingObject);
  var allKeys = Object.keys (originalObject);

  var inheritedPlusChangedKeys = {};

  for (var i = 0; i < allKeys.length; i++)
  {
    inheritedPlusChangedKeys[ allKeys[i] ] = originalObject[ allKeys[i] ];
  }

  for (var i = 0; i < changedKeys.length; i++)
  {
    inheritedPlusChangedKeys[ changedKeys[i] ] = inheritingObject[ changedKeys[i] ];
  }

  return inheritedPlusChangedKeys;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Converts degrees to radians.
 *
 * @param   {number} deg
 * @returns {number}        deg in radians
 */
Util.degToRad = function (deg)
{
  return deg * Preferences.Math.DEG_TO_RAD_CONVERSION;
};

/**
 * Converts radians to degrees.
 *
 * @param   {number}  rad
 * @returns {number}        rad in degrees
 */
Util.radToDeg = function (rad)
{
  return rad * Preferences.Math.RAD_TO_DEG_CONVERSION;
};

/**
 * Converts time in float to the standard time representation of hour:minutes.
 * Example:
 * 19.5 -> [19,30] (i.e. 19:30)
 * 5.1  -> [5,6]   (i.e. 05:06)
 *
 * @param   {number}    timeFloat24   time in the 24h format, minutes are represented as decimal part
 * @returns {string[]}                time in 24h format, [0] is hours, [1] is muntes
 */
Util.timeInFloatToStandardTime = function (timeFloat24)
{
  var hours = Math.floor (timeFloat24);
  var minutes = (timeFloat24 - hours) * 60;

  if (minutes >= 60)
  {
    minutes = 59;
  }

  // (the slide in the UI has max 24, but the 24 is not on the clock :)
  if (hours === 24)
  {
    hours   = 23;
    minutes = 59;
  }

  hours = hours.toFixed(0);
  minutes = minutes.toFixed(0);

  if (hours <= 9)
  {
    hours = "0" + hours;
  }

  if (minutes <= 9)
  {
    minutes = "0" + minutes;
  }

  return [hours, minutes];
};
