import * as Path from "path";
import * as Fs from "fs";
import * as Util from "util";
import { MultipleInstanceInterface } from "./multipleInstanceInterface";
import Settings from "./settings";
import Global from "./global";
import * as Index from './index.d';
import * as Utils from "./utils";


export default class Crashreporter implements MultipleInstanceInterface {
    private config: Index.CrashReporterOptions;
    private outDir: string;
    private hiddenAttributes: string[];
    private maxCrashFile: number;
    private exitOnCrash: boolean;

    private mailEnabled: boolean;
    private mailSubject?: string;
    private mailFrom?: string;
    private mailTo?: string;

    private mailer: Index.Mailer | null;

    constructor(){
        process.env.NODE_TLS_REJECT_UNAUTHORIZED="0";

        this.config = Settings.Instance.getCrashReporter("global");
        this.outDir = Path.normalize(Utils.default.getFullRouteFrom(this.config.outDir));
        this.hiddenAttributes = this.config.hiddenAttributes || [];
        this.maxCrashFile = this.config.maxCrashFile && this.config.maxCrashFile > 0 ? this.config.maxCrashFile : 5;
        this.exitOnCrash = this.config.exitOnCrash === undefined ? true : this.config.exitOnCrash;

        this.mailEnabled = this.config.mailEnabled ? this.config.mailEnabled : false;
		this.mailSubject = this.config.mailConfig && this.config.mailConfig.subject;
		this.mailFrom =  this.config.mailConfig && this.config.mailConfig.from;
        this.mailTo = this.config.mailConfig && this.config.mailConfig.to;
        
        this.mailer = Global.Instance.Mailers.get();


		// ensure log directory exists
		Fs.existsSync(this.outDir) || Fs.mkdirSync(this.outDir);
        process.on('uncaughtException', this.onUncaughtException.bind(this));
    }

    private onUncaughtException(err: Error) {
        let error = err.stack || err;
        let now = new Date();
        let fileName = Path.resolve(Util.format(
            '%s/crash_%s-%s-%s_%s-%s-%s_%s_UTC.txt',
            this.outDir,
            now.getUTCFullYear(),
            ("0" + (now.getUTCMonth() + 1)).slice(-2),
			("0" + (now.getUTCDate())).slice(-2),
			("0" + now.getUTCHours()).slice(-2),
			("0" + now.getUTCMinutes()).slice(-2),
			("0" + now.getUTCSeconds()).slice(-2),
            ("00" + now.getUTCMilliseconds()).slice(-3)
        ));
        let sep = '\n-----------------------\n';
        let content = [];
        let data;

        !this.checkHiddenAttribute('stack')              ? content.push(error)                                  : "";
		!this.checkHiddenAttribute('dateTime')           ? content.push('Date time: ' + now)                    : "";
		!this.checkHiddenAttribute('execPath')           ? content.push('execPath: ' + process.execPath)        : "";
		!this.checkHiddenAttribute('argv')               ? content.push('argv: ' + process.argv.join(', '))     : "";
		!this.checkHiddenAttribute('currentDirectory')   ? content.push('current directory: ' + process.cwd())  : "";
        !this.checkHiddenAttribute('env')                ? content.push('env:\n' + Util.inspect(process.env))   : "";

		if (process.platform !== 'win32') {
		    !this.checkHiddenAttribute('gid')           ? content.push('gid: ' + process.getgid())              : "";
			!this.checkHiddenAttribute('uid')           ? content.push('uid: ' + process.getuid())              : "";
        }
        
		!this.checkHiddenAttribute('pid')               ? content.push('pid: ' + process.pid)                   : "";
		!this.checkHiddenAttribute('processTitle')      ? content.push('process title: ' + process.title)       : "";
		!this.checkHiddenAttribute('uptime')            ? content.push('uptime (sec): ' + process.uptime())     : "";
		!this.checkHiddenAttribute('arch')              ? content.push('arch: ' + process.arch)                 : "";
		!this.checkHiddenAttribute('versions')          ? content.push('versions:\n' + Util.inspect(process.versions))                  : "";
		!this.checkHiddenAttribute('memoryUsage')       ? content.push('memory usage:\n' + Util.inspect(process.memoryUsage()))         : "";
		//!this.checkHiddenAttribute('requireCache')    ? content.push('require.cache:\n' + Object.keys(require.cache).join('\n'))      : "";
		!this.checkHiddenAttribute('activeHandle')      ? content.push('active handle:\n' + Util.inspect(((<any>process)._getActiveHandles()))) : "";
        !this.checkHiddenAttribute('activeRequest')     ? content.push('active request:\n' + Util.inspect((<any>process)._getActiveRequests())) : "";
        
		data = content.join(sep);
		if (Fs.writeFileSync(fileName, data) === undefined) {
			console.error('Crash written in: %s', fileName);
		} else {
			console.error('Failed to write crash in: %s', fileName);
		}

		console.error(error);

		this.deleteOlderCrashFile();

		this.sendMail(data); // should always be the last call
    };
    
