Source: huepi.js

(function(){
  'use strict';

////////////////////////////////////////////////////////////////////////////////
//
// hue (Philips Wireless Lighting) Api interface for JavaScript
//  +-> HUEPI sounds like Joepie which makes me smile during development...
//
// Requires jQuery 1.5+ for ajax calls and Deferreds
//
////////////////////////////////////////////////////////////////////////////////

/**
 * huepi Object, Entry point for all interaction with Lights etc via the Bridge.
 *
 * @class
 * @alias huepi
 */
var huepi = function() {
  /** @member {string} - version of the huepi interface */
  this.version = '1.2.2';

  /** @member {array} - Array of all Bridges on the local network */
  this.LocalBridges = [];

  /** @member {bool} - get: local network scan in progress / set:proceed with scan */
  this.ScanningNetwork = false;

  /** @member {string} - IP address of the Current(active) Bridge */
  this.BridgeIP = '';
  /** @member {string} - ID (Unique, is MAC address) of the Current(active) Bridge */
  this.BridgeID = '';
  /** @member {string} - Username for Whitelisting, generated by the Bridge */
  this.Username = '';

  /** @member {object} - Cache Hashmap of huepi BridgeID and Whitelisted Username */
  this.BridgeCache = {};
  /** @member {boolean} - Autosave Cache Hasmap of huepi BridgeID and Whitelisted Username */
  this.BridgeCacheAutosave = true;
  this._BridgeCacheLoad(); // Load BridgeCache on creation by Default

  /** @member {object} - Configuration of the Current(active) Bridge */
  this.BridgeConfig = {};
  /** @member {string} - Name of the Current(active) Bridge */
  this.BridgeName = '';

  /** @member {array} - Array of all Lights of the Current(active) Bridge */
  this.Lights = [];
  /** @member {array} - Array of all LightIds of the Current(active) Bridge */
  this.LightIds = [];

  /** @member {array} - Array of all Groups of the Current(active) Bridge */
  this.Groups = [];
  /** @member {array} - Array of all GroupIds of the Current(active) Bridge */
  this.GroupIds = [];

  // To Do: Add Schedules, Scenes, Sensors & Rules manupulation functions, they are read only for now
  /** @member {array} - Array of all Schedules of the Current(active) Bridge, NOTE: There are no Setter functions yet */
  this.Schedules = [];
  /** @member {array} - Array of all Scenes of the Current(active) Bridge, NOTE: There are no Setter functions yet */
  this.Scenes = [];
  /** @member {array} - Array of all Sensors of the Current(active) Bridge, NOTE: There are no Setter functions yet */
  this.Sensors = [];
  /** @member {array} - Array of all Rules of the Current(active) Bridge, NOTE: There are no Setter functions yet */
  this.Rules = [];
};

////////////////////////////////////////////////////////////////////////////////
//
// Detect Running in NodeJS; module exisists and module.exports exists
//  and type of global.process = object process
//
//  requires domino window to create jquery with window attached.
//
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined')
{
  var $;
  var XMLHttpRequest;

  if (typeof global !== 'undefined' && typeof global.process !== 'undefined' &&
   Object.prototype.toString.call(global.process) === '[object process]') {
    $ = require('jquery')(require('domino').createWindow('<html>huepi</html>'));
    XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
    $.support.cors = true; // cross domain, Cross-origin resource sharing
    $.ajaxSettings.xhr = function() {
      return new XMLHttpRequest();
    };
  }
  module.exports = huepi;
} else if (typeof define === 'function' && define.amd) {
  $ = require('jquery')(require('domino').createWindow('<html>huepi</html>'));
  XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
  $.support.cors = true; // cross domain, Cross-origin resource sharing
  $.ajaxSettings.xhr = function() {
    return new XMLHttpRequest();
  };
  define([], function() { return huepi; });
} else {
  $ = jQuery;
  window.huepi = huepi;
}

////////////////////////////////////////////////////////////////////////////////
//
// Private _BridgeCache Functions, Internal Used
//
//

/**
 * Loads the BridgeCache, typically on startup
 */
huepi.prototype._BridgeCacheLoad = function()
{
  this.BridgeCache = { };
  try {
    if (typeof navigator !== 'undefined' && typeof window !== 'undefined') { // running in Browser
      var huepiBridgeCache = localStorage.huepiBridgeCache || '{}';
      this.BridgeCache = JSON.parse(huepiBridgeCache); // Load
    } else if (typeof module !== 'undefined' && module.exports) { // running in NodeJS
      var fs = require('fs');
      var buffer = fs.readFileSync('huepiBridgeCache.json');
      this.BridgeCache = JSON.parse(buffer.toString());
    }
    //console.log('huepi._BridgeCacheLoad()-ed : \n '+ JSON.stringify(this.BridgeCache));
  } catch (error) {
    console.log('Unable to huepi._BridgeCacheLoad() ' + error);
  }
};

huepi.prototype._BridgeCacheAddCurrent = function()
{
  console.log('_BridgeCacheAddCurrent ' + this.BridgeID +' '+ this.Username);
  this.BridgeCache[this.BridgeID] = this.Username;
  if (this.BridgeCacheAutosave) {
    this._BridgeCacheSave();
  }
};

huepi.prototype._BridgeCacheRemoveCurrent = function()
{
  if (this.BridgeCache[this.BridgeID] === this.Username) {
    console.log('_BridgeCacheRemoveCurrent ' + this.BridgeID +' '+ this.Username);
    delete this.BridgeCache[this.BridgeID];
    if (this.BridgeCacheAutosave) {
      this._BridgeCacheSave();
    }
  }
};

/**
 * Selects the first Bridge from LocalBridges found in BridgeCache and stores in BridgeIP
 *  defaults to 1st Bridge in LocalBridges if no bridge from LocalBridges is found in BridgeCache
 *
 * Internally called in PortalDiscoverLocalBridges and NetworkDiscoverLocalBridges
 */
huepi.prototype._BridgeCacheSelectFromLocalBridges = function()
{
  if (this.LocalBridges.length > 0) { // Local Bridges are found
    this.BridgeIP = this.LocalBridges[0].internalipaddress || ''; // Default to 1st Bridge Found
    this.BridgeID = this.LocalBridges[0].id.toLowerCase() || '';
    if (!this.BridgeCache[this.BridgeID]) { // if this.BridgeID not found in BridgeCache
      for (var BridgeNr=1; BridgeNr<this.LocalBridges.length; BridgeNr++) { // Search and store Found
        this.BridgeID = this.LocalBridges[BridgeNr].id.toLowerCase();
        if (this.BridgeCache[this.BridgeID]) {
          this.BridgeIP = this.LocalBridges[BridgeNr].internalipaddress;
          break;
        } else {
          this.BridgeID = '';
        }
      }
    }
  }
  this.Username = this.BridgeCache[this.BridgeID] || '';
};

/**
 * Saves the BridgeCache, typically on Whitelist new Device or Device no longer whitelisted
 *   as is the case with with @BridgeCacheAutosave on @_BridgeCacheAddCurrent and @_BridgeCacheRemoveCurrent
 * NOTE: Saving this cache might be considered a security issue
 * To counter this security issue, arrange your own load/save code with proper encryption
 */
huepi.prototype._BridgeCacheSave = function()
{
  try {
    if (typeof navigator !== 'undefined' && typeof window !== 'undefined') { // running in Browser
      localStorage.huepiBridgeCache = JSON.stringify(this.BridgeCache); // Save
    } else if (typeof module !== 'undefined' && module.exports) { // running in NodeJS
      var fs = require('fs');
      fs.writeFileSync('huepiBridgeCache.json',JSON.stringify(this.BridgeCache));
    }
    //console.log('huepi._BridgeCacheSave()-ed  : \n '+ JSON.stringify(this.BridgeCache));
  } catch (error) {
    console.log('Unable to huepi._BridgeCacheSave() ' + error);
  }
};


////////////////////////////////////////////////////////////////////////////////
//
// Network Functions
//
//

/**
 * Creates the list of hue-Bridges on the local network
 */
 huepi.prototype.NetworkDiscoverLocalBridges = function()
{
  var self = this;
  var LocalIPs = [];
  var OverallDeferred = $.Deferred();
  self.ScanningNetwork = true;
  self.BridgeIP =
  self.BridgeID =
  self.BridgeName =
  self.Username = '';
  self.LocalBridges = [];

  function DiscoverLocalIPs() {
    var IPDeferred = $.Deferred();

    var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
    var PeerConnection = new RTCPeerConnection({iceServers: [] });
    PeerConnection.createDataChannel('');

    PeerConnection.onicecandidate = function(e) {
      if (!e.candidate) {
        PeerConnection.close();
        return IPDeferred.resolve();
      }
      var LocalIP = /^candidate:.+ (\S+) \d+ typ/.exec(e.candidate.candidate)[1];
      if (LocalIPs.indexOf(LocalIP) === -1) {
        LocalIPs.push(LocalIP);
      }
    };
    PeerConnection.createOffer(function(sdp) {
      PeerConnection.setLocalDescription(sdp);
    }, function onerror() {});
    return IPDeferred.promise();
  }

  function DiscoverLocalBridges() {
    var BridgeDeferred = $.Deferred();
    var Parallel = 16;

    function CheckIP(IPAddress) {
      self.BridgeGetConfig(IPAddress, 3000).then(function(data){
        self.LocalBridges.push({'internalipaddress': IPAddress, 'id': data.bridgeid.toLowerCase()});
      }).always(function(){
        var Segment = IPAddress.slice(0, IPAddress.lastIndexOf('.')+1);
        var Nr = parseInt(IPAddress.slice(IPAddress.lastIndexOf('.')+1, IPAddress.length));
        OverallDeferred.notify(Math.floor(100*Nr/255));
        if (self.ScanningNetwork === false) {
          Nr = 256; // Stop scanning if (self.ScanningNetwork = false)
        }
        if ((Nr+Parallel)<256) {
          CheckIP(Segment+(Nr+Parallel));
        } else {
          self.ScanningNetwork = false;
          BridgeDeferred.resolve();
        }
      });
    }

    for (var IPs=0; IPs<LocalIPs.length; IPs++) {
      var InitialIP = LocalIPs[IPs].slice(0, LocalIPs[IPs].lastIndexOf('.')+1);
      for (var P=1; P<=Parallel; P++) {
        CheckIP(InitialIP+P);
      }
    }

    return BridgeDeferred.promise();
  }

  DiscoverLocalIPs().then(function() {
    DiscoverLocalBridges().then(function() {
      if (self.LocalBridges.length > 0) {
        self._BridgeCacheSelectFromLocalBridges();
        OverallDeferred.resolve();
      } else {
        OverallDeferred.reject();
      }
    });
  });

  return OverallDeferred.promise();
};

////////////////////////////////////////////////////////////////////////////////
//
// Portal Functions
//
//

/**
 * Retreives the list of hue-Bridges on the local network from the hue Portal
 */
huepi.prototype.PortalDiscoverLocalBridges = function()
{
  var self = this;
  var deferred = $.Deferred();

  self.BridgeIP =
  self.BridgeID =
  self.BridgeName =
  self.Username = '';
  self.LocalBridges = [];
  $.ajax({ type: 'GET', url: 'https://www.meethue.com/api/nupnp', success: function(data) {
    if (data.length > 0) {
      if (data[0].internalipaddress) { // Bridge(s) Discovered
        self.LocalBridges = data;
        self._BridgeCacheSelectFromLocalBridges();
        deferred.resolve();
      } else {
        deferred.reject();
      }
    } else {
      deferred.reject();
    }
  }, error: function(/*xhr,status,error*/) {
    deferred.reject();
  } });
  return deferred.promise();
};

////////////////////////////////////////////////////////////////////////////////
//
//  Bridge Functions
//
//

/**
 * Function to retreive BridgeConfig before Checking Whitelisting.
 * ONCE call BridgeGetConfig Before BridgeGetData to validate we are talking to a hue Bridge
 * available members (as of 'apiversion': '1.11.0'):
 *   name, apiversion, swversion, mac, bridgeid, replacesbridgeid, factorynew, modelid
 *
 * @param {string} ConfigBridgeIP - Optional BridgeIP to GetConfig from, otherwise uses huepi.BridgeIP (this/self).
 * @param {string} ConfigTimeOut - Optional TimeOut for network request, otherwise uses 60 seconds.
 */
huepi.prototype.BridgeGetConfig = function(ConfigBridgeIP, ConfigTimeOut)
{ // GET /api/config -> data.config.whitelist.username
  var self = this;
  var deferred = $.Deferred();

  ConfigBridgeIP = ConfigBridgeIP || self.BridgeIP;
  ConfigTimeOut = ConfigTimeOut || 60000;
  $.ajax({ type: 'GET', timeout: ConfigTimeOut, url: 'http://' + ConfigBridgeIP + '/api/config/', success: function(data) {
    if (data.bridgeid) {
      if (self.BridgeIP === ConfigBridgeIP) {
        self.BridgeConfig = data;
        if (self.BridgeConfig.bridgeid) // SteveyO/Hue-Emulator doesn't supply bridgeid as of yet.
          self.BridgeID = self.BridgeConfig.bridgeid.toLowerCase();
        else {
          self.BridgeID = '';
        }
        self.BridgeName = self.BridgeConfig.name;
        self.Username = self.BridgeCache[self.BridgeID];
        if (typeof self.Username === 'undefined') {
          self.Username = '';
        }
      }
      deferred.resolve(data);
    } else { // this BridgeIP is not a hue Bridge
      deferred.reject();
    }
  }, error: function(/*xhr,status,error*/) { // $.ajax failed
    deferred.reject();
  } });
  return deferred.promise();
};

/**
 * Function to retreive BridgeDescription before Checking Whitelisting.
 * ONCE call BridgeGetDescription Before BridgeGetData to validate we are talking to a hue Bridge
 *
 * REMARK: Needs a fix of the hue bridge to allow CORS on xml endpoint too, just like on json endpoints already is implemented.
 *
 * @param {string} ConfigBridgeIP - Optional BridgeIP to GetConfig from, otherwise uses huepi.BridgeIP (this/self).
 * @param {string} ConfigTimeOut - Optional TimeOut for network request, otherwise uses 60 seconds.
 */
huepi.prototype.BridgeGetDescription = function(ConfigBridgeIP, ConfigTimeOut)
{ // GET /description.xml -> /device/serialNumber
  var self = this;
  var deferred = $.Deferred();

  ConfigBridgeIP = ConfigBridgeIP || self.BridgeIP;
  ConfigTimeOut = ConfigTimeOut || 60000;

  //$.support.cors = true; // cross domain, Cross-origin resource sharing
  //$.ajaxSetup( { contentType: 'text/plain', xhrFields: { withCredentials: false }, headers: { 'Origin': ConfigBridgeIP } });
  $.ajax({ type: 'GET', timeout: ConfigTimeOut, url: 'http://' + ConfigBridgeIP + '/description.xml', dataType: 'json', success: function(data) {
    var $data = $(data);
    if ($data.find('url').text() === 'hue_logo_0.png') {
      if (ConfigBridgeIP === self.BridgeIP) {
        if ($data.find('serialNumber').text() !== '')
          self.BridgeID = $data.find('serialNumber').text().toLowerCase();
        else {
          self.BridgeID = '';
        }
        self.BridgeName = $data.find('friendlyName').text();
        self.Username = self.BridgeCache[self.BridgeID];
        if (typeof self.Username === 'undefined') {
          // Correct 001788[....]200xxx -> 001788FFFE200XXX short and long serialnumer difference
          self.BridgeID = self.BridgeID.slice(0,6) + 'fffe' + self.BridgeID.slice(6,12);
          self.Username = self.BridgeCache[self.BridgeID];
          if (typeof self.Username === 'undefined') {
            self.Username = '';
          }
        }
      }
      deferred.resolve(data);
    } else { // this BridgeIP is not a hue Bridge
      deferred.reject();
    }
  }, error: function(/*xhr,status,error*/) { // $.ajax failed
    deferred.reject();
  } });
  return deferred.promise();
};

/**
 * Update function to retreive Bridge data and store it in this object.
 * Consider this the main 'Get' function.
 * Typically used for Heartbeat or manual updates of local data.
 */
huepi.prototype.BridgeGetData = function()
{ // GET /api/username -> data.config.whitelist.username
  var self = this;
  var deferred = $.Deferred();

  if (this.Username === '') {
    deferred.reject();
  } else $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username, success: function(data) {
    if (typeof data.config !== 'undefined') { // if able to read Config, Username must be Whitelisted
      self.BridgeConfig = data.config;
      if (self.BridgeConfig.bridgeid) // SteveyO/Hue-Emulator doesn't supply bridgeid as of yet.
        self.BridgeID = self.BridgeConfig.bridgeid.toLowerCase();
      else {
        self.BridgeID = '';
      }
      self.BridgeName = self.BridgeConfig.name;
      self.Lights = data.lights;
      self.LightIds = [];
      for (var key in self.Lights)
        self.LightIds.push(key);
      self.Groups = data.groups;
      self.GroupIds = [];
      for (key in self.Groups)
        self.GroupIds.push(key);
      self.Schedules = data.schedules;
      self.Scenes = data.scenes;
      self.Sensors = data.sensors;
      self.Rules = data.rules;
      self.BridgeName = self.BridgeConfig.name;
      deferred.resolve();
    } else { // Username is no longer whitelisted
      if (self.Username !== '')
        self._BridgeCacheRemoveCurrent();
      self.Username = '';
      deferred.reject();
    }
  }, error: function(/*xhr,status,error*/) { // $.ajax failed
    deferred.reject();
  } });
  return deferred.promise();
};

