'use strict';

var q = require("q");
var geocodingProvider;
var httpAdapter = 'https';
var UtilGeoJSON = require('../../../shared/utils/UtilGeoJSON');
var logger = require('../../utils/Logger').loggers.get('db');
var isGeocodingDisabled = false;
var _ = require('lodash/object');

function addressIsEmpty(document, field) {
    return !UtilGeoJSON.hasValues(document[field.name]);
}

function hasFeatures(document, field) {
    return document[field.name] &&
        document[field.name].features &&
        document[field.name].features[0];
}

function hasCoordinates(document, field) {
    return hasFeatures(document, field) &&
        document[field.name].features[0].geometry &&
        document[field.name].features[0].geometry.coordinates;
}

function hasProperty(document, field) {
    // check that any of the fields are present that make up the property. The check needs to check each level so that an error
    // isn't thrown if one level doesn't exist
    return (hasFeatures(document, field) && document[field.name].features[0].properties);
}

function hasLocation(document, field) {
    // both a latitude and longitude need to be present for the location to be valid so return true only if both are present
    return hasCoordinates(document, field) &&
        document[field.name].features[0].geometry.coordinates[0] &&
        document[field.name].features[0].geometry.coordinates[1];
}

function hasEnoughAddressData(document, field) {
    // a minimum of a suburb or postcode need to be returned
    return hasFeatures(document, field) &&
        document[field.name].features[0].properties &&
        document[field.name].features[0].properties.suburb &&
        document[field.name].features[0].properties.postcode ;
}

function shouldLookupCoordinates(document, field) {
    if(isGeocodingDisabled)
        return false;
    return hasProperty(document, field) && !hasLocation(document, field) && hasEnoughAddressData(document, field);
}

function shouldLookupAddress(document, field) {
    if(isGeocodingDisabled)
        return false;
    return hasLocation(document, field) && !hasEnoughAddressData(document, field);
}

function convertCoordinateStringToNumber(document, field, coordinateIndex) {
    // use typeof to check for an empty string, which returns false when checking for the value
    if (document[field.name].features[0].geometry.coordinates &&
        (document[field.name].features[0].geometry.coordinates[coordinateIndex] ||
        typeof document[field.name].features[0].geometry.coordinates[coordinateIndex] === 'string')) {
        var value = parseFloat(document[field.name].features[0].geometry.coordinates[coordinateIndex]);

        document[field.name].features[0].geometry.coordinates[coordinateIndex] = value;
    }
}

function convertCoordinatesToNumbers(document, field) {
    var valid = true;
    // convert string coordinates to numbers. They can be strings if read from csv imports or other integrations.
    // the parseFloat function will work if it is already a number
    if (hasCoordinates(document, field)) {
        // convert longitude, which is in position 0
        convertCoordinateStringToNumber(document, field, 0);

        // convert latitude, which is in position 1
        convertCoordinateStringToNumber(document, field, 1);

        if (isNaN(document[field.name].features[0].geometry.coordinates[0]) ||
            isNaN(document[field.name].features[0].geometry.coordinates[1])) {
            // remove the values. 0 can't be used for coordinate values
            // because it's a valid value. Remove the entire coordinates because it isn't valid
            // with one invalid value and deleting from the array shifts the position of existing values
            delete document[field.name].features[0].geometry.coordinates[1];
            delete document[field.name].features[0].geometry.coordinates[0];

            valid = false;
        }
    }

    return valid;
}

function correctStructure(document, field) {
    if (!addressIsEmpty(document, field)){
        if(typeof document[field.name].type == 'undefined')  {
            document[field.name].type = "FeatureCollection";
        }

        if(typeof document[field.name].features != 'undefined') {
            for (var i = 0; i < document[field.name].features.length; i++) {
                if(typeof document[field.name].features[i].type == 'undefined') {
                    document[field.name].features[i].type = "Feature";
                }
            }
        }
    }

    return document;
}

