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 Request = require('../net/Request.js').Request;
var Event = require('../event/Event.js').Event;
var EmptyLogger = require('../logger/EmptyLogger.js').EmptyLogger;
var http = require('http');

var Response = function(params){
    Message.call(this);
    
    this._pipelined = false;
    this._pipelinedSize = 0;
    this._sentHeaders = false;

    this._statusCode = null;
    this._cookieKey = 'set-cookie';
    this._httpServerResponse = null;
    this._reCharset = /charset=([A-Za-z\-0-9]+)/i;
    this._reStatusLine = /([0-9]{1,}\.[0-9]{1,}).*([0-9]{3,})/;
    this._logger = null;

    this.buffers = [];
    this.dataLength = 0;

    if(Types.isInstanceOf(params, http.ServerResponse)){
        this._httpServerResponse = params;
    } else if(Buffer.isBuffer(params)){
        this.init(this._parseBuffer(params));
    }else{
        this.init(params);
    }
};

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

Response.prototype.setLogger = function (logger) {
    this._logger = logger;
    return this;
};

Response.prototype.getLogger = function () {
    return this._logger;
};

Response.prototype.hasLogger = function () {
    return this._logger !== null;
};

Response.prototype.setCookie = function (cookie) {
    var cookies = this.headers.getHeader("set-cookie");

    if (!cookies) {
        cookies = [];
    } else if (!Types.isArray(cookies)) {
        cookies = [cookies];
    }

    cookies.push(cookie);

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

    return this;
};
Response.prototype.getCookie = function (cookieName) {
    var cookies = this.headers.getHeader("set-cookie"), i, l;

    if (!cookies) {
        return null;
    }

    i = 0, l = cookies.length;

    for (; i < l; i++) {
        if (cookies[i].indexOf(cookieName + "=") == 0) {
            return cookies[i];
        }
    }

    return null;
};
Response.prototype.removeCookie = function (cookieName) {
    var cookies = this.headers.getHeader("set-cookie"), i, l;

    if (!cookies) {
        return null;
    }

    i = 0, l = cookies.length;

    for (; i < l; i++) {
        if(cookies[i].indexOf(cookieName + "=") == 0) {
            break;
        }
    }

    if (i == l) {
        return false;
    }

    cookies.splice(i, 1);

    this.headers.getHeader("set-cookie", cookies, true);

    return true;
};

Response.prototype.setStatusCode = function(code) {
    Assertions.guard(code, [Assertions.isNumber, Assertions.isNull], Response.Exception.WRONG_STATUS_CODE);
    this._statusCode = code;
    return this;
};

Response.prototype.getStatusCode = function(){
    return this._statusCode;
};

Response.prototype.setPipelined = function (pipelined) {
    this._pipelined = pipelined;
};

Response.prototype.getPipelined = function () {
    return this._pipelined;
};

Response.prototype.getPipelinedSize = function () {
    return this._pipelinedSize;
};

Response.prototype._setBody = function(body){
    if(Types.isInstanceOf(body, BinaryData)){
        this._body = body;
    }else{
        var encoding, characterEncoding = null;
        if(Types.isInstanceOf(body, Buffer)){
            if(!this._isBinary()){
                encoding = BinaryData.Encoding.TEXT;
                var charset = this.getCharset();
                switch(charset){
                    case 'iso-8859-2':
                        characterEncoding = BinaryData.CharacterEncoding.ISO_8859_2;
                        encoding = BinaryData.Encoding.BINARY;
                    break;
                    case 'utf-8':
                    case 'utf8':
                        characterEncoding = BinaryData.CharacterEncoding.UTF8
                    break;
                    default:
                        // TODO poprawic to bo to nie jest binarka
                        encoding = BinaryData.Encoding.BINARY;
                    break;   
                }
            }else{
                encoding = BinaryData.Encoding.BINARY;
            }
        }else{
            encoding = BinaryData.Encoding.TEXT;
            characterEncoding = BinaryData.CharacterEncoding.UTF8;
        }
        this._body = new BinaryData(body, encoding, characterEncoding);
    }
};

Response.prototype.setBody = function(body){
    Assertions.isInstanceOf(body, BinaryData);
    this._body = body;
    return this;            
};