/**
 * Whitelists the Username stored in this object.
 * Note: a buttonpress on the bridge is requered max 30 sec before this to succeed.
 * please only use this once per device, Username is stored in cache.
 *
 * @param {string} DeviceName - Optional device name to Whitelist.
 */
huepi.prototype.BridgeCreateUser = function(DeviceName)
{ // POST /api {'devicetype': 'AppName#DeviceName' }
  var self = this;
  var deferred = $.Deferred();

  DeviceName = DeviceName || 'WebInterface';
  $.ajax({ type: 'POST', dataType: 'json', contentType: 'application/json', url: 'http://' + this.BridgeIP + '/api',
  data: '{"devicetype": "huepi#' + DeviceName + '"}', success: function(data) {
    if (data[0]) {
      if (data[0].success) {
        self.Username = data[0].success.username;
        self._BridgeCacheAddCurrent();
        deferred.resolve();
      } else {
        deferred.reject();
      }
    } else {
      deferred.reject();
    }
  }, error: function(/*xhr,status,error*/) { // $.ajax failed
    deferred.reject();
  } });
  return deferred.promise();
};

/**
 * @param {string} UsernameToDelete - Username that will be revoked from the Whitelist.
 * Note: Username stored in this object need to be Whitelisted to succeed.
 */
