'use strict';

var fs = require('fs');
var spawn = require('child_process').spawn;
var util = require('util');
var net = require('net');
var Timeout = require('node-timeout');

var OSRMBaseLoader = class {
    constructor (scope) {
        this.scope = scope;
    }

    launch (callback) {
        var limit = Timeout(this.scope.TIMEOUT, { err: this.scope.RoutedError('Launching osrm-routed timed out.') });

        var runLaunch = (cb) => {
            this.osrmUp(() => { this.waitForConnection(cb); });
        };

        runLaunch(limit((e) => { if (e) callback(e); else callback(); }));
    }

    shutdown (callback) {
        var limit = Timeout(this.scope.TIMEOUT, { err: this.scope.RoutedError('Shutting down osrm-routed timed out.')});

        var runShutdown = (cb) => {
            this.osrmDown(cb);
        };

        runShutdown(limit((e) => { if (e) callback(e); else callback(); }));
    }

    osrmIsRunning () {
        return !!this.scope.pid && this.child && !this.child.killed;
    }

    osrmDown (callback) {
        if (this.scope.pid) {
            process.kill(this.scope.pid, this.scope.TERMSIGNAL);
            this.waitForShutdown(callback);
            this.scope.pid = null;
        } else callback(true);
    }

    waitForConnection (callback) {
        var retryCount = 0;
        var connectWithRetry = () => {
            net.connect({ port: this.scope.OSRM_PORT, host: '127.0.0.1' })
               .on('connect', () => { callback(); })
               .on('error', () => {
                   if (retryCount < 2) {
                       retryCount++;
                       setTimeout(connectWithRetry, 100);
                   } else {
                       callback(new Error('Could not connect to osrm-routed after three retires'));
                   }
               });
        };

        connectWithRetry();
    }

    waitForShutdown (callback) {
        var check = () => {
            if (!this.osrmIsRunning()) return callback();
        };
        setTimeout(check, 100);
    }
};

var OSRMDirectLoader = class extends OSRMBaseLoader {
    constructor (scope) {
        super(scope);
    }

    load (inputFile, callback) {
        this.inputFile = inputFile;
        this.shutdown(() => {
            this.launch(callback);
        });
    }

    osrmUp (callback) {
        if (this.scope.pid) return callback();
        var writeToLog = (data) => {
            fs.appendFile(this.scope.OSRM_ROUTED_LOG_FILE, data, (err) => { if (err) throw err; });
        };

        var child = spawn(util.format('%s/osrm-routed', this.scope.BIN_PATH), [this.inputFile, util.format('-p%d', this.scope.OSRM_PORT)]);
        this.scope.pid = child.pid;
        child.stdout.on('data', writeToLog);
        child.stderr.on('data', writeToLog);

        callback();
    }
};

var OSRMDatastoreLoader = class extends OSRMBaseLoader {
    constructor (scope) {
        super(scope);
    }

    load (inputFile, callback) {
        this.inputFile = inputFile;
        this.loadData((err) => {
            if (err) return callback(err);
            if (!this.scope.pid) return this.launch(callback);
            else callback();
        });
    }

    loadData (callback) {
        this.scope.runBin('osrm-datastore', this.inputFile, (err) => {
            if (err) return callback(this.scope.LaunchError(this.exitCode, 'datastore', err));
            callback();
        });
    }

    osrmUp (callback) {
        if (this.scope.pid) return callback();
        var writeToLog = (data) => {
            fs.appendFile(this.scope.OSRM_ROUTED_LOG_FILE, data, (err) => { if (err) throw err; });
        };

        var child = spawn(util.format('%s/osrm-routed', this.scope.BIN_PATH), ['--shared-memory=1', util.format('-p%d', this.scope.OSRM_PORT)]);
        this.child = child;
        this.scope.pid = child.pid;
        child.stdout.on('data', writeToLog);
        child.stderr.on('data', writeToLog);

        callback();
    }
};

module.exports = {
    _OSRMLoader: class {
        constructor (scope) {
            this.scope = scope;
            this.loader = null;
        }

        load (inputFile, callback) {
            var method = this.scope.loadMethod;
            if (method === 'datastore') {
                this.loader = new OSRMDatastoreLoader(this.scope);
                this.loader.load(inputFile, callback);
            } else if (method === 'directly') {
                this.loader = new OSRMDirectLoader(this.scope);
                this.loader.load(inputFile, callback);
            } else {
                callback(new Error('*** Unknown load method ' + method));
            }
        }

        shutdown (callback) {
            this.loader.shutdown(callback);
        }

        up () {
            return this.loader ? this.loader.osrmIsRunning() : false;
        }
    }
};