// LICENSE_CODE ZON
'use strict'; /*jslint node:true, es6:false*/
require('../../util/config.js');
var agent_proxy = require('./agent_proxy.js');
var etask = require('../../util/etask.js');
var http_hdr = require('../../util/http_hdr.js');
var zerr = require('../../util/zerr.js');
var zescape = require('../../util/escape.js');
var zutil = require('../../util/util.js');
var http = require('http');
var E = module.exports = outgoing_request;
var methods_with_body = zutil.bool_lookup('POST PUT');
var assign = Object.assign;

function outgoing_request(incoming, route, opt){
    this.incoming = incoming;
    this.route = route;
    switch (route)
    {
    case 'direct':
        this.proxy = null;
        this.hdrs_only = false;
        this.remote_role = 'w';
        break;
    case 'proxy':
        this.proxy = opt.peer ?
            incoming.smart_proxy.peer_proxy : incoming.smart_proxy.agent_proxy;
        this.hdrs_only = !!opt.hdrs_only;
        this.remote_role = 'a';
        break;
    default: throw new Error('Unsupported route');
    }
    this.start_ts = null;
    this.end_ts = null;
    this.policy = null;
    this.probing = !!(this.proxy && !opt.allowed
        && methods_with_body[incoming.req.method]);
    this.peer = !!opt.peer;
    this.slow = false;
    this.soft_timeout = opt.soft_timeout;
    this.hard_timeout = opt.hard_timeout;
    this.req = null;
    this.res = null;
    this.code = 0;
    this.len = 0;
    this.etag = null;
    this.type = null;
    this.location = null;
    this.error = null;
    this.sp = null;
}

E.prototype.recovery_fn = function(){
    var _this = this;
    return etask('outgoing_request.recovery', [function(){
        _this.sp.del_alarm();
        return _this.proxy.report_error();
    }, function(){ _this.sp.goto('start'); }, function catch$(err){
        _this.sp.throw(err); }]);
};

E.prototype.run = function(){
    var _this = this;
    return this.sp = etask('outgoing_request.run', [function(){
        if (_this.proxy)
            return _this.proxy.use();
    }, function start(){
        var __this = this;
        var url = _this.incoming.url;
        _this.headers = _this.probing ?
            {} : assign(_this.incoming.get_headers(), {host: url.host});
        if (_this.proxy)
        {
            _this.headers['x-hola-version'] = agent_proxy.version_header;
            _this.headers['proxy-authorization'] = _this.proxy.basic;
            if (_this.proxy.jwt.jwt)
                _this.headers['x-hola-jwt'] = _this.proxy.jwt.jwt;
            if (_this.hdrs_only)
                _this.headers['x-hola-headers-only'] = 1;
        }
        _this.req_params = {
            hostname: _this.proxy ? _this.proxy.ip : url.hostname,
            port: _this.proxy ? _this.proxy.port : +url.port||80,
            method: _this.probing ? 'GET' : _this.incoming.req.method,
            path: _this.probing ? zescape.uri('/probe',
                {url: url.orig, country: _this.proxy.country})
                : _this.proxy ? url.orig : url.relative,
            headers: http_hdr.restore_case(assign({}, _this.headers),
                _this.incoming.req.rawHeaders),
            keepAlive: true,
        };
        if (_this.hard_timeout)
        {
            setTimeout(function(){ __this.throw(new Error('Hard timeout')); },
                _this.hard_timeout);
        }
        _this.recovery_possible = _this.probing ||
            !methods_with_body[_this.incoming.req.method] && _this.proxy;
    }, function (){
        _this.req = http.request(_this.req_params);
        _this.zerr(zerr.L.INFO,
            '> '+(_this.proxy ?
                'via '+_this.proxy.server+':'+_this.proxy.port : 'direct')
            +(_this.probing ? ' probe' : '')
            +(_this.hdrs_only ? ' hdrs_only' : ''));
        _this.req.on('response', this.continue_fn());
        _this.req.on('error', _this.recovery_possible ?
            _this.recovery_fn.bind(_this) : this.throw_fn());
        if (methods_with_body[_this.incoming.req.method] && !_this.probing)
            _this.incoming.req.pipe(_this.req);
        else
            _this.req.end();
        if (_this.soft_timeout)
        {
            this.alarm(_this.soft_timeout, function(){
                _this.zerr(zerr.L.WARN, 'Soft timeout ('
                    +_this.soft_timeout+'ms)');
                _this.slow = true;
                _this.incoming.react(_this);
            });
        }
        _this.start_ts = Date.now();
        return this.wait();
    }, function(res){
        this.del_alarm();
        _this.res = res;
        _this.end_ts = Date.now();
        var time = _this.end_ts-_this.start_ts;
        if (_this.hdrs_only && res.headers['x-hola-response'])
        {
            _this.code = +res.headers['x-hola-status-code'];
            _this.len = +res.headers['x-hola-content-length']||0;
            _this.etag = res.headers['x-hola-etag'];
            _this.type = res.headers['x-hola-content-type'];
        }
        else
        {
            _this.code = res.statusCode;
            _this.len = +res.headers['content-length']||0;
            _this.etag = res.headers.etag;
            _this.type = res.headers['content-type'];
            _this.location = res.headers.location;
            _this.hdrs_only = false;
        }
        if (_this.proxy && !_this.policy)
            _this.policy = res.headers['x-hola-policy'];
        var msg = '< '+_this.code;
        if (_this.hdrs_only)
            msg += ' hdrs_only';
        if (_this.policy)
            msg += ' '+_this.policy;
        if (_this.code>=300 && _this.code<400)
            msg += ' -> '+_this.location;
        if (_this.len)
            msg += ' len '+_this.len;
        if (_this.type)
            msg += ' '+_this.type;
        msg += ' ('+time+'ms)';
        _this.zerr(zerr.L.INFO, msg);
        if (_this.probing && _this.policy.startsWith('allowed'))
        {
            _this.probing = false;
            return this.goto('start');
        }
        if (_this.proxy)
            _this.proxy.report_response(_this.code);
        _this.incoming.react(_this);
        return this.wait();
    }, function(){
        _this.res.on('end', this.continue_fn());
        _this.res.on('error', this.throw_fn());
        return this.wait();
    }, function(){
        _this.zerr(zerr.L.INFO, 'done');
    }, function catch$(err){
        this.del_alarm();
        _this.end_ts = Date.now();
        var time = _this.end_ts-_this.start_ts;
        _this.zerr(zerr.L.ERR, err);
        _this.error = err;
        if (_this.proxy)
            _this.proxy.report_error();
        _this.incoming.react(_this);
    }]);
};

E.prototype.get_headers = function(){
    var headers = {};
    for (var h in this.res.headers)
    {
        if (h.startsWith('x-hola-'))
            continue;
        if (this.incoming.is_proxy_req && h.startsWith('proxy-'))
            continue;
        headers[h] = this.res.headers[h];
    }
    return headers;
};

E.prototype.zerr = function(level, msg){
    if (msg instanceof TypeError)
        msg = msg.stack;
    if (!/^[<>]/.test(msg))
        msg = ' '+msg;
    zerr._zerr(level, [this.incoming.id+'p'+this.remote_role+msg]);
};

E.prototype.complete = function(){ return this.sp.continue(); };

E.prototype.abort = function(){
    this.zerr(zerr.L.INFO, 'abort');
    if (this.req)
        this.req.abort();
    this.sp.return();
};
