import { MAX_NAME_LENGTH } from "../postgres/constants";
import { TableID } from "./TableID";
import { Comment } from "./Comment";

export interface IDatabaseTriggerParams {
    name: string;
    table: TableID;
    procedure: {
        schema: string;
        name: string;
        args: string[];
    }

    before?: boolean;
    after?: boolean;
    insert?: boolean;
    delete?: boolean;
    update?: boolean;
    updateOf?: string[];
    when?: string;
    
    constraint?: boolean;
    deferrable?: boolean;
    notDeferrable?: boolean;
    statement?: boolean;
    initially?: "immediate" | "deferred";

    comment?: Comment;
}

export class DatabaseTrigger {
    readonly name!: string;
    readonly procedure!: {
        schema: string;
        name: string;
        args: string[];
    };
    readonly table!: TableID;

    readonly comment!: Comment;
    readonly frozen?: boolean;
    readonly cacheSignature?: string;

    readonly before?: boolean;
    readonly after?: boolean;
    readonly insert?: boolean;
    readonly delete?: boolean;
    readonly update?: boolean;
    readonly updateOf?: string[];
    readonly when?: string;
    
    readonly constraint?: boolean;
    readonly deferrable?: boolean;
    readonly notDeferrable?: boolean;
    readonly statement?: boolean;
    readonly initially?: "immediate" | "deferred";

    constructor(json: IDatabaseTriggerParams) {
        Object.assign(this, json);
        this.name = this.name.slice(0, MAX_NAME_LENGTH);

        if ( !(this.table instanceof TableID) ) {
            const tableJson = this.table as {schema: string, name: string};
            this.table = new TableID(
                tableJson.schema,
                tableJson.name
            );
        }

        this.comment = json.comment || Comment.fromFs({
            objectType: "trigger",
        });
        this.frozen = this.comment.frozen;
        this.cacheSignature = this.comment.cacheSignature;
    }

    equal(otherTrigger: DatabaseTrigger) {
        return (
            this.name.slice(0, MAX_NAME_LENGTH) === otherTrigger.name.slice(0, MAX_NAME_LENGTH) &&
            this.table.schema === otherTrigger.table.schema &&
            this.table.name === otherTrigger.table.name &&
            this.procedure.schema === otherTrigger.procedure.schema &&
            this.procedure.name.slice(0, MAX_NAME_LENGTH) === otherTrigger.procedure.name.slice(0, MAX_NAME_LENGTH) &&
            this.procedure.args.join(",") === otherTrigger.procedure.args.join(",") &&
            !!this.before === !!otherTrigger.before &&
            !!this.after === !!otherTrigger.after &&
            !!this.insert === !!otherTrigger.insert &&
            !!this.delete === !!otherTrigger.delete &&
            !!this.update === !!otherTrigger.update &&
            (this.updateOf || []).join(",") === (otherTrigger.updateOf || []).join(",") &&
            this.comment.equal(otherTrigger.comment) &&
            // null == undefined
            // tslint:disable-next-line: triple-equals
            this.when == otherTrigger.when &&
            !!this.constraint === !!otherTrigger.constraint &&
            !!this.deferrable === !!otherTrigger.deferrable &&
            !!this.notDeferrable === !!otherTrigger.notDeferrable &&
            !!this.statement === !!otherTrigger.statement &&
            // null == undefined
            // tslint:disable-next-line: triple-equals
            this.initially == otherTrigger.initially
        
        );
    }

    getSignature() {
        return `${this.name} on ${ this.table.schema }.${ this.table.name }`;
    }

    clone(changes: Partial<IDatabaseTriggerParams> = {}) {
        return new DatabaseTrigger({
            name: this.name,
            table: this.table,
            procedure: this.procedure,
        
            before: this.before,
            after: this.after,
            insert: this.insert,
            delete: this.delete,
            update: this.update,
            updateOf: this.updateOf,
            when: this.when,
            
            constraint: this.constraint,
            deferrable: this.deferrable,
            notDeferrable: this.notDeferrable,
            statement: this.statement,
            initially: this.initially,
        
            comment: this.comment,

            ... changes
        });
    }

    toSQL() {
        let out = "create ";

        if ( this.constraint ) {
            out += "constraint ";
        }
        
        out += `trigger ${this.name}\n`;

        // after|before
        if ( this.before ) {
            out += "before";
        }
        else if ( this.after ) {
            out += "after";
        }
        out += " ";

        // insert or update of x or delete
        const events: string[] = [];
        if ( this.insert ) {
            events.push("insert");
        }
        if ( this.update ) {
            if ( this.updateOf && this.updateOf.length ) {
                events.push(`update of ${ this.updateOf.join(", ") }`);
            }
            else if ( this.update === true ) {
                events.push("update");
            }
        }
        if ( this.delete ) {
            events.push("delete");
        }
        out += events.join(" or ");


        // table
        out += "\non ";
        out += `${this.table.schema}.${this.table.name}`;

        if ( this.notDeferrable ) {
            out += " not deferrable";
        }
        if ( this.deferrable ) {
            out += " deferrable";
        }
        if ( this.initially ) {
            out += " initially ";
            out += this.initially;
        }


        if ( this.statement ) {
            out += "\nfor each statement";
        } else {
            out += "\nfor each row";
        }

        if ( this.when ) {
            out += "\nwhen ( ";
            out += this.when;
            out += " ) ";
        }

        out += `\nexecute procedure ${ 
            this.procedure.schema === "public" ? 
                "" : 
                this.procedure.schema + "." 
        }${ this.procedure.name }()`;

        return out;
    }
}