/*
** Module dependencies
*/
var HttpAgent = require('http').Agent;
var HttpsAgent = require('https').Agent;
var parseUrl = require('url').parse;
var Q = require('q');
var request = require('superagent');
require('superagent-retry')(request);
var _ = require('lodash');
var semver = require('semver');
var debug = require('debug')('wfs');
var xml = require('./xml');

/*
** Config
*/
var Agent = {
  'http:': HttpAgent,
  'https:': HttpsAgent
};

var supportedVersions = {
    '1.0.0': require('./versions/1.0.0'),
    '1.1.0': require('./versions/1.1.0'),
    '2.0.0': require('./versions/2.0.0')
};
var supportedVersionsKeys = _.keys(supportedVersions);

/*
** Constructor
*/
function Client(url, options) {
    if (!url) throw new Error('URL is required!');
    this.url = url;
    this.options = options || {};
    this.queryStringToAppend = options.queryStringToAppend || {};

    if (this.options.version) {
        if (!(this.version in supportedVersions)) throw new Error('Version not supported by client');
        this.version = this.options.version;
    }

    if (this.options.maxSockets || this.options.keepAlive) {
        this.agent = new Agent[parseUrl(url).protocol](_.pick(this.options, 'keepAlive', 'maxSockets'));
    }
}

/*
** Private methods
*/
Client.prototype.ensureVersion = function() {
    if (!this.version) this.version = this.negociateVersion(supportedVersionsKeys[supportedVersionsKeys.length - 1]);
    return Q(this.version);
};

Client.prototype.negociateVersion = function(candidateVersion) {
    var client = this;
    debug('client is trying with version %s', candidateVersion);
    return this.request({ request: 'GetCapabilities', version: candidateVersion })
        .then(function(capabilities) {
            var rootNode = capabilities.root();
            var detectedVersion = rootNode.attr('version') ? rootNode.attr('version').value() : null;
            if (!detectedVersion || !semver.valid(detectedVersion)) {
                debug('unable to read version in Capabilities');
                throw new Error('Unable to read version in Capabilities');
            }
            debug('server responded with version %s', detectedVersion);
            if (detectedVersion === candidateVersion) {
                debug('client and server versions are matching!');
                return detectedVersion;
            }
            if (semver.gt(detectedVersion, candidateVersion)) {
                debug('client candidate version (%s) is smaller than the lowest supported by server (%s)', candidateVersion, detectedVersion);
                debug('version negociation failed');
                throw new Error('Version negociation has failed. Lowest version supported by server is ' + detectedVersion + ' but candidateVersion was ' + candidateVersion);
            } else {
                debug('candidate version (%s) is greater than server one (%s)', candidateVersion, detectedVersion);
                if (detectedVersion in supportedVersions) {
                    debug('version returned by server (%s) is supported by client', detectedVersion);
                    return detectedVersion;
                }
                var nextCandidateVersion = _.findLast(supportedVersionsKeys, function(supportedVersion) {
                    return semver.lt(supportedVersion, detectedVersion);
                });
                debug('nearest smaller version supported by client is %s', nextCandidateVersion);
                return client.negociateVersion(nextCandidateVersion);
            }
        });
};

Client.prototype.request = function(query) {
    var deferred = Q.defer();

    var req = request
        .get(this.url)
        .parse(xml.parse)
        .query({ service: 'WFS' })
        .query(this.queryStringToAppend)
        .query(query);

    if (this.agent) req.agent(this.agent); // Must be called before any set method!
    if (this.options.userAgent) req.set('User-Agent', this.options.userAgent);
    if (this.options.retry) req.retry(this.options.retry);

    req.end(function(err, res) {
        if (err) return deferred.reject(err);
        if (res.res.parseError) return deferred.reject(res.res.parseError);
        deferred.resolve(res.body);
    });

    return deferred.promise;
};

Client.prototype.capabilities = function() {
    var client = this;

    return client.ensureVersion().then(function(version) {
        return client.request({ request: 'GetCapabilities', version: version }).then(function(xmlDoc) {
            return supportedVersions[version].parseCapabilities(xmlDoc);
        });
    });

};

Client.prototype.featureTypes = function() {
    return this.capabilities().then(function(capabilities) {
        return capabilities.featureTypes || [];
    });
};


/*
** Exports
*/
module.exports = Client;

@@ -3524,32 +3524,727 @@
 ;%0A            %7D%0A
+        %7D, function(err) %7B%0A            /* Recovery mode %5Co/ */%0A            debug('unable to read Capabilities');%0A            var nextCandidateVersion = _.findLast(supportedVersionsKeys, function(supportedVersion) %7B%0A                return semver.lt(supportedVersion, candidateVersion);%0A            %7D);%0A            if (nextCandidateVersion) %7B%0A                debug('nearest smaller version supported by client is %25s', nextCandidateVersion);%0A                return client.negociateVersion(nextCandidateVersion);%0A            %7D else %7B%0A                debug('version negocation failed - recovery mode');%0A                throw new Error('Version negociation has failed (recovery mode)');%0A            %7D%0A
         %7D);%0A%7D;%0A%0A

Add recovery mode for servers with suck