'use strict';

var net = require('net');

/**
 * Constructor
 * @param {Object} httpAdapter Http Adapter
 * @param {Object} options     Options (language, client_id, client_secret)
 */
var AGOLGeocoder = function AGOLGeocoder(httpAdapter, options) {

  if (!httpAdapter || httpAdapter == 'undefined') {
    throw new Error('ArcGis Online Geocoder requires a httpAdapter to be defined');
  }

  if (!options || options == 'undefined') {
    options = {};
  }

  if (!options.client_id || options.client_id == 'undefined') {
    options.client_id = null;
  }

  if (!options.client_secret || options.client_secret == 'undefined') {
    options.client_secret = null;
  }

  if (!options.client_secret || !options.client_id) {

    throw new Error('You must specify the client_id and the client_secret');
  }

  this.options = options;

  this.httpAdapter = httpAdapter;
  this.cache = {};
};

AGOLGeocoder.prototype._authEndpoint = 'https://www.arcgis.com/sharing/oauth2/token';
AGOLGeocoder.prototype._endpoint = 'http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find';
AGOLGeocoder.prototype._reverseEndpoint = 'http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode';

//Cached vars


AGOLGeocoder.prototype._cachedToken = {
  'now': function() {
    return (new Date()).getTime();
  },
  'put': function(token, experation,cache) {
    cache.token = token;
    //Shave 30 secs off experation to ensure that we expire slightly before the actual expiration
    cache.tokenExp = this.now() + (experation - 30);
  },
  'get' : function(cache) {
    if(!cache) {
      return null;
    }

    if(this.now() <= cache.tokenExp) {
      return cache.token;
    } else {
      return null;
    }
  }
};

AGOLGeocoder.prototype._getToken = function(callback) {
  var _this = this;

  if(_this._cachedToken.get(_this.cache) !== null) {
    callback(_this._cachedToken.get());
    return;
    }

  var params = {
    'grant_type': 'client_credentials',
    'client_id': _this.options.client_id,
    'client_secret': _this.options.client_secret
  };

  _this.httpAdapter.get(_this._authEndpoint, params, function(err, result) {
    if (err) {
      return callback(err);
    } else {
      result = JSON.parse(result);
      var tokenExpiration = (new Date()).getTime() + result.expires_in;
      var token = result.access_token;
      _this._cachedToken.put(token,tokenExpiration,_this.cache);

      callback(false, token);
    }
  });
};

/**
 * Geocode
 * @param {String}   value    Value to geocode (Address)
 * @param {Function} callback Callback method
 */
AGOLGeocoder.prototype.geocode = function(value, callback) {
  var _this = this;

  if (net.isIP(value)) {
    throw new Error('The AGOL geocoder does not support IP addresses');
  }

  if (value instanceof Array) {
    //As defined in http://resources.arcgis.com/en/help/arcgis-rest-api/#/Batch_geocoding/02r300000003000000/
    throw new Error('An ArcGIS Online organizational account is required to use the batch geocoding functionality');
  }

  var execute = function (value,token,callback) {
    var params = {
      'token':token,
      'f':'json',
      'text':value,
      'outFields': 'AddNum,StPreDir,StName,StType,City,Postal,Region,Country'
    };

    _this.httpAdapter.get(_this._endpoint, params, function(err, result) {
      result = JSON.parse(result);
        if (err) {
          return callback(err);
        } else {
          //This is to work around ESRI's habit of returning 200 OK for failures such as lack of authentication
          if(result.error){
            callback(result.error);

            return null;
          }

          var results = [];
          for(var i = 0; i < result.locations.length; i++) {
            results.push(_this._formatResult(result.locations[i]));
          }

          results.raw = result;
          callback(false, results);
        }
    });
  };

  this._getToken(function(err,token) {
    if (err) {
      return callback(err);
    } else {
      execute(value,token,callback);
    }
  });
};

AGOLGeocoder.prototype._formatResult = function(result) {
  if(result.address){
    return {
      'latitude' : result.location.y,
      'longitude' : result.location.x,
      'country' : result.address.CountryCode,
      'city' : result.address.City,
      'state' : result.address.Region,
      'zipcode' : result.address.Postal,
      'countryCode' : result.address.CountryCode,
      'address': result.address.Address,
      'neighborhood': result.address.Neighborhood,
      'loc_name': result.address.Loc_name
    };
  }

  var country = null;
  var countryCode = null;
  var city = null;
  var state = null;
  var stateCode = null;
  var zipcode = null;
  var streetPreDir = null;
  var streetType = null;
  var streetName = null;
  var streetNumber = null;

  var attributes = result.feature.attributes;
  for (var property in attributes) {
    if (attributes.hasOwnProperty(property)) {
      if(property == 'City') {
        city = attributes[property];
      }
      if(property == 'Postal') {
        zipcode = attributes[property];
      }
      if(property == 'Region') {
        state = attributes[property];
      }
      if(property == 'StPreDir') {
        streetPreDir = attributes[property];
      }
      if(property == 'AddNum') {
        streetNumber = attributes[property];
      }
      if(property == 'StName') {
        streetName = attributes[property];
      }
      if(property == 'StType') {
        streetType = attributes[property];
      }
      if(property == 'Country') {
        countryCode = attributes[property];
        country = attributes[property];
      }
    }
  }

  return {
    'latitude' : result.feature.geometry.y,
    'longitude' : result.feature.geometry.x,
    'country' : country,
    'city' : city,
    'state' : state,
    'stateCode' : stateCode,
    'zipcode' : zipcode,
    'streetName': streetPreDir + ' ' + streetName + ' ' + streetType,
    'streetNumber' : streetNumber,
    'countryCode' : countryCode
  };
};

/**
 * Reverse geocoding
 * @param {lat:<number>,lon:<number>}  lat: Latitude, lon: Longitude
 * @param {function} callback Callback method
 */
AGOLGeocoder.prototype.reverse = function(query, callback) {
  var lat = query.lat;
  var long = query.lon;

  var _this = this;

  var execute = function (lat,long,token,callback) {
    var params = {
      'token':token,
      'f':'json',
      'location' : long + ',' + lat,
      'outFields': 'AddrNum,StPreDir,StName,StType,City,Postal,Region,Country'
    };

    _this.httpAdapter.get(_this._reverseEndpoint, params, function(err, result) {
      result = JSON.parse(result);
      if (err) {
        return callback(err);
      } else {
        //This is to work around ESRI's habit of returning 200 OK for failures such as lack of authentication
        if(result.error){
          callback(result.error,{raw:result});
          return null;
        }

        var results = [];
        results.push(_this._formatResult(result));

        results.raw = result;
        callback(false, results);
      }
    });
  };

  this._getToken(function(err,token) {
    if (err) {
      return callback(err);
    } else {
      execute(lat,long,token,callback);
    }
  });
};

module.exports = AGOLGeocoder;