huepi.prototype.BridgeDeleteUser = function(UsernameToDelete)
{ // DELETE /api/username/config/whitelist/username {'devicetype': 'iPhone', 'username': '1234567890'}
  return $.ajax({
    type: 'DELETE',
    dataType: 'json',
    contentType: 'application/json',
    url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/config/whitelist/' + UsernameToDelete,
    //data: '{'devicetype': 'WebInterface', 'username': '' + this.Username + ''}'
  });
};

////////////////////////////////////////////////////////////////////////////////
//
//  Helper Functions
//
//

/**
 * @param {string} Model
 * @returns {boolean} Model is capable of CT
 */
huepi.HelperModelCapableCT = function(Model)
{ // CT Capable	LCT* LLM* LTW* LLC020 LST002
  var ModelType = Model.slice(0,3);

  return ((ModelType === 'LCT') || (ModelType === 'LLM') || (ModelType === 'LTW') || (Model === 'LLC020') || (Model === 'LST002'));
};

/**
* @param {string} Model
* @returns {boolean} Model is capable of XY
*/
huepi.HelperModelCapableXY = function(Model)
{ // XY Capable	LCT* LLC* LST* LLM001 LLC020 LST002
  var ModelType = Model.slice(0,3);

  return ((ModelType === 'LCT') || (ModelType === 'LLC') || (ModelType === 'LST') || (Model === 'LLM001') || (Model === 'LLC020') || (Model === 'LST002'));
};

/**
 * @param {float} Red - Range [0..1]
 * @param {float} Green - Range [0..1]
 * @param {float} Blue - Range [0..1]
 * @returns {object} [Ang, Sat, Bri] - Ranges [0..360] [0..1] [0..1]
 */
huepi.HelperRGBtoHueAngSatBri = function(Red, Green, Blue)
{
  var Ang, Sat, Bri;
  var Min = Math.min(Red, Green, Blue);
  var Max = Math.max(Red, Green, Blue);
  if (Min !== Max) {
    if (Red === Max) {
      Ang = (0 + ((Green - Blue) / (Max - Min))) * 60;
    } else if (Green === Max) {
      Ang = (2 + ((Blue - Red) / (Max - Min))) * 60;
    } else {
      Ang = (4 + ((Red - Green) / (Max - Min))) * 60;
    }
    Sat = (Max - Min) / Max;
    Bri = Max;
  } else { // Max === Min
    Ang = 0;
    Sat = 0;
    Bri = Max;
  }
  return {Ang: Ang, Sat: Sat, Bri: Bri};
};

/**
 * @param {float} Ang - Range [0..360]
 * @param {float} Sat - Range [0..1]
 * @param {float} Bri - Range [0..1]
 * @returns {object} [Red, Green, Blue] - Ranges [0..1] [0..1] [0..1]
 */
huepi.HelperHueAngSatBritoRGB = function(Ang, Sat, Bri)
{ // Range 360, 1, 1, return .Red, .Green, .Blue
  var Red, Green, Blue;
  if (Sat === 0) {
    Red = Bri;
    Green = Bri;
    Blue = Bri;
  } else
  {
    var Sector = Math.floor(Ang / 60) % 6;
    var Fraction = (Ang / 60) - Sector;
    var p = Bri * (1 - Sat);
    var q = Bri * (1 - Sat * Fraction);
    var t = Bri * (1 - Sat * (1 - Fraction));
    switch (Sector) {
      case 0:
        Red = Bri;
        Green = t;
        Blue = p;
        break;
      case 1:
        Red = q;
        Green = Bri;
        Blue = p;
        break;
      case 2:
        Red = p;
        Green = Bri;
        Blue = t;
        break;
      case 3:
        Red = p;
        Green = q;
        Blue = Bri;
        break;
      case 4:
        Red = t;
        Green = p;
        Blue = Bri;
        break;
      default: // case 5:
        Red = Bri;
        Green = p;
        Blue = q;
        break;
    }
  }
  return {Red: Red, Green: Green, Blue: Blue};
};

/**
 * @param {float} Red - Range [0..1]
 * @param {float} Green - Range [0..1]
 * @param {float} Blue - Range [0..1]
 * @returns {number} Temperature ranges [2200..6500]
 */
huepi.HelperRGBtoColortemperature = function(Red, Green, Blue)
{ // Approximation from https://github.com/neilbartlett/color-temperature/blob/master/index.js
  var Temperature;
  var TestRGB;
  var Epsilon = 0.4;
  var MinTemperature = 2200;
  var MaxTemperature = 6500;

  while ( (MaxTemperature - MinTemperature) > Epsilon) {
    Temperature = (MaxTemperature + MinTemperature) / 2;
    TestRGB = huepi.HelperColortemperaturetoRGB(Temperature);
    if ((TestRGB.Blue / TestRGB.Red) >= (Blue / Red)) {
      MaxTemperature = Temperature;
    } else {
      MinTemperature = Temperature;
    }
  }
  return Math.round(Temperature);
};

