'use strict';

const Defaults = require('../entities/Defaults.js'),
    Client = require('../dao/client.js'),
    Server = require('../dao/socketServer.js'),
    util = require('util');

class IPC{
    constructor(){
        Object.defineProperties(
            this,
            {
                config      : {
                    enumerable:true,
                    writable:true,
                    value:new Defaults
                },
                connectTo   : {
                    enumerable:true,
                    writable:false,
                    value:connect
                },
                connectToNet: {
                    enumerable:true,
                    writable:false,
                    value:connectNet
                },
                disconnect  : {
                    enumerable:true,
                    writable:false,
                    value:disconnect
                },
                serve       : {
                    enumerable:true,
                    writable:false,
                    value:serve
                },
                serveNet    : {
                    enumerable:true,
                    writable:false,
                    value:serveNet
                },
                of          : {
                    enumerable:true,
                    writable:true,
                    value:{}
                },
                server      : {
                    enumerable:true,
                    writable:true,
                    configurable:true,
                    value:false
                },
                log         : {
                    enumerable:true,
                    writable:false,
                    value:log
                }
            }
        );
    }
}

function log(...args){
    if(this.config.silent){
        return;
    }

    for(let i=0, count=args.length; i<count; i++){
        if(typeof args[i] != 'object'){
            continue;
        }

        args[i]=util.inspect(
            args[i],
            {
                depth:this.config.logDepth,
                colors:this.config.logInColor
            }
        );
    }

    this.config.logger(
        args.join(' ')
    );
}

function disconnect(id){
    if(!this.of[id]){
        return;
    }

    this.of[id].explicitlyDisconnected=true;

    this.of[id].off('*','*');
    if(this.of[id].socket){
        if(this.of[id].socket.destroy){
            this.of[id].socket.destroy();
        }
    }

    delete this.of[id];
}

function serve(path,callback){
    if(typeof path=='function'){
        callback=path;
        path=false;
    }
    if(!path){
        this.log(
            'Server path not specified, so defaulting to',
            'ipc.config.socketRoot + ipc.config.appspace + ipc.config.id',
            this.config.socketRoot+this.config.appspace+this.config.id
        );
        path=this.config.socketRoot+this.config.appspace+this.config.id;
    }

    if(!callback){
        callback=emptyCallback;
    }

    this.server=new Server(
        path,
        this.config,
        log
    );

    this.server.on(
        'start',
        callback
    );
}

function emptyCallback(){
    //Do Nothing
}

function serveNet(host,port,UDPType,callback){
    if(typeof host=='number'){
        callback=UDPType;
        UDPType=port;
        port=host;
        host=false;
    }
    if(typeof host=='function'){
        callback=host;
        UDPType=false;
        host=false;
        port=false;
    }
    if(!host){
        this.log(
            'Server host not specified, so defaulting to',
            'ipc.config.networkHost',
            this.config.networkHost
        );
        host=this.config.networkHost;
    }
    if(host.toLowerCase()=='udp4' || host.toLowerCase()=='udp6'){
        callback=port;
        UDPType=host.toLowerCase();
        port=false;
        host=this.config.networkHost;
    }

    if(typeof port=='string'){
        callback=UDPType;
        UDPType=port;
        port=false;
    }
    if(typeof port=='function'){
        callback=port;
        UDPType=false;
        port=false;
    }
    if(!port){
        this.log(
            'Server port not specified, so defaulting to',
            'ipc.config.networkPort',
            this.config.networkPort
        );
        port=this.config.networkPort;
    }

    if(typeof UDPType=='function'){
        callback=UDPType;
        UDPType=false;
    }

    if(!callback){
        callback=emptyCallback;
    }

    this.server=new Server(
        host,
        this.config,
        log,
        port
    );

    if(UDPType){
        this.server[UDPType]=true;
        if(UDPType === "udp4" && host === "::1") {
            // bind udp4 socket to an ipv4 address
            this.server.path = "127.0.0.1";
        }
    }

    this.server.on(
        'start',
        callback
    );
}

function connect(id,path,callback){
    if(typeof path == 'function'){
        callback=path;
        path=false;
    }

    if(!callback){
        callback=emptyCallback;
    }

    if(!id){
        this.log(
            'Service id required',
            'Requested service connection without specifying service id. Aborting connection attempt'
        );
        return;
    }

    if(!path){
        this.log(
            'Service path not specified, so defaulting to',
            'ipc.config.socketRoot + ipc.config.appspace + id',
            (this.config.socketRoot+this.config.appspace+id).data
        );
        path=this.config.socketRoot+this.config.appspace+id;
    }

    if(this.of[id]){
        if(!this.of[id].socket.destroyed){
            this.log(
                'Already Connected to',
                id,
                '- So executing success without connection'
            );
            callback();
            return;
        }
        this.of[id].socket.destroy();
    }

    this.of[id] = new Client(this.config,this.log);
    this.of[id].id = id;
    this.of[id].path = path;

    this.of[id].connect();

    callback(this);
}

function connectNet(id,host,port,callback){
    if(!id){
        this.log(
            'Service id required',
            'Requested service connection without specifying service id. Aborting connection attempt'
        );
        return;
    }
    if(typeof host=='number'){
        callback=port;
        port=host;
        host=false;
    }
    if(typeof host=='function'){
        callback=host;
        host=false;
        port=false;
    }
    if(!host){
        this.log(
            'Server host not specified, so defaulting to',
            'ipc.config.networkHost',
            this.config.networkHost
        );
        host=this.config.networkHost;
    }

    if(typeof port=='function'){
        callback=port;
        port=false;
    }
    if(!port){
        this.log(
            'Server port not specified, so defaulting to',
            'ipc.config.networkPort',
            this.config.networkPort
        );
        port=this.config.networkPort;
    }

    if(typeof callback == 'string'){
        UDPType=callback;
        callback=false;
    }
    if(!callback){
        callback=emptyCallback;
    }

    if(this.of[id]){
        if(!this.of[id].socket.destroyed){

            this.log(
                'Already Connected to',
                id,
                '- So executing success without connection'
            );
            callback();
            return;
        }
        this.of[id].socket.destroy();
    }

    this.of[id] = new Client(this.config,this.log);
    this.of[id].id = id;
    (this.of[id].socket)? this.of[id].socket.id=id:null;
    this.of[id].path = host;
    this.of[id].port = port;

    this.of[id].connect();

    callback(this);
}

module.exports=IPC;