module.exports.preProcess = function(field, document) {
    var deferred = q.defer();
    var geocodingConfig = config.get("geocoding");

    if (!geocodingProvider) {
        if (geocodingConfig && geocodingConfig.lookupProvider) {
            geocodingProvider = require(geocodingConfig.lookupProvider);
        }
        else {
            logger.info("No geocoding provider configured. Addresses will not be looked up on the server");
            document = correctStructure(document, field);
            deferred.resolve(document);
            return deferred.promise;
        }
    }

    // var extraGeocodingParams = {
    //     apiKey: config.get('googleMapsKey'),
    //     formatter: null
    // };
    //var geocoder = require('node-geocoder')(geocoderProvider, httpAdapter, extraGeocodingParams);

    var apiKey = geocodingConfig.apiKey;

    isGeocodingDisabled = (config.get('disableServerGeocoding') === undefined) ? false : config.get('disableServerGeocoding');

    if (addressIsEmpty(document, field)) {
        // delete the address if no fields are present, so that it won't be stored
//        delete document[field.name];
        deferred.resolve(document);
    } else if (shouldLookupCoordinates(document, field)) {
        // look up the position if the latitude and longitude are not present
        var streetNo = document[field.name].features[0].properties.streetNo;
        var street = document[field.name].features[0].properties.street;
        var suburb = document[field.name].features[0].properties.suburb;
        var postalCode = document[field.name].features[0].properties.postcode;
        var lookupAddress = '';

        if (streetNo)
            lookupAddress += streetNo + ' ';
        if (street)
            lookupAddress += street + ' ';
        if (suburb)
            lookupAddress += suburb + ' ';
        if (postalCode)
            lookupAddress += postalCode;

        geocodingProvider.geocode(lookupAddress, apiKey, function (err, res) {
            if (err) {
                logger.error("Error in geocoding for document " + document.documentId + " - " + err);
            } else {
                logger.info("Got address location for document " + document.documentId);
                if (res && res[0] && res[0].latitude && res[0].longitude) {
                    var latitudeFloat = parseFloat(res[0].latitude);
                    var longitudeFloat = parseFloat(res[0].longitude);

                    var typeOfLat = typeof(latitudeFloat);
                    var typeOfLon = typeof(longitudeFloat);

                    if (typeOfLat !== 'NaN' && typeOfLon !== 'NaN') {
                        if (!hasCoordinates(document, field)) {
                            document[field.name].features[0].geometry = {
                                type: "Point",
                                coordinates: []
                            };
                        }
                        document[field.name].features[0].geometry.coordinates[1] = parseFloat(res[0].latitude);
                        document[field.name].features[0].geometry.coordinates[0] = parseFloat(res[0].longitude);
                    }
                }
            }

            // resolve the promise for the document even if there was an error. If the geocoding server is down then we still want
            // to save the document
            document = correctStructure(document, field);
            deferred.resolve(document);
        });
    } else if (shouldLookupAddress(document, field)) {
        var formattedAddress;

        // convert the coordinates to numbers if they aren't already
        var hasValidCoordinates = convertCoordinatesToNumbers(document, field);

        // check that the coordinate conversion didn't result in NaN values. It needs to be checked after both coordinate values
        // have been changed so that the elements in the coordinates array don't get shifted when deleting an invalid coordinate
        if (!hasValidCoordinates) {
            // remove the values. 0 can't be used for coordinate values
            // because it's a valid value. Remove the entire coordinates because it isn't valid
            // with one invalid value and deleting from the array shifts the position of existing values
            delete document[field.name].features[0].geometry.coordinates[1];
            delete document[field.name].features[0].geometry.coordinates[0];
            document = correctStructure(document, field);
            deferred.resolve(document);
        } else {
            var lat = document[field.name].features[0].geometry.coordinates[1];
            var lon = document[field.name].features[0].geometry.coordinates[0];

            geocodingProvider.reverse({lat: lat, lon: lon}, apiKey, function (err, res) {
                if (err) {
                    logger.error("Error in geocoding for document " + document.documentId + " - " + err);
                } else {
                    logger.info("Got address location for document " + document.documentId);

                    if (!document[field.name].features[0].properties) {
                        document[field.name].features[0].properties = res;
                    }
                    else{
                         _.forOwn(res, function(value, key) {
                            var oldVal = document[field.name].features[0].properties[key];
                            if (oldVal && !value){
                                document[field.name].features[0].properties[key] = oldVal;
                            }
                            else{
                                document[field.name].features[0].properties[key] = value;
                            }
                        });
                    }

                }
                document = correctStructure(document, field);

                deferred.resolve(document);
            });
        }
    } else {
        logger.info("No address or location information needs to be looked up");
        // convert the coordinates to numbers if they aren't already
        convertCoordinatesToNumbers(document, field);
        document = correctStructure(document, field);
        deferred.resolve(document);
    }

    return deferred.promise;
};