/**
 * Indicates if response content type matches given contentType parameter
 * @public
 * @param {String} contentType mime type to match (e.g. text/html, application/*)
 * @returns {Boolean} 
 */
Response.prototype.isContentType = function(contentType){
    Assertions.isString(contentType, Response.Exception.WRONG_CONTENT_TYPE);

    var contentHeader = this.getHeader('content-type');
    
    if(contentHeader){
       var currentContentTypeParts = this.getHeader('content-type').split(";")[0].split("/");
       var checkedContentTypeParts = contentType.split(";")[0].split("/");
       if( 
           (checkedContentTypeParts[0] == "*" || checkedContentTypeParts[0] == currentContentTypeParts[0])
           &&
           (checkedContentTypeParts[1] == "*" || checkedContentTypeParts[1] == currentContentTypeParts[1])
       ){
           return true;            
       }
    }

    return false;
};

Response.prototype.isCharset = function(charset){
    Assertions.isString(charset, Response.Exception.WRONG_CHARSET_TYPE);

    var currentCharset = this.getCharset();

    if(!Types.isNull(currentCharset) && currentCharset.toLowerCase() == charset.toLowerCase()){
        return true;
    }

    return false;
};

Response.prototype.getCharset = function(){
    var matches = this._reCharset.exec(this.getHeader('content-type'));

    if(matches && !Types.isUndefined(matches[1])){
        return matches[1].toLowerCase();
    }

    return null;
};

Response.prototype.getEncoding = function(){
    if(this._isBinary()){
        return 'base64';
    }
    return this.getCharset();
};

Response.prototype.toJson = function(){
    var body = this.getBody();

    if (body) {
        this.setHeader('content-length', this.getBody().length(), true);
    }
    
    return {
        "statusCode": this._statusCode,
        "httpVersion": this._httpVersion,
        "headers": this.headers.getHeaders(),
        "body": body ? this.getBody().toString() : body
    };
};

Response.prototype.setHeaders = function (headers, normalize) {
    if (this.hasLogger()) {
        headers = this._logger.parse(headers);
    }
    Message.prototype.setHeaders.call(this, headers, normalize);    
    return this;
};

Response.prototype.send = function(){
    // 666 - 709

	if (!this.getPipelined() && this._httpServerResponse) {

        var body = '';
        
        var contentBody = this.getBody();
        
        if(this._statusCode!=304 && this._statusCode!=415){
            
            if(contentBody instanceof BinaryData){
                
                // 653 - 680
                // mozemy wyslac jako czysty string (nie musimy robic buffera)
                // HACKYY'
                
                if(this.getHeader('content-encoding')!='gzip' &&
                        contentBody.getEncoding() == BinaryData.Encoding.TEXT &&
                        contentBody.getCharacterEncoding() == BinaryData.CharacterEncoding.UTF8) {
                     body = contentBody.toUTF8String();
                     this.setHeader('content-length', contentBody.length(), true);
                } else {
                    body = contentBody.toBuffer();
                    this.setHeader('content-length', contentBody.length(), true);
                }                
                
            }
        }
        // PRO 20x: 1912-2169
        // PRO 20x: 1845-2061 (bez samego access log (po zlozeniu msg)
        // PRO 20x: 1455-1623 
        // 632 - 666
        
        if(!this._statusCode){
            console.log('  -- StatusCode is not SET! Defaulting to 503!');
            this._statusCode = 503;
        }
        
        if (this.hasLogger()) {
            this._logger.append(this);
        }
        
        this._httpServerResponse.writeHead(this._statusCode, this.getHeaders());
        
        if(body.length>0){
            this._httpServerResponse.end(body);
        } else {
            this._httpServerResponse.end();
        }
    }

    this.dispatchEvent(SendEvent);
};

Response.prototype._isBinary = function(){
    if(this.isContentType('text/*')|| this.isContentType('application/javascript') || this.isContentType('application/x-javascript') || this.isContentType('application/xhtml+xml') || this.isContentType('application/json') || this.isContentType('application/xml')){
        return false;
    }
    return true;
};

Response.prototype._isText = function(contentType){
    return !this._isBinary(contentType);
};

