/**
 * General purpose logger
 */
import * as fs from "fs";
import Settings from "./settings";
let rfs = require('rotating-file-stream');
import { MultipleInstanceInterface } from "./multipleInstanceInterface";
import * as Utils from "./utils";

export enum LOG_LEVEL {
    INFO = 1 << 0,
    WARNING = 1 << 1,
    ERROR = 1 << 2,
    DEBUG = 1 << 3,
    DEBUG_VERBOSE = 1 << 4,
    QUERIES = 1 << 5,
    EXTERNAL_API = 1 << 6,
    INTERNAL_API = 1 << 7
}

class OutputSystem {
    private stream: any;
    private buf: Array<string> = [];
    private timer: any | number | null = null;
    private interval: number;
    private inRotation: boolean = false;

    constructor(filename: string, path: fs.PathLike, size: string, interval: number = 1000) {

        // ensure log directory exists
        fs.existsSync(path) || fs.mkdirSync(path);
        let options = {
            size: size,
            path: path
        }

        this.stream = rfs(filename, options);
        this.interval = interval;
        let self = this;
        this.stream.on("rotation", function(){
            self.inRotation = true;
            if(self.timer != null){
                clearTimeout(self.timer);
                self.flush();                
            }
        });
        this.stream.on("rotated", function(filename: string){
            self.inRotation = false;
            if(self.buf.length > 0){
                self.flush();
            }
        });
    }

    // flush function
    private flush() {
        this.timer = null
        this.stream.write(this.buf.join(''))
        this.buf.length = 0
    }

    // write function
    public write(str: string) {
        if (this.timer === null && !this.inRotation) {
            this.timer = setTimeout(this.flush.bind(this), this.interval);
        }

        this.buf.push(str)
    }

    public close(){
        this.stream.end();
    }
}

export default class Logger implements MultipleInstanceInterface {
    
    static LOG_LEVEL = LOG_LEVEL;
    protected errorStream: OutputSystem;    
    protected outputStream: OutputSystem;
    private currentLogLevel: number;
    private debugLogging: boolean ;

    constructor(token: string) {
        let config = Settings.Instance.getLogger(token);
        this.currentLogLevel = config.logLevel != null ? config.logLevel : LOG_LEVEL.INFO | LOG_LEVEL.WARNING | LOG_LEVEL.ERROR;
        this.debugLogging = config.debugLogging ? config.debugLogging : false;

        let outputFile = config.outputFile ? config.outputFile : "outputFile";
        let errorFile = config.errorFile ? config.errorFile : "errorFile";
        let logFolder = config.logFolder ? config.logFolder : "logs";
        let rotatingSize = config.rotatingSize ? config.rotatingSize : "1d";

        logFolder = Utils.default.getFullRouteFrom(logFolder);

        this.outputStream = new OutputSystem(outputFile, logFolder, rotatingSize);
        this.errorStream = new OutputSystem(errorFile, logFolder, rotatingSize);        
    }

    public setLogLevel(level: number){
        this.currentLogLevel = level;
    }

    public destroy(): void{
        this.outputStream.close();
        this.errorStream.close();
    }

    public getStatus(): string{
        return "unknown";
    }

    public isFunctional(): boolean{
        return true;
    }

    /**
     * based on the parameters add a new entry in crawler, workers logs or console if it that option was selected via settings
     * @param str 
     * @param level 
     */
    log(str: string, level: number): void {

        let time = new Date().toISOString();
        if (level && (this.currentLogLevel & level)) {
            if (this.debugLogging) {
                console.log("[" + time + "] [" + this.getLevelStr(level) + '] ' + str);
            } else {
                this.outputStream.write("[" + time + "] [" + this.getLevelStr(level) + '] ' + str + "\n");
            }
        }

        if (level == Logger.LOG_LEVEL.ERROR){
            this.errorStream.write("[" + time + "] [" + this.getLevelStr(level) + '] ' + str + "\n");
        }
    }

    private getLevelStr(_level: number): string {
        switch (_level) {
            case Logger.LOG_LEVEL.INFO:
                return "INFO";
            case Logger.LOG_LEVEL.WARNING:
                return "WARNING";
            case Logger.LOG_LEVEL.ERROR:
                return "ERROR";
            case Logger.LOG_LEVEL.DEBUG:
                return "DEBUG";
            case Logger.LOG_LEVEL.DEBUG_VERBOSE:
                return "VERBOSE";
            case Logger.LOG_LEVEL.QUERIES:
                return "QUERY";
            case Logger.LOG_LEVEL.EXTERNAL_API:
                return "EXTERNAL_API";
            case Logger.LOG_LEVEL.INTERNAL_API:
                return "INTERNAL_API";
            default:
                return "";
        }
    }

}