/**
 * @param {number} Temperature ranges [1000..6600]
 * @returns {object} [Red, Green, Blue] ranges [0..1] [0..1] [0..1]
 */
huepi.HelperColortemperaturetoRGB = function(Temperature)
{ // http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
  // Update Available: https://github.com/neilbartlett/color-temperature/blob/master/index.js
  var Red, Green, Blue;

  Temperature = Temperature / 100;
  if (Temperature <= 66)
    Red = /*255;*/ 165+90*((Temperature)/(66));
  else {
    Red = Temperature - 60;
    Red = 329.698727466 * Math.pow(Red, -0.1332047592);
    if (Red < 0)
      Red = 0;
    if (Red > 255)
      Red = 255;
  }
  if (Temperature <= 66) {
    Green = Temperature;
    Green = 99.4708025861 * Math.log(Green) - 161.1195681661;
    if (Green < 0)
      Green = 0;
    if (Green > 255)
      Green = 255;
  } else {
    Green = Temperature - 60;
    Green = 288.1221695283 * Math.pow(Green, -0.0755148492);
    if (Green < 0)
      Green = 0;
    if (Green > 255)
      Green = 255;
  }
  if (Temperature >= 66)
    Blue = 255;
  else {
    if (Temperature <= 19)
      Blue = 0;
    else {
      Blue = Temperature - 10;
      Blue = 138.5177312231 * Math.log(Blue) - 305.0447927307;
      if (Blue < 0)
        Blue = 0;
      if (Blue > 255)
        Blue = 255;
    }
  }
  return {Red: Red/255, Green: Green/255, Blue: Blue/255};
};

/**
 * @param {float} Red - Range [0..1]
 * @param {float} Green - Range [0..1]
 * @param {float} Blue - Range [0..1]
 * @returns {object} [x, y] - Ranges [0..1] [0..1]
 */
huepi.HelperRGBtoXY = function(Red, Green, Blue)
{ // Source: https://github.com/PhilipsHue/PhilipsHueSDK-iOS-OSX/blob/master/ApplicationDesignNotes/RGB%20to%20xy%20Color%20conversion.md
  // Apply gamma correction
  if (Red > 0.04045)
    Red = Math.pow((Red + 0.055) / (1.055), 2.4);
  else
    Red = Red / 12.92;
  if (Green > 0.04045)
    Green = Math.pow((Green + 0.055) / (1.055), 2.4);
  else
    Green = Green / 12.92;
  if (Blue > 0.04045)
    Blue = Math.pow((Blue + 0.055) / (1.055), 2.4);
  else
    Blue = Blue / 12.92;
  // RGB to XYZ [M] for Wide RGB D65, http://www.developers.meethue.com/documentation/color-conversions-rgb-xy
  var X = Red * 0.664511 + Green * 0.154324 + Blue * 0.162028;
  var Y = Red * 0.283881 + Green * 0.668433 + Blue * 0.047685;
  var Z = Red * 0.000088 + Green * 0.072310 + Blue * 0.986039;
  // But we don't want Capital X,Y,Z you want lowercase [x,y] (called the color point) as per:
  if ((X + Y + Z) === 0)
    return {x: 0, y: 0};
  return {x: X / (X + Y + Z), y: Y / (X + Y + Z)};
};

/**
 * @param {float} x
 * @param {float} y
 * @param {float} Brightness Optional
 * @returns {object} [Red, Green, Blue] - Ranges [0..1] [0..1] [0..1]
 */
huepi.HelperXYtoRGB = function(x, y, Brightness)
{ // Source: https://github.com/PhilipsHue/PhilipsHueSDK-iOS-OSX/blob/master/ApplicationDesignNotes/RGB%20to%20xy%20Color%20conversion.md
  Brightness = Brightness || 1.0; // Default full brightness
  var z = 1.0 - x - y;
  var Y = Brightness;
  var X = (Y / y) * x;
  var Z = (Y / y) * z;
  // XYZ to RGB [M]-1 for Wide RGB D65, http://www.developers.meethue.com/documentation/color-conversions-rgb-xy
  var Red   =  X * 1.656492 - Y * 0.354851 - Z * 0.255038;
  var Green = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
  var Blue  =  X * 0.051713 - Y * 0.121364 + Z * 1.011530;
  // Limit RGB on [0..1]
  if (Red > Blue && Red > Green && Red > 1.0) { // Red is too big
    Green = Green / Red;
    Blue = Blue / Red;
    Red = 1.0;
  }
  if (Red < 0)
    Red = 0;
  if (Green > Blue && Green > Red && Green > 1.0) { // Green is too big
    Red = Red / Green;
    Blue = Blue / Green;
    Green = 1.0;
  }
  if (Green < 0)
    Green = 0;
  if (Blue > Red && Blue > Green && Blue > 1.0) { // Blue is too big
    Red = Red / Blue;
    Green = Green / Blue;
    Blue = 1.0;
  }
  if (Blue < 0)
    Blue = 0;
  // Apply reverse gamma correction
  if (Red <= 0.0031308) {
    Red = Red * 12.92;
  } else {
    Red = 1.055 * Math.pow(Red, (1.0 / 2.4)) - 0.055;
  }
  if (Green <= 0.0031308) {
    Green = Green * 12.92;
  } else {
    Green = 1.055 * Math.pow(Green, (1.0 / 2.4)) - 0.055;
  }
  if (Blue <= 0.0031308) {
    Blue = Blue * 12.92;
  } else {
    Blue = 1.055 * Math.pow(Blue, (1.0 / 2.4)) - 0.055;
  }
  // Limit RGB on [0..1]
  if (Red > Blue && Red > Green && Red > 1.0) { // Red is too big
    Green = Green / Red;
    Blue = Blue / Red;
    Red = 1.0;
  }
  if (Red < 0)
    Red = 0;
  if (Green > Blue && Green > Red && Green > 1.0) { // Green is too big
    Red = Red / Green;
    Blue = Blue / Green;
    Green = 1.0;
  }
  if (Green < 0)
    Green = 0;
  if (Blue > Red && Blue > Green && Blue > 1.0) { // Blue is too big
    Red = Red / Blue;
    Green = Green / Blue;
    Blue = 1.0;
  }
  if (Blue < 0)
    Blue = 0;
  return {Red: Red, Green: Green, Blue: Blue};
};

/**
 * @param {float} x
 * @param {float} y
 * @param {float} Brightness Optional
 * @param {string} Model - Modelname of the Light
 * @returns {object} [Red, Green, Blue] - Ranges [0..1] [0..1] [0..1]
 */
huepi.HelperXYtoRGBforModel = function(x, y, Brightness, Model)
{
  var GamutCorrected = huepi.HelperGamutXYforModel(x, y, Model);
  return huepi.HelperXYtoRGB(GamutCorrected.x, GamutCorrected.y, Brightness);
};

/**
 * Tests if the Px,Py resides within the Gamut for the model.
 * Otherwise it will calculated the closesed point on the Gamut.
 * @param {float} Px - Range [0..1]
 * @param {float} Py - Range [0..1]
 * @param {string} Model - Modelname of the Light to Gamutcorrect Px, Py for
 * @returns {object} [x, y] - Ranges [0..1] [0..1]
 */
