/*
 * Copyright (C) 2017 Centro de Computacao Cientifica e Software Livre
 * Departamento de Informatica - Universidade Federal do Parana
 *
 * This file is part of blend.
 *
 * blend is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * blend is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with blend.  If not, see <http://www.gnu.org/licenses/>.
 */

import { Client, PoolConfig } from "pg";
import { View, LoadView } from "../../src/core/view";
import { Source } from "../../src/core/source";
import { each, series } from "async";
import * as fs from "fs";

interface TransSet {
    init: string;
    data: string[];
}

interface SoucerParse{
    name: string;
    type: string[];
    fields: string[];
}

export interface Schema {
    alias?: string;
    query?: string;
    data?: string;
    fields: any[];
}

export class Fixture {
    private database: string;
    private config: PoolConfig;

    constructor(config: PoolConfig) {
        this.config = config;
        this.database = config.database;
    }

    public load(schemas: LoadView[], create: boolean, cb: (err: Error) => void): void {
        let client = new Client(this.config);

        /*
            Loading data has 2 steps:
            1 - Create a table or truncate a existing one.
                Create or truncate dependes from a parameter in the configure
                file, this parameter reaches this function as the create
                parameter.
            2 - Insert data.
        */
        let init: string[] = [];
        let data: string[] = [];
        for (let i = 0; i < schemas.length; ++i) {
            let table: TransSet = this.createTransSet(schemas[i].view, schemas[i].data, create);
            init.push(table.init);
            data = data.concat(table.data);
        }

        client.connect((error) => {
            if (error) {
                cb(error);
                return;
            }

            /*
                Tables must be creates before data could be inserted, so the
                queries that create and insert are splited in 2 arrays.
                To garantee that tables exists before start insert data, series
                is used. Inside series each query is executed, using each;
            */
            series([(callback: (err: Error) => void) => {
                each(init, (query, cback) => {
                    return client.query(query, [], (err: Error) => cback(err));
                }, (errQuery: Error) => callback(errQuery));
            }, (callback: (err: Error) => void) => {
                each(data, (query, cback) => {
                    return client.query(query, [], (err: Error) => cback(err));
                }, (errQuery: Error) => callback(errQuery));
            }], (errQuery: Error) => {
                if (errQuery) {
                    client.end();
                    cb(errQuery);
                }
                client.end((err) => {
                    cb(err);
                });
            });

        });
    }

    private typeConvertion(t: string): string {
        switch (t) {
            case "integer":
                return "INTEGER";
            case "date":
                return "DATE";
            case "string":
                return "TEXT";
            case "boolean":
                return "BOOLEAN";
            default:
                return "TEXT";
        }
    }

    private createTransSet(view: View, filePath: string, create: boolean): TransSet {
        let props = [];

        for (let i = 0; i < view.metrics.length; ++i) {
            let met = view.metrics[i];
            props.push("\"" + met.name + "\" " + this.typeConvertion(met.dataType));
        }

        for (let i = 0; i < view.dimensions.length; ++i) {
            let dim = view.dimensions[i];
            props.push("\"" + dim.name + "\" " + this.typeConvertion(dim.dataType));
        }

        let name = "view_" + view.id;
        let transaction: TransSet = {init: "", data: []};
        if (create) {
            transaction.init = "CREATE TABLE " + name + "(" + props.join(", ") + ")";
        }
        else {
            transaction.init = "TRUNCATE TABLE " + name;
        }

        transaction.data = [];
        let rows = JSON.parse(fs.readFileSync(filePath, {encoding : "utf8"}));
        for (let i = 0; i < rows.length; ++i) {
            let values = [];
            let keys = [];
            for (let key of Object.keys(rows[i])) {
                keys.push("\"" + key + "\"");
                values.push("'" + rows[i][key] + "'");
            }
            transaction.data.push("INSERT INTO " + name +
                                    "(" + keys.join(", ") + ") " +
                                    "VALUES (" + values.join(", ") + ")");
        }
        return transaction;
    }
    public LoadSource(source: Source[], create: boolean , cb: (err: Error) => void): void {

        let client = new Client(this.config);

        let query: string[] = [];
        for (let i = 0; i < source.length; i++ ){
        query[i] = this.ExtractData(source[i], create);
        }

        client.connect((error) => {
            if (error) {
                cb(error);
                return;
            }

            series([(callback: (err: Error) => void) => {
                each(query, (insere, cback) => {
                    return client.query(insere , [], (err: Error) => cback(err));
                }, (errQuery: Error) => callback(errQuery));
             }], (errQuery: Error) => {
                 if (errQuery) {
                     client.end();
                     cb(errQuery);
                 }
                 client.end((err) => {
                     cb(err);
                 });
             });
        });

    }
    private ExtractData(data: Source , create: boolean): string{
        let name: string;
        let type: string[];
        let fields: string[];
        let consult: string;

        name = data.name;
        type = data.fields.map((item) => item.dataType);
        fields = data.fields.map((item) => item.name);

        if (create){
        consult = "CREATE TABLE " + name + " (" + '"';
        for (let i = 0; i < fields.length; i++){
            fields[i] = fields[i].concat('"' + " " + this.typeConvertion(type[i]));
        }
        consult = consult.concat(fields.join(", " + '"'));
        consult = consult.concat(");");
        }
        else{
        consult = "TRUNCATE TABLE " + name + ";";
        }
        return consult;

    }
}