var Request = require("./Request.js").Request;
var Response = require("./Response.js").Response;
var BinaryData = require("./../data/BinaryData.js").BinaryData;
var RequestProcessor = require("./RequestProcessor.js").RequestProcessor;
var Crypto = require("../common/Crypto.js");
var Types = require("../common/Types.js");

var fs = require('fs');
var http = require('http');
var path = require('path');

http.STATUS_CODES['308'] = 'Permanent Redirect';
http.STATUS_CODES['428'] = 'Precondition Required';
http.STATUS_CODES['429'] = 'Too Many Requests';
http.STATUS_CODES['431'] = 'Request Header Fields Too Large';
http.STATUS_CODES['508'] = 'Loop Detected';
http.STATUS_CODES['511'] = 'Network Authentication Required';

/**
 * @class Server Main http server class. Wraps native node.js http object. Creates Request processor instance and dispatch processing.
 * @param {Class} RequestProcessorClass Subclass of RequestProcessor family.
 */
var Server = function (RequestProcessorClass) {
    this.RequestProcessorClass = RequestProcessorClass;
    this._nativeServers = [];
};

Server.prototype._reattachListeners = function (nativeServer, listeners) {
    var that = this;

    nativeServer.removeAllListeners('connection');
    nativeServer.addListener('connection', function (socket) {
        var destroy = socket.destroy;

        socket.addListener('parseError', function () {
            if (socket.writable) {
                socket.write("HTTP/1.1 400 Bad Request\r\nserver: " +  that.getServerName() + "\r\ncontent-type: text/plain\r\nconnection: close\r\n\r\n400 Bad Request");
            }
            var rp = new RequestProcessor(that);
            var req = new Request();
            var res = new Response();
            res.setStatusCode(400);
            rp.process(req, res);

            res.send();
        });

        socket.destroy = function (err) {
            if (err && err.hasOwnProperty('bytesParsed')) {
                socket.emit('parseError');
                process.nextTick(function () {
                    destroy.call(socket, err);
                });
            } else {
                destroy.call(socket, err);
            }
        }
    });
    nativeServer.addListener('connection', http._connectionListener);

    if (!listeners || (listeners && listeners.indexOf('upgrade') > -1)) {
        nativeServer.removeAllListeners('upgrade');
        nativeServer.addListener('upgrade', function (req, socket, upgrade) {
            that._upgrade(req, socket);
        });
    }
};

Server.prototype.getServerName = function () {
    return "node-server";
};

Server.prototype._createNativeServer = function (listeners) {
    var that = this;

    var nativeServer = http.createServer(function (request, response) {
        that._onRequestHandler(request, response);
    });

    this._reattachListeners(nativeServer, listeners);
    this._nativeServers.push(nativeServer);

    return nativeServer;
};

Server.prototype.listen = function (port, host, callback) {
    var nativeServer = this._createNativeServer();

    nativeServer._backlog = 4096;
    nativeServer.listen(port, host, callback);

    return nativeServer;
};

Server.prototype.listenFD = function (fd, protocol) {
    var nativeServer = this._createNativeServer();

    nativeServer._backlog = 4096;
    nativeServer.listenFD(fd, protocol);

    return nativeServer;
};

Server.prototype.listenSocket = function (socketPath, options, callback) {
    if (callback === undefined) {
        callback = options;
        options = {};
    }

    options.makedirs = options.makedirs || false;
    options.socketPerm = options.socketPerm || 0700;
    options.dirPerm = options.dirPerm || 0700;

    var that = this;
    var doListen = function () {
        fs.unlink(socketPath, function (unlinkErr) {
            if (unlinkErr && unlinkErr.code !== 'ENOENT') {
                return callback(unlinkErr);
            }

            var nativeServer = that._createNativeServer(options.listeners || null);

            nativeServer._backlog = 4096;
            nativeServer.listen(socketPath, function (err) {
                if (err) {
                    return callback(err);
                }

                fs.chmod(socketPath, options.socketPerm, function (chmodErr) {
                    if (chmodErr) {
                        return callback(chmodErr);
                    }

                    return callback(null, nativeServer);
                });
            });
        });
    };

    if (!options.makedirs) {
        return doListen();
    }

    var dirname = path.dirname(socketPath);
    var createDir = function (dir, cb) {
        fs.mkdir(dir, options.dirPerm, function (mkdirErr) {
            if (mkdirErr && mkdirErr.code === 'ENOENT') {
                return createDir(path.dirname(dir), cb);
            } else if (mkdirErr && mkdirErr.code !== 'EEXIST') {
                return callback(mkdirErr);
            }

            return cb();
        });
    };

    return createDir(dirname, doListen);
};

Server.prototype.close = function () {
    for (var i = 0, l = this._nativeServers.length; i < l; i++) {
        this._nativeServers[i].close();
    }
};

/**
 * @private
 */

Server.prototype._onRequestHandler = function (httpRequest, httpResponse) {
    //czas rozpoczecia przetwarzania requestu
    httpRequest.__timeStart = Date.now();

    var that = this;

    var dataLength = 0, buffers = [];

    // gathering post data
    httpRequest.on('data', function (data) {
        buffers.push(data);
        dataLength += data.length;
    });

    // i'm the master of every buffer @ post data'
    httpRequest.on('end', function () {
        var joinedBuffer = new Buffer(dataLength), bufferPosition = 0;

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

        buffers = [];

        httpRequest.data = new BinaryData(
            joinedBuffer,
            BinaryData.Encoding.BINARY
        );

        that._process(httpRequest, httpResponse);
    });

};

Server.prototype._upgrade = function (httpRequest, socket) {
    socket.pause();

    var request = new Request(httpRequest);
    var response = new Response(socket);

    var requestProcessor = this.createRequestProcessor();
    requestProcessor.process(request, response);
};

Server.prototype._process = function (httpRequest, httpResponse) {
    var request = new Request(httpRequest);
    var response = new Response(httpResponse);

    var requestProcessor = this.createRequestProcessor();
    requestProcessor.process(request, response);
};

Server.prototype.getServer = function () {
    return this;
};

Server.prototype.createRequestProcessor = function () {
    var requestProcessor = new this.RequestProcessorClass(this.getServer());
    if (!(requestProcessor instanceof  RequestProcessor)){
        throw Server.Exception.WRONG_PROCESSOR_CLASS;
    }

    return requestProcessor;
};

Server.prototype.getStats = function () {
    //Wsteczna kompatybilnosc do wywalenia po aktualizacji node-server
    return {};
};

/**
 * Namespace for exceptions messages.
 * @static
 * @namespace
 */
Server.Exception = {};
/**
 * @static
 * @public
 */
Server.Exception.WRONG_PROCESSOR_CLASS = "Request processor class must be inheriting RequestProcessor";
//

exports.Server = Server;