Response.prototype._parseStatusLine = function(statusLine){
    var matches = this._reStatusLine.exec(statusLine);

    return {statusCode: parseInt(matches[2], 10), httpVersion: matches[1]};
};

Response.prototype._statusLineAsString = function(){
    var statusCode = this.getStatusCode();
    return 'HTTP/' + this.getHttpVersion() + ' ' + statusCode + ' ' + http.STATUS_CODES[statusCode];
};

Response.prototype.isCachingDisabled = function(){
    var pragma = this.getHeader('pragma');
    var cacheControl = this.getHeader('cache-control');
    var cachingDisabled = false;
    cachingDisabled = cachingDisabled || (pragma && pragma.search('no-cache')>-1);
    cachingDisabled = cachingDisabled || (cacheControl && cacheControl.search('no-cache')>-1);
    cachingDisabled = cachingDisabled || (cacheControl && cacheControl.search('private')>-1);
    return cachingDisabled;
};

Response.prototype.disableCaching = function () {
    this.setHeader('pragma', 'no-cache', true);
    this.setHeader('cache-control', 'no-cache', true);
    return this;
};

Response.prototype.enableCaching = function () {
    this.removeHeader('pragma');
    this.removeHeader('cache-control');
    return this;
};

Response.prototype.getCacheTimeInSeconds = function(){
    // jezeli jest pragma: no-cache to nigdy nie cachuj!
    var pragma = this.getHeader('pragma');
    if(pragma && pragma.search('no-cache')>-1){
        return 0;
    }
    
    var cacheControl = this.getHeader('cache-control');
    if(cacheControl){
        // jezeli cache-control: no-cache to nigdy nie cachuj!
        if(cacheControl.search('no-cache')>-1){
            return 0;
        }
        // jezeli cache-control: private to nigdy nie cachuj bo mamy shared cache
        if(cacheControl.search('private')>-1){
            return 0;
        }
        // poszukaj czy cache-control ma max-age wtedy zacachuj na max-age
        var maxAge = cacheControl.match(/max-age=([0-9]+)/i);
        if(maxAge && maxAge[1]){
            return parseInt(maxAge[1], 10); // ilosc sekund na ile cachujemy
        }
    }
    
    // jezeli jest expires to zacachuj na expires
    var expires = this.getHeader('expires');
    if(expires){
        expires = new Date(expires);
        if(!isNaN(expires.getTime())) {
            var now = new Date();
            var diff = Math.round((expires-now)/1000);
            if(diff>0){
                return diff;
            }
        }
    }
    
    // jezeli nie ma zadnych naglowkow to nigdy nie cachuj
    return 0;
};

Response.prototype.setCacheTime = function (time) {
    time = parseInt(time, 10);
    if (!isNaN(time)) {
        this.setHeader('cache-control', 'max-age=' + time, true);
    }
};

Response.prototype.isEmptyBody = function(){
    return this._body == null;
};


Response.prototype.isExpired = function(){
    var expires = this.getHeader('expires');
    if(expires){
        expires = new Date(expires);
        if (!isNaN(expires.getTime())) {
          var now = new Date();
          if (expires > now) return false;
        }
    }
    return true;
};

Response.prototype.isModified = function(request){
    if(!(request instanceof Request)){
        throw Response.Exception.WRONG_REQUEST_OBJECT;
    };
    var modifiedSince = request.getHeader('if-modified-since');
    var lastModified = this.getHeader('last-modified');
    
    // check If-Modified-Since
    if (modifiedSince && lastModified) {
        modifiedSince = new Date(modifiedSince);
        lastModified = new Date(lastModified);
        // Ignore invalid dates
        if (!isNaN(modifiedSince.getTime())) {
          if (lastModified <= modifiedSince) return false;
        }
    }
    
    return true;
};


// FIXME: ta funkcja dziala tylko z Response!
Response.prototype._parseBuffer = function(buffer){
    var headersEnd = -1;
    var bodyStart = -1;

    for(var i = 0, l = buffer.length; i < l; i++){
        if(buffer[i] == 13 && buffer[i+2] == 13 && buffer[i+1] == 10 && buffer[i+3] == 10){
            headersEnd = i;
            bodyStart = i+4;
            break;
        }
    }

    Assertions.equal(true, headersEnd > -1, Message.Exception.WRONG_BUFFER);
    Assertions.equal(true, bodyStart > -1, Message.Exception.WRONG_BUFFER);
        
    var resp = this._parseHead(buffer.slice(0, headersEnd).toString('utf8'));
    resp.body = buffer.slice(bodyStart);

    return resp;
};