    private deleteOlderCrashFile() {
        interface crashfile {
            file: string;
            date: Date;
        }
        let files: string[] = [];
        let crashfiles: crashfile[] = [];
        let crashfilesRe = /^crash_(\d{4})-(\d{2})-(\d{2})_(\d{2})-(\d{2})-(\d{2})_(\d{3})_UTC.txt$/;
        let match: RegExpMatchArray | null = null;
        let self = this;

		try {
			files = Fs.readdirSync(self.outDir);
			files.forEach(function(file: string) {
				match = file.match(crashfilesRe);
				if (match !== null) {
					crashfiles.push({
						file: Path.resolve(self.outDir,file),
						date : new Date(
							parseInt(match[1], 10),//year
							parseInt(match[2], 10) - 1,//month
							parseInt(match[3], 10),//day
							parseInt(match[4], 10),//hour
                            parseInt(match[5], 10),//minute
                            parseInt(match[6], 10),//second
                            parseInt(match[7], 10) //millisecond
						)
					});
				}
			});

			if (crashfiles.length <= self.maxCrashFile) {
				return;
			}

			crashfiles = crashfiles.sort(function(a,b){
				if (a.date > b.date) {
					return 1;
				}
				if (a.date < b.date) {
					return -1;
				}
				return 0;
			});
			crashfiles = crashfiles.slice(0, crashfiles.length - this.maxCrashFile);
			crashfiles.forEach(function(file){
				try {
					Fs.unlinkSync(file.file);
				} catch(e) {
					self.crashExit(e, false);
					// we continue, cleanup is not essential
				}
			});
		} catch (err) {
			self.crashExit(err, false);
			// we continue, cleanup is not essential
		}
    };
    
    private sendMail(plaintextBody: string) {
		if (this.mailer == null || !this.mailEnabled || this.mailFrom == undefined || this.mailTo == undefined || this.mailSubject == undefined) {
			this.exit();
			return;
		}

		// setup e-mail data with unicode symbols
		let mailOptions = {
			from:  this.mailFrom, // sender address
			to: this.mailTo, // list of receivers
			subject: this.mailSubject, // Subject line
			text: plaintextBody // plaintext body
        };
        let self = this;

		this.mailer.sendMail(mailOptions, function(error: any, response: any){
		    if(error){
		    	self.crashExit(error, self.exitOnCrash);
		    }else{
				console.error("Message sent: " + response.response);
				self.exit();
		    }		    
		});
	};

    private checkHiddenAttribute(name: string) {
        return this.hiddenAttributes.indexOf(name) !== -1;
    }

    protected exit() {
		if (this.exitOnCrash) {
			process.nextTick(function() {
				process.exit(1); // should exit after all 'uncaughtException' event calls
			});
		}
	};
    protected crashExit(error: Error, exit?: boolean) {
		exit = exit !== false;
		console.log(exit);
		console.error('\n-------------------\ncrashreporter Error\n-------------------');
		console.error(error.stack || error);
		if (exit) {
			process.exit(1);
		}
    };
    
    public destroy(){
        process.removeListener('uncaughtException', this.onUncaughtException.bind(this));
    };
    public getStatus(){
        return "";
    };
    public isFunctional(){
        return true;
    };
}