huepi.HelperGamutXYforModel = function(Px, Py, Model)
{ // https://developers.meethue.com/documentation/supported-lights
  Model = Model || 'LCT001'; // default hue Bulb 2012
  var ModelType = Model.slice(0,3);
  var PRed, PGreen, PBlue;
  var NormDot;

  if ( ((ModelType === 'LST') || (ModelType === 'LLC')) && (Model !=='LLC020') && (Model !=='LLC002') && (Model !== 'LST002') ) { // For LivingColors Bloom, Aura and Iris etc the triangle corners are:
    PRed = {x: 0.704, y: 0.296}; // Gamut A
    PGreen = {x: 0.2151, y: 0.7106};
    PBlue = {x: 0.138, y: 0.080};
  } else if ( ((ModelType === 'LCT') || (ModelType === 'LLM')) && (Model !=='LCT010') && (Model !=='LCT014') && (Model !=='LCT011')  && (Model !=='LCT012') ) { // For the hue bulb and beyond led modules etc the corners of the triangle are:
    PRed = {x: 0.675, y: 0.322}; // Gamut B
    PGreen = {x: 0.409, y: 0.518};
    PBlue = {x: 0.167, y: 0.040};
  } else { // Exceptions and Unknown default to
    PRed = {x: 0.692, y: 0.308}; // Gamut C
    PGreen = {x: 0.17, y: 0.7};
    PBlue = {x: 0.153, y: 0.048};
  }

  var VBR = {x: PRed.x - PBlue.x, y: PRed.y - PBlue.y}; // Blue to Red
  var VRG = {x: PGreen.x - PRed.x, y: PGreen.y - PRed.y}; // Red to Green
  var VGB = {x: PBlue.x - PGreen.x, y: PBlue.y - PGreen.y}; // Green to Blue

  var GBR = (PGreen.x - PBlue.x) * VBR.y - (PGreen.y - PBlue.y) * VBR.x; // Sign Green on Blue to Red
  var BRG = (PBlue.x - PRed.x) * VRG.y - (PBlue.y - PRed.y) * VRG.x; // Sign Blue on Red to Green
  var RGB = (PRed.x - PGreen.x) * VGB.y - (PRed.y - PGreen.y) * VGB.x; // Sign Red on Green to Blue

  var VBP = {x: Px - PBlue.x, y: Py - PBlue.y}; // Blue to Point
  var VRP = {x: Px - PRed.x, y: Py - PRed.y}; // Red to Point
  var VGP = {x: Px - PGreen.x, y: Py - PGreen.y}; // Green to Point

  var PBR = VBP.x * VBR.y - VBP.y * VBR.x; // Sign Point on Blue to Red
  var PRG = VRP.x * VRG.y - VRP.y * VRG.x; // Sign Point on Red to Green
  var PGB = VGP.x * VGB.y - VGP.y * VGB.x; // Sign Point on Green to Blue

  if ((GBR * PBR >= 0) && (BRG * PRG >= 0) && (RGB * PGB >= 0)) // All Signs Match so Px,Py must be in triangle
    return {x: Px, y: Py};

  //  Outside Triangle, Find Closesed point on Edge or Pick Vertice...
  else if (GBR * PBR <= 0) { // Outside Blue to Red
    NormDot = (VBP.x * VBR.x + VBP.y * VBR.y) / (VBR.x * VBR.x + VBR.y * VBR.y);
    if ((NormDot >= 0.0) && (NormDot <= 1.0)) // Within Edge
      return {x: PBlue.x + NormDot * VBR.x, y: PBlue.y + NormDot * VBR.y};
    else if (NormDot < 0.0) // Outside Edge, Pick Vertice
      return {x: PBlue.x, y: PBlue.y}; // Start
    else
      return {x: PRed.x, y: PRed.y}; // End
  }
  else if (BRG * PRG <= 0) { // Outside Red to Green
    NormDot = (VRP.x * VRG.x + VRP.y * VRG.y) / (VRG.x * VRG.x + VRG.y * VRG.y);
    if ((NormDot >= 0.0) && (NormDot <= 1.0)) // Within Edge
      return {x: PRed.x + NormDot * VRG.x, y: PRed.y + NormDot * VRG.y};
    else if (NormDot < 0.0) // Outside Edge, Pick Vertice
      return {x: PRed.x, y: PRed.y}; // Start
    else
      return {x: PGreen.x, y: PGreen.y}; // End
  }
  else if (RGB * PGB <= 0) { // Outside Green to Blue
    NormDot = (VGP.x * VGB.x + VGP.y * VGB.y) / (VGB.x * VGB.x + VGB.y * VGB.y);
    if ((NormDot >= 0.0) && (NormDot <= 1.0)) // Within Edge
      return {x: PGreen.x + NormDot * VGB.x, y: PGreen.y + NormDot * VGB.y};
    else if (NormDot < 0.0) // Outside Edge, Pick Vertice
      return {x: PGreen.x, y: PGreen.y}; // Start
    else
      return {x: PBlue.x, y: PBlue.y}; // End
  }
};

/**
 * @param {float} Ang - Range [0..360]
 * @param {float} Sat - Range [0..1]
 * @param {float} Bri - Range [0..1]
 * @returns {number} Temperature ranges [2200..6500]
 */
huepi.HelperHueAngSatBritoColortemperature = function(Ang, Sat, Bri)
{
  var RGB = huepi.HelperHueAngSatBritoRGB(Ang, Sat, Bri);
  return huepi.HelperRGBtoColortemperature(RGB.Red, RGB.Green, RGB.Blue);
};

/**
 * @param {number} Temperature ranges [1000..6600]
 * @returns {object} [Ang, Sat, Bri] - Ranges [0..360] [0..1] [0..1]
 */
huepi.HelperColortemperaturetoHueAngSatBri = function(Temperature)
{
  var RGB = huepi.HelperColortemperaturetoRGB(Temperature);
  return huepi.HelperRGBtoHueAngSatBri(RGB.Red, RGB.Green, RGB.Blue);
};

/**
 * @param {float} x
 * @param {float} y
 * @param {float} Brightness Optional
 * @returns {number} Temperature ranges [1000..6600]
 */
huepi.HelperXYtoColortemperature = function(x, y, Brightness)
{
  var RGB = huepi.HelperXYtoRGB(x, y, Brightness);
  return huepi.HelperRGBtoColortemperature(RGB.Red, RGB.Green, RGB.Blue);
};

/**
 * @param {number} Temperature ranges [1000..6600]
 * @returns {object} [x, y] - Ranges [0..1] [0..1]
 */
huepi.HelperColortemperaturetoXY = function(Temperature)
{
  var RGB = huepi.HelperColortemperaturetoRGB(Temperature);
  return huepi.HelperRGBtoXY(RGB.Red, RGB.Green, RGB.Blue);
};

/**
 * @param {number} CT in Mired (micro reciprocal degree)
 * @returns {number} ColorTemperature
 */
 huepi.HelperCTtoColortemperature = function(CT)
{
  return Math.round(1000000 / CT);
};

/**
 * @param {number} ColorTemperature
 * @returns {number} CT in Mired (micro reciprocal degree)
 */
 huepi.HelperColortemperaturetoCT = function(Temperature)
{
  return Math.round(1000000 / Temperature);
};

/**
 * @param {multiple} Items - Items to convert to StringArray
 * @returns {string} String array containing Items
 */
huepi.HelperToStringArray = function(Items) {
  if (typeof Items === 'number') {
    return '"' + Items.toString() + '"';
  } else if (Object.prototype.toString.call(Items) === '[object Array]') {
    var Result = '[';
    for (var ItemNr = 0; ItemNr < Items.length; ItemNr++) {
      Result += huepi.HelperToStringArray(Items[ItemNr]);
      if (ItemNr < Items.length - 1)
        Result += ',';
    }
    Result = Result + ']';
    return Result;
  } else if (typeof Items === 'string') {
    return '"' + Items + '"';
  }
};

////////////////////////////////////////////////////////////////////////////////
//
// huepi.Lightstate Object
//
//

/**
 * huepi.Lightstate Object.
 * Internal object to recieve all settings that are about to be send to the Bridge as a string.
 *
 * @class
 */