// FIXME: ta funkcja dziala tylko z Response!
Response.prototype._parseHead = function(data){
    var headers = data.split("\r\n"), statusLine = this._parseStatusLine(headers.shift());

    return {
        headers: this._parseHeaders(headers),
        statusCode: statusLine.statusCode,
        httpVersion: statusLine.httpVersion
    };
};

Response.prototype._parseHeaders = function(headers){
    var newHeaders = {}, pos = 0, headerName;

    for(var i = 0, l = headers.length; i < l; i++){
        //wiem ze ten kod jest glupi ale split w javascripcie jest jeszcze glupszy
        pos = headers[i].indexOf(':');
        headerName = headers[i].substring(0, pos).trim().toLowerCase();
        if(headerName=='set-cookie'){
            if(!newHeaders.hasOwnProperty(headerName) || !Types.isArray(newHeaders[headerName])){
                newHeaders[headerName] = [];
            }
            newHeaders[headerName].push(headers[i].substring(pos + 1, headers[i].length).trim());
        } else {
            newHeaders[headerName] = headers[i].substring(pos + 1, headers[i].length).trim();
        }
    }

    return newHeaders;
};

Response.prototype._headAsString = function(){
    return this._statusLineAsString() + "\r\n" + this._headersAsString() + "\r\n";
};

Response.prototype._headersAsString = function(){
    var str = '',
        cookie,
        headers = this.headers;

    for (var key in headers.getHeaders()) {
        //if(this._headers.hasOwnProperty(key)){
            if (key == 'set-cookie'){
                cookie = headers.getRawHeader(key);
                for(var items = 0, max = cookie.length; items < max; items++){
                    str += key + ':' + cookie[items] + "\r\n";
                }
            } else {
                str += key + ':' + headers.getRawHeader(key).join(',') + "\r\n";
            }
        //}
    }

    return str;
};

// FIXME: ta funkcja dziala tylko z Response!
Response.prototype.toBuffer = function(){
    this.setHeader('content-length', this.getBody().length(), true);

    var headBuffer = new Buffer(this._headAsString());
    var bodyBuffer = this.getBody().toBuffer();

    var buffer = new Buffer(headBuffer.length + bodyBuffer.length);
    
    headBuffer.copy(buffer);
    bodyBuffer.copy(buffer, headBuffer.length);

    return buffer;
};

// FIXME: ta funkcja dziala tylko z Response!
Response.prototype.toBinaryData = function(){
    return new BinaryData(this.toBuffer(), this.getBody().getEncoding(), this.getBody().getCharacterEncoding());
};

Response.prototype.write = function(buffer){
     this.buffers.push(buffer);
     this.dataLength += buffer.length;
     this.dispatchEvent(new Event(Response.Event.WRITE, buffer));
};

Response.prototype.end = function(){
    var buffer = new Buffer(this.dataLength)
    var bufferPosition = 0;
    for(var i = 0, l = this.buffers.length; i < l; i++){
        this.buffers[i].copy(buffer, bufferPosition);
        bufferPosition += this.buffers[i].length;
    }
    this.buffers = [];
    this.dataLength = 0;
    this._setBody(buffer);
    this.dispatchEvent(new Event(Response.Event.END, null));
};
    
Response.Event = {};
Response.Event.SEND = "Response_Event_SEND";
Response.Event.WRITE = "Response.Event.WRITE";
Response.Event.END = "Response.Event.END";

Response.Exception = {};
Response.Exception.WRONG_BODY = "Body has to be a string";
Response.Exception.WRONG_STATUS_CODE = "Status code has to be a number";
Response.Exception.WRONG_CONTENT_TYPE = "Content-type must be a string";
Response.Exception.WRONG_CHARSET_TYPE = "Charset must be a string";
Response.Exception.WRONG_REQUEST_OBJECT = "Wrong request object";

// precached send event
var SendEvent = new Event(Response.Event.SEND);

exports.Response = Response;
