var Message = require('./Message.js').Message;

var assertions = require('../common/Assertions.js');
var types = require('../common/Types.js');
var BinaryData = require('../data/BinaryData.js').BinaryData;

//var urlParser = require('url');
var urlParser = require('uriparser');
var http = require('http');
var util = require('util');

/**
 * @class Request
 * @requires Class | Message
 *
 * @param {String} url
 */
var Request = function(params){
    Message.call(this, params);
    
    this._url = null;
    this._method = 'GET';
    this._host = null;
    this._port = 80;
    this._path = '/';
    this._proto = 'http:';
    this._authUser = null;
    this._authPassword = null;
    this._query = {};
    this._clientAddress = null;
    this._clientPort = null;
    this._timeStart = null;

    if (Buffer.isBuffer(params)) {
        throw Request.Exception.REQUEST_CANNOT_BE_A_BUFFER;
    } else if (types.isString(params)) {
        this.setUrl(params);
    } else if (types.isInstanceOf(params, Request)) {
        this.setUrl(params.getUrl());
        this.setMethod(params.getMethod());
        this.setHeaders(params.getHeaders());
        this.setBody(params.getBody());
        this.setHttpVersion(params.getHttpVersion());
        //connection host
        this.setConnectionHost(params.getConnectionHost());
    } else if (types.isInstanceOf(params, http.IncomingMessage)) {
        // 1831 - 1886
        
        
        
        // 1500 - 1618
        this.setMethod(params.method);
        // 1483 - 1572
        this.setHeaders(params.headers);
        // 1416 - 1552
        var httpVer = params.httpVersion;
        // 1381 - 1483
        this.setHttpVersion(httpVer);
        // 1381 - 1483 
        
        // 1362 - 1488   (bez return this)  1388 - 1492
        this.setBody(params.data);
        // 1340 - 1453
        
        this.setUrl(params.url);
        
        this._clientAddress = params.client.remoteAddress;
        this._clientPort = params.client.remotePort;
        
        this._timeStart = (params.__timeStart || Date.now());
    } else {
        this.init(params);
    }
};

Request.prototype = Object.create(Message.prototype);

Request.prototype._cookieRegExpCache = {};
Request.prototype._cookieRegExp = "=?([^;]+)(;\\s?|$)"; 

/**
 * Checks if given string is already encoded
 * @method
 * @private
 * @param {String} str
 * @return {Boolean}
 */
Request.prototype._isEncoded = function (str) {
    return (str.indexOf("%") != -1 || str.indexOf("+") != -1);
};

/**
 * Set cookie
 * @method
 * @param {String} cookie
 * @return {Request}
 */
Request.prototype.setCookie = function (cookie) {
    var cookies = this.headers.getHeader("cookie");

    if (!cookies) {
        cookies = '';
    }

    if (cookies.length) {
        cookies += '; ' + cookie;
    } else {
        cookies = cookie;
    }

    this.headers.setHeader("cookie", cookies, true);

    return this;
};

Request.prototype.getCookie = function (cookieName) {
    var cookies = this.headers.getHeader("cookie"), regexp;

    if (!cookies) {
        return null;
    }

    if (this._cookieRegExpCache[cookieName]) {
        regexp = this._cookieRegExpCache[cookieName];
    } else {
        regexp = this._cookieRegExpCache[cookieName] = new RegExp(cookieName + this._cookieRegExp);
    }

    var result = cookies.match(regexp);

    if (result) {
        return result[1];
    }

    return null;
};

Request.prototype.removeCookie = function (cookieName) {
    var cookies = this.headers.getHeader("cookie"), regexp;

    if (!cookies) {
        return null;
    }

    if (this._cookieRegExps[cookieName]) {
        regexp = this._cookieRegExpCache[cookieName];
    } else {
        regexp = this._cookieRegExpCache[cookieName] = new RegExp(cookieName + this._cookieRegExp);
    }

    cookies = cookies.replace(regexp, '');

    this.headers.setHeader("cookie", cookies, true);

    return true;
};
    