huepi.Lightstate = function()
{
///** */
////this.SetOn = function(On) {
//  this.on = On;
//};
  /** */
  this.On = function() {
    this.on = true;
    return this;
  };
  /** */
  this.Off = function() {
    this.on = false;
    return this;
  };
  /*
   * @param {number} Hue Range [0..65535]
   * @param {float} Saturation Range [0..255]
   * @param {float} Brightness Range [0..255]
   */
  this.SetHSB = function(Hue, Saturation, Brightness) { // Range 65535, 255, 255
    this.hue = Math.round(Hue);
    this.sat = Math.round(Saturation);
    this.bri = Math.round(Brightness);
    return this;
  };
  /**
   * @param {number} Hue Range [0..65535]
   */
  this.SetHue = function(Hue) {
    this.hue = Math.round(Hue);
    return this;
  };
  /**
   * @param {float} Saturation Range [0..255]
   */
  this.SetSaturation = function(Saturation) {
    this.sat = Math.round(Saturation);
    return this;
  };
  /**
   * @param {float} Brightness Range [0..255]
   */
  this.SetBrightness = function(Brightness) {
    this.bri = Math.round(Brightness);
    return this;
  };
  /**
   * @param {float} Ang Range [0..360]
   * @param {float} Sat Range [0..1]
   * @param {float} Bri Range [0..1]
   */
  this.SetHueAngSatBri = function(Ang, Sat, Bri) {
    // In: Hue in Deg, Saturation, Brightness 0.0-1.0 Transform To Philips Hue Range...
    while (Ang < 0)
      Ang = Ang + 360;
    Ang = Ang % 360;
    return this.SetHSB(Math.round(Ang / 360 * 65535), Math.round(Sat * 255), Math.round(Bri * 255));
  };
  /**
   * @param {number} Red Range [0..1]
   * @param {number} Green Range [0..1]
   * @param {number} Blue Range [0..1]
   */
  this.SetRGB = function(Red, Green, Blue) {
    var HueAngSatBri = huepi.HelperRGBtoHueAngSatBri(Red, Green, Blue);
    return this.SetHueAngSatBri(HueAngSatBri.Ang, HueAngSatBri.Sat, HueAngSatBri.Bri);
  };
  /**
   * @param {number} Ct Micro Reciprocal Degree of Colortemperature (Ct = 10^6 / Colortemperature)
   */
  this.SetCT = function(Ct) {
    this.ct = Math.round(Ct);
    return this;
  };
  /**
   * @param {number} Colortemperature Range [2200..6500] for the 2012 lights
   */
  this.SetColortemperature = function(Colortemperature) {
    return this.SetCT(huepi.HelperColortemperaturetoCT(Colortemperature));
  };
  /**
   * @param {float} X
   * @param {float} Y
   */
  this.SetXY = function(X, Y) {
    this.xy = [X, Y];
    return this;
  };
///** */
//this.SetAlert = function(Alert) {
//  this.alert = Alert;
//};
  /** */
  this.AlertSelect = function() {
    this.alert = 'select';
    return this;
  };
  /** */
  this.AlertLSelect = function() {
    this.alert = 'lselect';
    return this;
  };
  /** */
  this.AlertNone = function() {
    this.alert = 'none';
    return this;
  };
///** */
//this.SetEffect = function(Effect) {
//  this.effect = Effect;
//};
  /** */
  this.EffectColorloop = function() {
    this.effect = 'colorloop';
    return this;
  };
  /** */
  this.EffectNone = function() {
    this.effect = 'none';
    return this;
  };
  /**
   * @param {number} Transitiontime Optional Transitiontime in multiple of 100ms, defaults to 4 (on bridge, meaning 400 ms)
   */
  this.SetTransitiontime = function(Transitiontime) {
    if (typeof Transitiontime !== 'undefined') // Optional Parameter
      this.transitiontime = Transitiontime;
    return this;
  };
  /**
   * @returns {string} Stringified version of the content of LightState ready to be sent to the Bridge.
   */
  this.Get = function() {
    return JSON.stringify(this);
  };

};

////////////////////////////////////////////////////////////////////////////////
//
// Light Functions
//
//


/**
 * @param {number} LightNr - LightNr
 * @returns {string} LightId
 */
huepi.prototype.LightGetId = function(LightNr)
{
  if (typeof LightNr  === 'number')
    if (LightNr <= this.LightIds.length)
      return this.LightIds[LightNr-1];
  return LightNr;
};

/**
 * @param {string} LightId - LightId
 * @returns {number} LightNr
 */
huepi.prototype.LightGetNr = function(LightId)
{
  if (typeof LightId  === 'string')
   return this.LightIds.indexOf(LightId) + 1;
  return LightId;
};

/**
 */
huepi.prototype.LightsGetData = function()
{ // GET /api/username/lights
  var self = this;

  return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/lights', success: function(data) {
    if (data) {
      self.Lights = data;
      self.LightIds = [];
      for (var key in self.Lights)
        self.LightIds.push(key);
      }
    }
  });
};

/**
 */
huepi.prototype.LightsSearchForNew = function()
{ // POST /api/username/lights
  return $.ajax({
    type: 'POST',
    dataType: 'json',
    contentType: 'application/json',
    url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/lights'
  });
};

/**
 */
huepi.prototype.LightsGetNew = function()
{ // GET /api/username/lights/new
  return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/lights/new', success: function(/*data*/) {} });
};

/**
 * @param {number} LightNr
 * @param {string} Name New name of the light Range [1..32]
 */
huepi.prototype.LightSetName = function(LightNr, Name)
{ // PUT /api/username/lights
  return $.ajax({
    type: 'PUT',
    dataType: 'json',
    contentType: 'application/json',
    url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/lights/' + this.LightGetId(LightNr),
    data: '{"name" : "' + Name + '"}'
  });
};

/**
 * @param {number} LightNr
 * @param {LightState} State
 */
huepi.prototype.LightSetState = function(LightNr, State)
{ // PUT /api/username/lights/[LightNr]/state
  return $.ajax({
    type: 'PUT',
    dataType: 'json',
    contentType: 'application/json',
    url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/lights/' + this.LightGetId(LightNr) + '/state',
    data: State.Get()
  });
};

