// LICENSE_CODE ZON
'use strict'; /*jslint node:true, es6:false*/
require('../../util/config.js');
var binding = process.zon && process.binding('zsvc');
var smart_proxy = require('./smart_proxy.js');
var zerr = require('../../util/zerr.js');
var net = require('net');
var E = exports, assign = Object.assign;

var server, server_addr;

var app_country = {}, app_sockets = {}, connections = {};

E.enable = function(ip){
    if (server)
        E.disable();
    // Server.prototype.listen is asynchronous, but only because it tries
    // DNS resolution on its address argument. We want to keep this sequence
    // synchronous, so using this undocumented Node internal here.
    server = net.createServer({allowHalfOpen: true}, onconnect);
    server._listen2(ip, undefined, -1, -1);
    server_addr = {};
    server._handle.getsockname(server_addr);
    zerr.notice('Transparent proxy listening on '+ip+':'+server_addr.port);
};

E.start_connection = function(cid, uid, rp){
    connections[cid] = {uid: uid, start: Date.now(), active: !!rp}; };

E.stop_connection = function(cid){
    if (!connections[cid] || connections[cid].end)
        return;
    connections[cid].end = Date.now();
};

E.get_and_flush = function(){
    var res = {}, conn = connections, uid;
    connections = {};
    for (var k in conn)
    {
        var rec = conn[k];
        if (!rec.end)
        {
            connections[k] = rec;
            continue;
        }
        uid = rec.uid;
        res[uid] = res[uid]||{};
        var typ = rec.active ? 'active' : 'inactive';
        res[uid][typ] = res[uid][typ]||{start: Infinity, end: -Infinity};
        res[uid][typ].start = Math.min(res[uid][typ].start, rec.start);
        res[uid][typ].end = Math.max(rec.end, res[uid][typ].end);
    }
    for (uid in res)
    {
        var app = res[uid];
        if (app.active)
            app.active = app.active.end-app.active.start;
        if (app.inactive)
            app.inactive = app.inactive.end-app.inactive.start;
    }
    return res;
};

E.disable = function(){
    if (!server)
        return;
    server.close();
    server = null;
    server_addr = null;
};

E.unblocker_json_set = function(json){
    var old_app_country = app_country, uid;
    app_country = {};
    for (var r in json.unblocker_rules)
    {
        var rule = json.unblocker_rules[r];
        uid = rule.uid;
        var all_app = rule.name == 'org.hola.android.all';
        if (!rule.enabled || rule.type!='apk' || !uid && !all_app)
            continue;
        var country = rule.country.toUpperCase();
        zerr.notice('Unblocking: '+rule.name+' (uid '+uid+') = '+country);
        app_country[all_app ? 0 : uid] = country;
    }
    for (uid in old_app_country)
    {
        if (app_country[uid]!=old_app_country[uid])
            close_sockets(uid, old_app_country[uid]);
    }
};

E.should_redirect_outgoing_tcp = function(ip, port, uid){
    if (port!=80 && port!=443 || !app_country[0] && !app_country[uid])
        return;
    return server_addr && server_addr.port;
};

function close_sockets(uid, old_country){
    if (!old_country)
        return;
    zerr.notice('Closing all '+old_country+' sockets for uid '+uid);
    var sockets = app_sockets[uid];
    for (var fd in sockets)
        sockets[fd].destroy();
}

function onconnect(socket){
    var fake_ip = socket.remoteAddress, fake_port = socket.remotePort;
    var dst_port = socket.localPort;
    if (!fake_ip)
        return;
    var info = socket.zsvc_rdr_info = binding.route_vpn_rdr_accept(
        fake_ip, fake_port, dst_port);
    var country = info && (app_country[0] || app_country[info.uid]);
    if (!info || !country)
    {
        zerr('Intercept failed: connection from '+fake_ip+':'+fake_port);
        socket.destroy();
        return;
    }
    var sockets = app_sockets[info.uid], fd = socket._handle.fd;
    if (!sockets)
        sockets = app_sockets[info.uid] = {};
    sockets[fd] = socket;
    socket.on('error', function(){
        zerr('socket error from '+fake_ip+':'+fake_port); });
    socket.on('close', function(){
        binding.route_vpn_rdr_close(fake_ip, fake_port, dst_port);
        delete sockets[fd];
    });
    zerr.notice('Intercepted connection to '+info.ip+':'+info.port
        +(info.hostname ? ' ('+info.hostname+')' : '')
        +' uid '+info.uid+' country '+country);
    smart_proxy.get(country).intercept(socket);
}