/**
 * sets url of a service endpoint
 *
 * @param {String} url
 * @returns {UrlRequest}
 */
Request.prototype.setUrl = function (url) {
    this._url = url;

    // FIXME: parser (rfc) nie pozwala przeslac [ i ] trzeba urlencode
    url = url.replace(/\[/g, '%5B').replace(/\]/g, '%5D');

    var matches = urlParser.parse(url);

    if (matches.hasOwnProperty('protocol')) {
        this.setProto(matches.protocol);
    }

    if (matches.hasOwnProperty('auth')) {
        this.setAuth(matches.auth);
    }
    
    if (matches.hasOwnProperty('host')) {
        this.setConnectionHost(matches.host);
    } else {
        this.setConnectionHost(this.getHost());
    }

    if (matches.hasOwnProperty('port')) {
        this.setPort(matches.port);
    }

    if (matches.hasOwnProperty('path')) {
        this.setPath(matches.path);
    }

    if (matches.hasOwnProperty('query')) {
        this.setQuery(matches.query);
    }

    return this;
};

/**
 * returns url of a service endpoint
 *
 * @returns {String}
 */
Request.prototype.getUrl = function () {
    if (this._url) {
        return this._url;
    }

    var url = '';

    var proto = this.getProto();
    if (proto) {
        url += proto + '//';
    }

    var auth = this.getAuth();
    if (auth) {
        url += auth + '@';
    }

    var host = this.getConnectionHost(), 
        port = this.getPort();
    
    if (this.getHost()) {
        host = this.getHost();
        if (host.lastIndexOf(':') > 0) {
            port = null;
        }
    }    
    if (port) {
        url += host + ':' + port;
    } else {
        url += host;
    }

    var path = this.getPath();
    if (path) {
        url += path;
    } else {
        url += '/';
    }

    url += this.getQueryString();

    this._url = url;
    return url;
};

/**
 * Returns query string component of an url
 * @method
 * @returns {String}
 */
Request.prototype.getQueryString = function () {
    var url = '';
    var query = this.getQuery()
    if (query) {
        url += '?';

        var tmp = '';
        // to nie zadziala dla case   test ącki
        for (var key in query) {
            if (query.hasOwnProperty(key)) {
                tmp += this._isEncoded(key) ? key : encodeURIComponent(key);

                if (query[key]) {
                    tmp += "=" + (this._isEncoded(query[key]) ? query[key] : encodeURIComponent(query[key]));
                }

                tmp += '&';
            }
        }

        url += tmp.substring(0, tmp.length - 1);
    }

    if (url == '?') {
        url = '';
    }

    return url;
};

/**
 * sets method of communicating with a service
 *
 * @param {String} method
 * @returns {UrlRequest}
 */
Request.prototype.setMethod = function(method){
    this._method = method;

    return this;
};

/**
 * returns method of communicating with a service
 *
 * @returns {String}
 */
Request.prototype.getMethod = function () {
    return this._method;
};

Request.prototype.setConnectionHost = function (host) {
    this._url = null;
    
    if (host) {
        this._host = host.toLowerCase();
    } else {
        this._host = host;
    }
    return this;
};

Request.prototype.getConnectionHost = function () {
    if (this._host) {
        return this._host.toLowerCase();
    } else {
        return this._host;
    }
};

Request.prototype.setHost = function (host) {
    this._url = null;
    
    if (host) {
        this.headers.setHeader('host', host.toLowerCase(), true);
    } else {
        this.headers.setHeader('host', host, true);
    }
    
    return this;
};

Request.prototype.getHost = function () {
    var host = this.headers.getHeader('host');
    if (host) {
        return host.toLowerCase();
    } else {
        return host;
    }
};

Request.prototype.setPort = function (port) {
    this._url = null;
    this._port = port;
    return this;
};

Request.prototype.getPort = function () {
    return this._port;
};

Request.prototype.setPath = function (path) {
    this._url = null;
    this._path = path;
    return this;
};