/**
 * @param {number} LightNr
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightOn = function(LightNr, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.On();
  State.SetTransitiontime(Transitiontime);
  return this.LightSetState(LightNr, State);
};

/**
 * @param {number} LightNr
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightOff = function(LightNr, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.Off();
  State.SetTransitiontime(Transitiontime);
  return this.LightSetState(LightNr, State);
};

/**
 * Sets Gamut Corrected values for HSB
 * @param {number} LightNr
 * @param {number} Hue Range [0..65535]
 * @param {number} Saturation Range [0..255]
 * @param {number} Brightness Range [0..255]
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightSetHSB = function(LightNr, Hue, Saturation, Brightness, Transitiontime)
{
  var HueAng = Hue * 360 / 65535;
  var Sat = Saturation / 255;
  var Bri = Brightness / 255;

  var Color = huepi.HelperHueAngSatBritoRGB(HueAng, Sat, Bri);
  var Point = huepi.HelperRGBtoXY(Color.Red, Color.Green, Color.Blue);
  return $.when(
  this.LightSetBrightness(LightNr, Brightness, Transitiontime),
  this.LightSetXY(LightNr, Point.x, Point.y, Transitiontime)
  );
};

/**
 * @param {number} LightNr
 * @param {number} Hue Range [0..65535]
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightSetHue = function(LightNr, Hue, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.SetHue(Hue);
  State.SetTransitiontime(Transitiontime);
  return this.LightSetState(LightNr, State);
};

/**
 * @param {number} LightNr
 * @param Saturation Range [0..255]
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightSetSaturation = function(LightNr, Saturation, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.SetSaturation(Saturation);
  State.SetTransitiontime(Transitiontime);
  return this.LightSetState(LightNr, State);
};

/**
 * @param {number} LightNr
 * @param Brightness Range [0..255]
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightSetBrightness = function(LightNr, Brightness, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.SetBrightness(Brightness);
  State.SetTransitiontime(Transitiontime);
  return this.LightSetState(LightNr, State);
};

/**
 * @param {number} LightNr
 * @param Ang Range [0..360]
 * @param Sat Range [0..1]
 * @param Bri Range [0..1]
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightSetHueAngSatBri = function(LightNr, Ang, Sat, Bri, Transitiontime)
{ // In: Hue in Deg, Saturation, Brightness 0.0-1.0 Transform To Philips Hue Range...
  while (Ang < 0)
    Ang = Ang + 360;
  Ang = Ang % 360;
  return this.LightSetHSB(LightNr, Ang / 360 * 65535, Sat * 255, Bri * 255, Transitiontime);
};

/**
 * @param {number} LightNr
 * @param Red Range [0..1]
 * @param Green Range [0..1]
 * @param Blue Range [0..1]
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightSetRGB = function(LightNr, Red, Green, Blue, Transitiontime)
{
  var Point = huepi.HelperRGBtoXY(Red, Green, Blue);
  var HueAngSatBri = huepi.HelperRGBtoHueAngSatBri(Red, Green, Blue);
  return $.when(
  this.LightSetBrightness(LightNr, HueAngSatBri.Bri * 255),
  this.LightSetXY(LightNr, Point.x, Point.y, Transitiontime)
  );
};

/**
 * @param {number} LightNr
 * @param {number} CT micro reciprocal degree
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightSetCT = function(LightNr, CT, Transitiontime)
{
  var Model = this.Lights[this.LightGetId(LightNr)].modelid;

  if (huepi.HelperModelCapableCT(Model)) {
    var State = new huepi.Lightstate();
    State.SetCT(CT);
    State.SetTransitiontime(Transitiontime);
    return this.LightSetState(LightNr, State);
  } else if (huepi.HelperModelCapableXY(Model))
  {  // hue CT Incapable Lights: CT->RGB->XY to ignore Brightness in RGB}
    var Color = huepi.HelperColortemperaturetoRGB(huepi.HelperCTtoColortemperature(CT));
    var Point = huepi.HelperRGBtoXY(Color.Red, Color.Green, Color.Blue);
    return this.LightSetXY(LightNr, Point.x, Point.y, Transitiontime);
  }
};

/**
 * @param {number} LightNr
 * @param {number} Colortemperature Range [2200..6500] for the 2012 model
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightSetColortemperature = function(LightNr, Colortemperature, Transitiontime)
{
  return this.LightSetCT(LightNr, huepi.HelperColortemperaturetoCT(Colortemperature), Transitiontime);
};

/**
 * @param {number} LightNr
 * @param {float} X
 * @param {float} Y
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightSetXY = function(LightNr, X, Y, Transitiontime)
{
  var Model = this.Lights[this.LightGetId(LightNr)].modelid;

  if (huepi.HelperModelCapableXY(Model)) {
    var State = new huepi.Lightstate();
    var Gamuted = huepi.HelperGamutXYforModel(X, Y, Model);
    State.SetXY(Gamuted.x, Gamuted.y);
    State.SetTransitiontime(Transitiontime);
    return this.LightSetState(LightNr, State);
  } else if (huepi.HelperModelCapableCT(Model)) {
    // hue XY Incapable Lights: XY->RGB->CT to ignore Brightness in RGB
    var Color = huepi.HelperXYtoRGB(X, Y);
    var Colortemperature = huepi.HelperRGBtoColortemperature(Color.Red, Color.Green, Color.Blue);
    return this.LightSetColortemperature(LightNr, Colortemperature, Transitiontime);
  }
};

/**
 * @param {number} LightNr
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightAlertSelect = function(LightNr, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.AlertSelect();
  State.SetTransitiontime(Transitiontime);
  return this.LightSetState(LightNr, State);
};

/**
 * @param {number} LightNr
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightAlertLSelect = function(LightNr, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.AlertLSelect();
  State.SetTransitiontime(Transitiontime);
  return this.LightSetState(LightNr, State);
};

/**
 * @param {number} LightNr
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightAlertNone = function(LightNr, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.AlertNone();
  State.SetTransitiontime(Transitiontime);
  return this.LightSetState(LightNr, State);
};

/**
 * @param {number} LightNr
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightEffectColorloop = function(LightNr, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.EffectColorloop();
  State.SetTransitiontime(Transitiontime);
  return this.LightSetState(LightNr, State);
};

/**
 * @param {number} LightNr
 * @param {number} Transitiontime optional
 */
huepi.prototype.LightEffectNone = function(LightNr, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.EffectNone();
  State.SetTransitiontime(Transitiontime);
  return this.LightSetState(LightNr, State);
};

////////////////////////////////////////////////////////////////////////////////
//
// Group Functions
//
//

/**
 * @param {number} GroupNr - GroupNr
 * @returns {string} GroupId
 */
huepi.prototype.GroupGetId = function(GroupNr)
{
  if (typeof GroupNr  === 'number')
    if (GroupNr === 0)
      return '0';
    else if (GroupNr > 0)
      if (GroupNr <= this.GroupIds.length)
        return this.GroupIds[GroupNr-1];
  return GroupNr;
};

/**
 * @param {string} GroupId - GroupId
 * @returns {number} GroupNr
 */
huepi.prototype.GroupGetNr = function(GroupId)
{
  if (typeof GroupId  === 'string')
   return this.GroupIds.indexOf(GroupId) + 1;
  return GroupId;
};

/**
 */
huepi.prototype.GroupsGetData = function()
{ // GET /api/username/groups
  var self = this;

  return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups', success: function(data) {
    if (data) {
      self.Groups = data;
      self.GroupIds = [];
      for (var key in self.Groups)
        self.GroupIds.push(key);
      }
    }
  });
};

/**
 */
huepi.prototype.GroupsGetZero = function()
{ // GET /api/username/groups/0
  var self = this;

  return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups/0', success: function(data) {
    if (data) {
      self.Groups['0'] = data;
      }
    }
  });
};

/**
 * Note: Bridge doesn't accept lights in a Group that are unreachable at moment of creation
 * @param {string} Name New name of the light Range [1..32]
 * @param {multiple} Lights LightNr or Array of Lights to Group
 */
huepi.prototype.GroupCreate = function(Name, Lights)
{ // POST /api/username/groups
  return $.ajax({
    type: 'POST',
    dataType: 'json',
    contentType: 'application/json',
    url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups/',
    data: '{"name": "' + Name + '" , "lights":' + huepi.HelperToStringArray(Lights) + '}'
  });
};

/**
 * @param {number} GroupNr
 * @param {string} Name New name of the light Range [1..32]
 */
huepi.prototype.GroupSetName = function(GroupNr, Name)
{ // PUT /api/username/groups/[GroupNr]
  return $.ajax({
    type: 'PUT',
    dataType: 'json',
    contentType: 'application/json',
    url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups/' + this.GroupGetId(GroupNr),
    data: '{"name": "' + Name + '"}'
  });
};

/**
 * Note: Bridge doesn't accept lights in a Group that are unreachable at moment of creation
 * @param {number} GroupNr
 * @param {multiple} Lights LightNr or Array of Lights to Group
 */
huepi.prototype.GroupSetLights = function(GroupNr, Lights)
{ // PUT /api/username/groups/[GroupNr]
  return $.ajax({
    type: 'PUT',
    dataType: 'json',
    contentType: 'application/json',
    url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups/' + this.GroupGetId(GroupNr),
    data: '{"lights":' + huepi.HelperToStringArray(Lights) + '}'
  });
};

/**
 * Note: Bridge doesn't accept lights in a Group that are unreachable at moment of creation
 * @param {number} GroupNr
 * @param {string} Name New name of the light Range [1..32]
 * @param {multiple} Lights LightNr or Array of Lights to Group
 */
huepi.prototype.GroupSetAttributes = function(GroupNr, Name, Lights)
{ // PUT /api/username/groups/[GroupNr]
  return $.ajax({
    type: 'PUT',
    dataType: 'json',
    contentType: 'application/json',
    url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups/' + this.GroupGetId(GroupNr),
    data: '{"name": "' +Name + '", "lights":' + huepi.HelperToStringArray(Lights) + '}'
  });
};

/**
 * @param {number} GroupNr
 */
huepi.prototype.GroupDelete = function(GroupNr)
{ // DELETE /api/username/groups/[GroupNr]
  return $.ajax({
    type: 'DELETE',
    dataType: 'json',
    contentType: 'application/json',
    url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups/' + this.GroupGetId(GroupNr)
  });
};

/**
 * @param {number} GroupNr
 * @param {LightState} State
 */
