var http = require('http');
var Request = require('./Request.js').Request;
var Response = require('./Response.js').Response;
var Event = require('../event/Event.js').Event;
var ErrorEvent = require('../event/ErrorEvent.js').ErrorEvent;
var EventDispatcher = require('../event/EventDispatcher.js').EventDispatcher;
var Class = require('../class/Class.js').Class;
var util = require('util');
var Types = require('../common/Types.js');

var Loader = function (request) {
    EventDispatcher.call(this);
    this._buffer = null;
    this._request = null;
    this._requestStart = null;
    this._requestLength = null;
    this._timeout = 8000;
    this._timer = null;
    this._followRedirects = true;
    this._maxRedirectsCount = 10;
    this._currentRedirectsCount = 0;
    this._dataBuffering = true;
    this._agent = false;
    this._forwardedHost = null;

    this.setRequest(request);
};

Loader.prototype = Object.create(EventDispatcher.prototype);

Loader.prototype.setDataBuffering = function (buffering) {
    this._dataBuffering = !!buffering;
    return this;
};

Loader.prototype.setForwardedHost = function (host) {
    this._forwardedHost = host;
    return this;
};

Loader.prototype.getForwardedHost = function () {
    return this._forwardedHost;
};

/**
 * callback responsible for gathering response chunks
 * @private
 * @param {Server.HttpResponse} serverResponse
 */
Loader.prototype._handleServerResponse = function(serverResponse){
    var dataLength = 0,
        buffers = [],
        self = this;

    serverResponse.addListener('data', function (buffer) {
        if (self._dataBuffering) {
            buffers.push(buffer);
            dataLength += buffer.length;
        }
    });

    serverResponse.addListener('end', function () {
        self._buffer = new Buffer(dataLength);
        var bufferPosition = 0;

        for (var i = 0, l = buffers.length; i < l; i++) {
            buffers[i].copy(self._buffer, bufferPosition);
            bufferPosition += buffers[i].length;
        }

        self._onEnd(serverResponse);
    });
};

/**
 * sets timer who is responsible for timeout detection
 * @private
 * @param {Server.HttpRequest} request
 */
Loader.prototype._setTimer = function (request) {
    var self = this;
    this._timer = setTimeout(function () {
        request.removeAllListeners('response');
        request.abort();
        self._log();
        self.dispatchEvent(new ErrorEvent(Loader.Event.ERROR, null, -3, 'Http response timeout'));
    }, this.getTimeout());
};

/**
 * clears timer handle that is responsible for timeout detection
 * @private
 */
Loader.prototype._clearTimer = function () {
    clearTimeout(this._timer);
};

Loader.prototype._clearChunks = function () {
    this._buffer = [];
};

Loader.prototype._log = function (res, type) {
    if (!this._forwardedHost) {
        return;
    }

    var req = this.getRequest();
    var code = 0;
    var len = 0;

    if (res) {
        code = res.statusCode;
        len = this._buffer.length;
    }

    var upstream = req.getHeader('host') || '-';
    var gateway = req.getConnectionHost() || '-';
    var tt = Date.now() - this._requestStart;
    var rel = this._requestLength || 0;

    type = type || 'http';

    var str = 'Loader/Monitoring: vhost=%s upstream=%s code=%s tt=%s len=%s method=%s gateway=%s type=%s reqlen=%s';
    console.info(str, this._forwardedHost, upstream, code, tt, len, req.getMethod(), gateway, type, rel);
};

/**
 * called on request end, when whole data is retreived from server
 * @private
 */
Loader.prototype._onEnd = function (response) {
    var requestMethod = this._request.getMethod();

    this._log(response);

    if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.hasOwnProperty('location') && this._followRedirects && requestMethod != Request.PUT && requestMethod != Request.POST) {
        if (this._currentRedirectsCount >= this._maxRedirectsCount) {
            this.dispatchEvent(new ErrorEvent(Loader.Event.ERROR, null, -4, 'maxRedirectsCount reached, stopping'));
            return;
        }

        this._currentRedirectsCount++;
        this._request.setUrl(response.headers.location);
        //XXX: ale to jest slabe :(
        this._request.setHeader('host', this._request.getConnectionHost(), true);
        this._clearChunks();
        return this.load();
    }


    var data = this._parseResponse(response);

    if (data == false) {
        //tutaj zakladamy ze _parseResponse obsluzyl blad
        return;
    }

    this.dispatchEvent(new Event(Loader.Event.LOADED, data));
};

Loader.prototype._parseResponse = function (response) {
    return new Response({
        'statusCode': response.statusCode,
        'headers': response.headers,
        'httpVersion': response.httpVersion,
        'body': this._buffer
    });
};