Request.prototype.getPath = function () {
    return this._path;
};

Request.prototype.setProto = function (proto) {
    this._url = null;
    this._proto = proto;

    return this;
};

Request.prototype.getProto = function () {
    return this._proto;
};

Request.prototype.setAuth = function (auth) {
    this._url = null;

    var data;

    if (types.isString(auth)) {
        var tmp = auth.split(':');
        data = {user: tmp[0], password: tmp[1]}
    } else {
        data = auth;
    }

    this.setAuthUser(data.user);
    this.setAuthPassword(data.password);

    return this;
};

Request.prototype.getAuth = function () {
    var user = this.getAuthUser(), password = this.getAuthPassword();

    if (user && password) {
        return user + ':' + password;
    };

    return null;
};

Request.prototype.setAuthUser = function (user) {
    this._url = null;
    this._authUser = user;

    return this;
};

Request.prototype.getAuthUser = function () {
    return this._authUser;
};

Request.prototype.setAuthPassword = function (password) {
    this._url = null;
    this._authPassword = password;

    return this;
};

Request.prototype.getAuthPassword = function () {
    return this._authPassword;
};

Request.prototype.setQuery = function (query) {
    this._url = null;
    this._query = query;
};

Request.prototype.getQuery = function () {
    return this._query;
};

Request.prototype.setQueryParam = function (name, value) {
    this._url = null;
    if (!this._query) {
        this._query = {};
    };

    this._query[name] = value;

    return this;
};

Request.prototype.getQueryParam = function (name) {
    if (!this._query) {
        return null;
    }

    return (this._query.hasOwnProperty(name)) ? this._query[name] : null;
};

Request.prototype.getClientAddress = function () {
    return (this._clientAddress || null);
};

Request.prototype.getClientPort = function () {
    return (this._clientPort || null);
};

Request.prototype.getProcessingTime = function () {
    return this._timeStart ? (Date.now() - this._timeStart) : 0;
};

Request.prototype.setBody = function (body) {
    if (types.isInstanceOf(body, BinaryData)) {
        Message.prototype.setBody.call(this, body);
    } else {
        Message.prototype.setBody.call(this, new BinaryData(body?body:'', BinaryData.Encoding.TEXT, BinaryData.CharacterEncoding.UTF8));
    }
};

Request.prototype.isCachingDisabled = function () {
    if (this.getMethod() == Request.POST) {
        return true;
    }

    return false;
};

Request.prototype.isDevelView = function () {
/*    if (this.getCookie('onet_devvint')) {
        return true;
    }

    return false;*/

    var cookies = this.getHeader('cookie');
    if (cookies) {
        if (cookies.indexOf('onet_devvint') > -1) {
            return true;
        }
    }
    return false;
};

Request.prototype.isConditional = function () {
    return this.getHeader('if-modified-since');
};

Request.prototype.isCompressedContentAccepted = function () {
    var ae = this.getHeader('accept-encoding');
    if (ae) {
        return ae.indexOf('gzip') > -1;
    }
    return false;
};

Request.prototype.isGenerating = function () {
    return this.getHeader('x-onet-regenerate');
}

Request.prototype.toJson = function () {
    var body = this.getBody();
    return {
        "url": this.getUrl(),
        "method": this._method,
        "headers": this.headers.getHeaders(),
        "body": body ? body.toString() : body,
        "httpVersion": this._httpVersion
    };
};

Request.OPTIONS = "OPTIONS";
Request.GET = "GET";
Request.HEAD = "HEAD";
Request.POST = "POST";
Request.PUT = "PUT";
Request.DELETE = "DELETE";
Request.TRACE = "TRACE";
Request.CONNECT = "CONNECT";

Request.Exception = {};
Request.Exception.WRONG_BODY = "Body has to be a instance of BinaryData";
Request.Exception.REQUEST_CANNOT_BE_A_BUFFER = "Request cannot be a Buffer";

exports.Request = Request;