huepi.prototype.GroupSetState = function(GroupNr, State)
{ // PUT /api/username/groups/[GroupNr]/action
  return $.ajax({
    type: 'PUT',
    dataType: 'json',
    contentType: 'application/json',
    url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups/' + this.GroupGetId(GroupNr) + '/action',
    data: State.Get()
  });
};

/**
 * @param {number} GroupNr
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupOn = function(GroupNr, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.On();
  State.SetTransitiontime(Transitiontime);
  return this.GroupSetState(GroupNr, State);
};

/**
 * @param {number} GroupNr
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupOff = function(GroupNr, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.Off();
  State.SetTransitiontime(Transitiontime);
  return this.GroupSetState(GroupNr, State);
};

/**
 * Sets Gamut Corrected values for HSB
 * @param {number} GroupNr
 * @param {number} Hue Range [0..65535]
 * @param {number} Saturation Range [0..255]
 * @param {number} Brightness Range [0..255]
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupSetHSB = function(GroupNr, Hue, Saturation, Brightness, Transitiontime)
{
  var Ang = Hue * 360 / 65535;
  var Sat = Saturation / 255;
  var Bri = Brightness / 255;

  var Color = huepi.HelperHueAngSatBritoRGB(Ang, Sat, Bri);
  var Point = huepi.HelperRGBtoXY(Color.Red, Color.Green, Color.Blue);

  return $.when(// return Deferred when of both Brightness and XY
  this.GroupSetBrightness(GroupNr, Brightness, Transitiontime),
  this.GroupSetXY(GroupNr, Point.x, Point.y, Transitiontime)
  );
};

/**
 * @param {number} GroupNr
 * @param {number} Hue Range [0..65535]
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupSetHue = function(GroupNr, Hue, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.SetHue(Hue);
  State.SetTransitiontime(Transitiontime);
  return this.GroupSetState(GroupNr, State);
};

/**
 * @param {number} GroupNr
 * @param Saturation Range [0..255]
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupSetSaturation = function(GroupNr, Saturation, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.SetSaturation(Saturation);
  State.SetTransitiontime(Transitiontime);
  return this.GroupSetState(GroupNr, State);
};

/**
 * @param {number} GroupNr
 * @param Brightness Range [0..255]
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupSetBrightness = function(GroupNr, Brightness, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.SetBrightness(Brightness);
  State.SetTransitiontime(Transitiontime);
  return this.GroupSetState(GroupNr, State);
};

/**
 * @param {number} GroupNr
 * @param Ang Range [0..360]
 * @param Sat Range [0..1]
 * @param Bri Range [0..1]
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupSetHueAngSatBri = function(GroupNr, Ang, Sat, Bri, Transitiontime)
{
  while (Ang < 0)
    Ang = Ang + 360;
  Ang = Ang % 360;
  return this.GroupSetHSB(GroupNr, Ang / 360 * 65535, Sat * 255, Bri * 255, Transitiontime);
};

/**
 * @param {number} GroupNr
 * @param Red Range [0..1]
 * @param Green Range [0..1]
 * @param Blue Range [0..1]
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupSetRGB = function(GroupNr, Red, Green, Blue, Transitiontime)
{
  var HueAngSatBri = huepi.HelperRGBtoHueAngSatBri(Red, Green, Blue);
  return this.GroupSetHueAngSatBri(GroupNr, HueAngSatBri.Ang, HueAngSatBri.Sat, HueAngSatBri.Bri, Transitiontime);
};

/**
 * @param {number} GroupNr
 * @param {number} CT micro reciprocal degree
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupSetCT = function(GroupNr, CT, Transitiontime)
{
  var Lights = [];

  GroupNr = this.GroupGetId(GroupNr);
  if (GroupNr === '0') // All Lights
    Lights = this.LightIds;
  else
    Lights = this.Groups[GroupNr].lights;

  if (Lights.length !== 0) {
    var deferreds = [];
    for (var LightNr=0; LightNr<Lights.length; LightNr++)
      deferreds.push(this.LightSetCT(Lights[LightNr], CT, Transitiontime));
    return $.when.apply($, deferreds); // return Deferred when with array of deferreds
  }
  // No Lights in Group GroupNr, Set State of Group to let Bridge create the API Error and return it.
  var State = new huepi.Lightstate();
  State.SetCT(CT);
  State.SetTransitiontime(Transitiontime);
  return this.GroupSetState(GroupNr, State);
};

/**
 * @param {number} GroupNr
 * @param {number} Colortemperature Range [2200..6500] for the 2012 model
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupSetColortemperature = function(GroupNr, Colortemperature, Transitiontime)
{
  return this.GroupSetCT(GroupNr, huepi.HelperColortemperaturetoCT(Colortemperature), Transitiontime);
};

/**
 * @param {number} GroupNr
 * @param {float} X
 * @param {float} Y
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupSetXY = function(GroupNr, X, Y, Transitiontime)
{
  var Lights = [];

  GroupNr = this.GroupGetId(GroupNr);
  if (GroupNr === '0') // All Lights
    Lights = this.LightIds;
  else
    Lights = this.Groups[GroupNr].lights;

  if (Lights.length !== 0) {
    var deferreds = [];
    for (var LightNr=0; LightNr<Lights.length; LightNr++)
      deferreds.push(this.LightSetXY(Lights[LightNr], X, Y, Transitiontime));
    return $.when.apply($, deferreds); // return Deferred when with array of deferreds
  }
  // No Lights in Group GroupNr, Set State of Group to let Bridge create the API Error and return it.
  var State = new huepi.Lightstate();
  State.SetXY(X, Y);
  State.SetTransitiontime(Transitiontime);
  return this.GroupSetState(GroupNr, State);
};

/**
 * @param {number} GroupNr
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupAlertSelect = function(GroupNr, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.AlertSelect();
  State.SetTransitiontime(Transitiontime);
  return this.GroupSetState(GroupNr, State);
};

/**
 * @param {number} GroupNr
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupAlertLSelect = function(GroupNr, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.AlertLSelect();
  State.SetTransitiontime(Transitiontime);
  return this.GroupSetState(GroupNr, State);
};

/**
 * @param {number} GroupNr
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupAlertNone = function(GroupNr, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.AlertNone();
  State.SetTransitiontime(Transitiontime);
  return this.GroupSetState(GroupNr, State);
};

/**
 * @param {number} GroupNr
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupEffectColorloop = function(GroupNr, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.EffectColorloop();
  State.SetTransitiontime(Transitiontime);
  return this.GroupSetState(GroupNr, State);
};

/**
 * @param {number} GroupNr
 * @param {number} Transitiontime optional
 */
huepi.prototype.GroupEffectNone = function(GroupNr, Transitiontime)
{
  var State = new huepi.Lightstate();
  State.EffectNone();
  State.SetTransitiontime(Transitiontime);
  return this.GroupSetState(GroupNr, State);
};

////////////////////////////////////////////////////////////////////////////////
//
// Schedule Functions
//
//

/**
 */
huepi.prototype.SchedulesGetData = function()
{ // GET /api/username/schedules
  var self = this;

  return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/schedules', success: function(data) {
    if (data) {
      self.Schedules = data;
      }
    }
  });
};

////////////////////////////////////////////////////////////////////////////////
//
// Scenes Functions
//
//

/**
 */
huepi.prototype.ScenesGetData = function()
{ // GET /api/username/scenes
  var self = this;

  return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/scenes', success: function(data) {
    if (data) {
      self.Scenes = data;
      }
    }
  });
};

////////////////////////////////////////////////////////////////////////////////
//
// Sensors Functions
//
//

/**
 */
huepi.prototype.SensorsGetData = function()
{ // GET /api/username/sensors
  var self = this;

  return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/sensors', success: function(data) {
    if (data) {
      self.Sensors = data;
      }
    }
  });
};

////////////////////////////////////////////////////////////////////////////////
//
// Rules Functions
//
//

/**
 */
huepi.prototype.RulesGetData = function()
{ // GET /api/username/rules
  var self = this;

  return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/rules', success: function(data) {
    if (data) {
      self.Rules = data;
      }
    }
  });
};


return huepi;


})();