Loader.prototype._createRequest = function (request) {
    var ua = request.getHeader('user-agent');

    if (!ua) {
        request.setHeader('user-agent', 'nodejs', true);
    }

    var host = request.getConnectionHost() || request.getHost();
    var port = request.getPort();
    var path = request.getPath();
    var method = request.getMethod();
    var headers = request.getHeaders();

    if (!host || !port || !path || !method || !headers) {
        this._log();
        return this.dispatchEvent(new ErrorEvent(Loader.Event.ERROR, null, -5, Loader.Exception.INCOMPLETE_REQUEST));
    }

    //FIXME to pasuje rozwiazac w klasie Request gdy klonuje inny request
    host = host.replace(':'+request.getPort(), '');

    var retValue = {
        host: host,
        port: port,
        path: path + request.getQueryString(),
        method: method,
        headers: headers
    };

    if (!this._agent) {
        retValue.agent = false;
    }

    return retValue;
};

/**
 * sets timeout in miliseconds
 * @param {Number} ms
 */
Loader.prototype.setTimeout = function (ms) {
    this._timeout = ms;
    return this;
};

/**
 * gets timeout in miliseconds
 * @returns {Number}
 */
Loader.prototype.getTimeout = function () {
    return this._timeout;
};

Loader.prototype.setFollowRedirects = function (follow) {
    this._followRedirects = !!follow;
    return this;
};

Loader.prototype.getFollowRedirects = function () {
    return this._followRedirects;
};

Loader.prototype.setMaxRedirectsCount = function (count) {
    this._maxRedirectsCount = count;
    return this;
};

Loader.prototype.getMaxRedirectsCount = function () {
    return this._maxRedirectsCount;
};

/**
 * sets request object to process
 * @param {OpalRequest} request
 */
Loader.prototype.setRequest = function (request) {
    if(request && !(request instanceof Request)){
        throw Loader.Exception.WRONG_REQUEST_INSTANCE;
    } else if(request) {
        this._request = new Request(request);
        this._currentRedirectCount = 0;
        this._clearChunks();
    }

    return this;
};

/**
 * returns request (a copy of it) which is going to be processed
 */
Loader.prototype.getRequest = function () {
    return this._request;
};

/**
 * Aborts a request, clears timeout, dispatches ABORTED event
 */
Loader.prototype.abort = function () {
    if (this._clientRequest) {
        this._clientRequest.abort();
    }

    this._clearTimer();
    this.dispatchEvent(new Event(Loader.Event.ABORTED, 'Aborted'));
};

/**
 * Sets agent value
 * @param {Boolean} value
 */
Loader.prototype.setAgent = function (value) {
    this._agent = !!value;
};

/**
 * makes request to the backend system based on the request object
 * @param {Request} request
 */
Loader.prototype.load = function (request) {
    var that = this;

    this.setRequest(request);

    if (this._request == null) {
        throw Loader.Exception.REQUEST_NOT_SET;
    }

    this._requestStart = Date.now();

    var options = this._createRequest(this._request);

    if (options) {
        this._clientRequest = http.request(options, function(serverResponse){
            that._clearTimer();
            that._handleServerResponse(serverResponse);
        });

        this._setTimer(this._clientRequest);

        this._clientRequest.addListener('error', function (e) {
            options.agent = null;
            console.error('LOADER ERROR NODE REQUEST: ' + util.inspect(e, true, 20) + ' Options: ' + util.inspect(options, true, 20));
            that._clearTimer();
            that._log();
            that.dispatchEvent(new ErrorEvent(Loader.Event.ERROR, e, -1, 'Http request error: ' + e.message));
        });

        // jezeli sa dane np POST to tez je wpisuje
        if (this._request.getBody().length() > 0) {
            var buf = this._request.getBody().toBuffer();

            this._requestLength = buf.length;
            this._clientRequest.write(buf);
        }

        this._clientRequest.end();
    }
};

Loader.Event = {};
Loader.Event.LOADED   = 'Loader_LOADED';
Loader.Event.ABORTED  = 'Loader_ABORTED';
Loader.Event.ERROR    = 'Loader_ERROR';

Loader.Exception = {};
Loader.Exception.INCOMPLETE_REQUEST = 'Given request misses some key values';
Loader.Exception.REQUEST_NOT_SET = 'Request has not been set';
Loader.Exception.WRONG_REQUEST_INSTANCE = 'Given request is not a instance of Request';
Loader.Exception.FOLLOW_REDIRECTS_TYPE_ERROR = 'setFollowRedirects requires an argument to be a boolean';
Loader.Exception.AGENT_VALUE = 'Agent param value must be a type of Boolean';

exports.Loader = Loader;
