"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MySQLSchemaTable = void 0;
const MySQLSchemaField_1 = require("./MySQLSchemaField");
const HotDBSchema_1 = require("../HotDBSchema");
/**
 * The database table.
 */
class MySQLSchemaTable {
    constructor(name = "", fields = []) {
        this.name = name;
        this.description = "";
        this.fields = fields;
        this.engine = "InnoDB";
        this.charset = "utf8";
    }
    /**
     * Add a field.
     */
    addField(field) {
        this.fields.push(field);
    }
    /**
     * Add a field.
     */
    addFields(fields) {
        for (let iIdx = 0; iIdx < this.fields.length; iIdx++) {
            let field = this.fields[iIdx];
            this.fields.push(field);
        }
    }
    /**
     * Generate the db command. If type is set to modify, you must pass a db with an
     * active connection. Since field parsing isn't completely implemented yet, not all
     * fields will be modified correctly. Use modifiying with caution. This will
     * skip checking for:
     * * Binary columnexistingFields
     * * unique
     * * zero-filled
     * * generated column
     */
    generate() {
        return __awaiter(this, arguments, void 0, function* (type = HotDBSchema_1.HotDBGenerationType.Create, db = null) {
            let output = [];
            if (type === HotDBSchema_1.HotDBGenerationType.Create) {
                let primaryKeys = [];
                let keys = [];
                let result = `CREATE TABLE IF NOT EXISTS ${this.name} (\n`;
                // Generate the fields.
                for (let iIdx = 0; iIdx < this.fields.length; iIdx++) {
                    let field = this.fields[iIdx];
                    let fieldResult = yield field.generate();
                    if (fieldResult.primaryKey !== "")
                        primaryKeys.push(fieldResult.primaryKey);
                    if (fieldResult.key !== "")
                        keys.push(fieldResult.key);
                    result += `${fieldResult.field},\n`;
                }
                // Generate the primary keys.
                if (primaryKeys.length > 0) {
                    result += "PRIMARY KEY (";
                    for (let iIdx = 0; iIdx < primaryKeys.length; iIdx++) {
                        let primaryKey = primaryKeys[iIdx];
                        result += `\`${primaryKey}\`,`;
                    }
                    result = result.substr(0, (result.length - 1));
                    result += "),\n";
                }
                // Generate the keys.
                if (keys.length > 0) {
                    for (let iIdx = 0; iIdx < keys.length; iIdx++) {
                        let key = keys[iIdx];
                        result += `${key},\n`;
                    }
                }
                result = result.substr(0, (result.length - 2));
                result += `) ENGINE=${this.engine} DEFAULT CHARSET=${this.charset};\n\n`;
                output.push(result);
            }
            if (type === HotDBSchema_1.HotDBGenerationType.Modify) {
                if (db == null)
                    throw new Error("Cannot modify a database structure when db is null.");
                let dbresult = yield db.query(`describe ${this.name};`);
                let existingFields = [];
                let existingFieldsNames = [];
                let thisFieldsNames = [];
                if (dbresult.error != null)
                    throw new Error(`Error while modifying structure ${this.name}, error: ${dbresult.error}`);
                // Get this fields names.
                for (let iIdx = 0; iIdx < this.fields.length; iIdx++) {
                    let thisField = this.fields[iIdx];
                    thisFieldsNames.push(thisField.name);
                }
                // Get the fields.
                for (let iIdx = 0; iIdx < dbresult.results.length; iIdx++) {
                    let jsonField = dbresult.results[iIdx];
                    let dbfield = MySQLSchemaField_1.MySQLSchemaField.parse(jsonField);
                    existingFieldsNames.push(dbfield.name);
                    existingFields.push(dbfield);
                }
                let ignoreFields = [];
                // Delete any fields missing from this.fields
                for (let iIdx = 0; iIdx < existingFields.length; iIdx++) {
                    let existingField = existingFields[iIdx];
                    let pos = thisFieldsNames.indexOf(existingField.name);
                    if (pos < 0) {
                        let tempAlter = `ALTER TABLE ${this.name} DROP COLUMN \`${existingField.name}\`;`;
                        ignoreFields.push(existingField.name);
                        let result = tempAlter;
                        output.push(result);
                    }
                }
                // Add any fields missing from this.fields
                for (let iIdx = 0; iIdx < this.fields.length; iIdx++) {
                    let thisField = this.fields[iIdx];
                    let pos = existingFieldsNames.indexOf(thisField.name);
                    if (pos < 0) {
                        let generatedField = yield thisField.generate();
                        let position = "";
                        if (iIdx === 0)
                            position = "FIRST";
                        else {
                            /// @fixme I could see this causing issues with an existing field's 
                            /// name being changed down below. Maybe? Maybe not.
                            let prevName = existingFieldsNames[(iIdx - 1)];
                            if (prevName == null) {
                                // Is this correct? Since the previous name doesn't exist. Then 
                                // the previous name must be the previous this.fields name? 
                                // Basically the previous newly added field name?
                                if (this.fields[(iIdx - 1)] != null)
                                    prevName = this.fields[(iIdx - 1)].name;
                                else {
                                    /// @fixme Gotta fix this someday...
                                    debugger;
                                }
                            }
                            position = `AFTER \`${prevName}\``;
                        }
                        let tempAlter = `ALTER TABLE ${this.name} ADD COLUMN ${generatedField.field} ${position};`;
                        ignoreFields.push(thisField.name);
                        let result = tempAlter;
                        output.push(result);
                    }
                }
                // See if any columns have moved.
                for (let iIdx = 0; iIdx < existingFields.length; iIdx++) {
                    let existingField = existingFields[iIdx];
                    let pos = thisFieldsNames.indexOf(existingField.name);
                    // This field has been added/deleted, ignore it.
                    if (ignoreFields.indexOf(existingField.name) > -1)
                        continue;
                    if (pos > -1) {
                        if (iIdx !== pos) {
                            let tempAlter = `ALTER TABLE ${this.name} CHANGE COLUMN \`${existingField.name}\` \`${existingField.name}\` `;
                            let generatedField = yield existingField.generate();
                            let position = "";
                            if (pos === 0)
                                position = "FIRST";
                            else
                                position = `AFTER \`${existingFields[iIdx].name}\``;
                            tempAlter += `${generatedField.field} ${position};\n`;
                            let result = tempAlter;
                            output.push(result);
                        }
                    }
                }
                let checkKeysInFields = ["name", "dataType", "primaryKey", "notNull",
                    "unsignedDataType", "autoIncrement", "defaultValue"];
                /**
                 * Check for any differences between the two tables.
                 * Since MySQLSchemaField.parse is missing some fields, this will
                 * skip checking for:
                 * * Binary column
                 * * unique
                 * * zero-filled
                 * * generated column
                 */
                for (let iIdx = 0; iIdx < existingFields.length; iIdx++) {
                    let existingField = existingFields[iIdx];
                    let existingFieldName = existingField.name.toLowerCase();
                    let existingFieldDataType = existingField.dataType.toLowerCase();
                    // This field has been added/deleted, ignore it.
                    if (ignoreFields.indexOf(existingField.name) > -1)
                        continue;
                    if (this.fields[iIdx] != null) {
                        let newField = this.fields[iIdx];
                        let newFieldName = newField.name.toLowerCase();
                        let newFieldDataType = newField.dataType.toLowerCase();
                        // Check to see if both are exactly the same. If they are, skip.
                        if (MySQLSchemaField_1.MySQLSchemaField.compare(existingField, newField, checkKeysInFields) === true)
                            continue;
                        let tempAlter = `ALTER TABLE ${this.name} CHANGE COLUMN \`${existingField.name}\` `;
                        if (newFieldName !== existingFieldName)
                            tempAlter += `\`${newField.name}\` `;
                        else
                            tempAlter += `\`${existingField.name}\` `;
                        if (newField.autoIncrement === true)
                            tempAlter += `AUTO_INCREMENT `;
                        if (newFieldDataType !== existingFieldDataType)
                            tempAlter += `\`${newField.dataType}\` `;
                        else
                            tempAlter += `\`${existingField.dataType}\` `;
                        if (newField.unsignedDataType === true)
                            tempAlter += `UNSIGNED `;
                        if (newField.notNull === true)
                            tempAlter += `NOT NULL `;
                        else
                            tempAlter += `NULL `;
                        if (newField.defaultValue !== existingField.defaultValue)
                            tempAlter += `DEFAULT ${newField.defaultValue} `;
                        if (newField.primaryKey !== existingField.primaryKey) {
                            tempAlter += `\n`;
                            if (existingField.primaryKey === true) {
                                if (newField.primaryKey === true) {
                                    // Do nothing.
                                }
                                else {
                                    tempAlter += `DROP PRIMARY KEY `;
                                }
                            }
                            else {
                                if (newField.primaryKey === true) {
                                    tempAlter += `ADD PRIMARY KEY (\`${newField.name}\`)`;
                                }
                                else {
                                    // Do nothing.
                                }
                            }
                        }
                        tempAlter += ';\n';
                        let result = tempAlter;
                        output.push(result);
                    }
                }
            }
            return (output);
        });
    }
}
exports.MySQLSchemaTable = MySQLSchemaTable;
//# sourceMappingURL=MySQLSchemaTable.js.map