<?php

/**
 * Class DatabaseTableBuilder
 * Generates the database tables used in module builder modules.
 *
 * @author Rihards Silins <rihards.silins@mtcmedia.co.uk>
 *
 * @version 2 14/07/2016
 *
 * @copyright MTC media Ltd. 2015
 */
namespace Mtc\Modules\ModuleBuilder\Classes\Builders;

use Illuminate\Database\Capsule\Manager as Capsule;

class DatabaseTableBuilder
{
    /**
     * Database table name.
     *
     * @var string
     */
    private $table_name;

    /**
     * Iniitalized eloquent class of the model
     *
     * @var Object
     */
    private $model;

    /**
     * Special column names that shouldn't be dropped in a table in any case.
     *
     * @var string[]
     */
    public $special_columns = array(
        'id',
        'active',
        'order',
        'published',
        'timestamp',
        'created',
        'updated',
        'created_at',
        'updated_at',
    );

    /**
     * DatabaseTableBuilder::setTableName()
     * Set table name of table to be built.
     *
     * @param string table_name
     *
     * @return bool success
     */
    public function setTableName($table_name)
    {
        // validate
        if (preg_match("/^[a-z_A-Z\-]+$/", $table_name)) {
            $this->table_name = $table_name;

            return true;
        }

        return false;
    }

    /**
     * DatabaseTableBuilder::setModel()
     * Set iniitalized eloquent class of the model.
     * @param string model
     */
    public function setModel($model)
    {
        // validate
        $this->model = $model;
        $this->table_name = $this->model->getTable();
    }

    /**
     * Generates the database table for model relative.
     * @param mixed[][] $relative_setup
     * @return string[] $result
     */
    public function buildRelationTable($relative_setup)
    {
        $result = array();

        if (empty($this->table_name)) {
            return $result;
        }

        if (Capsule::schema()->hasTable($this->table_name)) {
            $parent_key = $relative_setup['parent_key'];
            if (! Capsule::schema()->hasColumn($this->table_name, $parent_key)) {
                Capsule::schema()->table($this->table_name, function ($table) use ($parent_key): void {
                    $table->integer($parent_key)->index($parent_key);
                });
                $result[] = 'Adding "'.$parent_key.'" (int) column to table "'.$this->table_name.'"!';
            }

            return $result;
        }

        Capsule::schema()->create($this->table_name, function ($table) use ($relative_setup): void {
            $table->increments('id');
            $table->integer($relative_setup['parent_key'])->index($relative_setup['parent_key']);
            $table
                ->tinyInteger('active')
                ->default(1)
                ->index('active');
        });

        $result[] = "Built relation table '".$this->table_name."'.";

        return $result;
    }

    /**
     * DatabaseTableBuilder::build()
     * Generates the database tables used in module builder modules.
     *
     * @return bool success
     */
    public function build()
    {
        if (empty($this->table_name)) {
            return false;
        }

        if (Capsule::schema()->hasTable($this->table_name)) {
            return false;
        }

        Capsule::schema()->create($this->table_name, function ($table): void {
            $table->increments('id');
            $table->string('name')->index('name');
            $table
                ->tinyInteger('active')
                ->default(1)
                ->index('active');
            $table
                ->integer('order')
                ->default(0)
                ->index('order');
        });

        return true;
    }

    /**
     * DatabaseTableBuilder::change()
     * Changes table according to form requirements.
     *
     * @param string[][] requested_columns
     *
     * @return string[] result - empty means no changes
     */
    public function change($requested_columns)
    {
        if (empty($this->table_name) || empty($this->model)) {
            return false;
        }

        $schema = Capsule::schema()->getColumnListing($this->table_name);

        $result = array();

        // Go through $requested_columns and add in any missing table columns
        foreach ($requested_columns as $row_name => $row) {
            if ($row['type'] == 'fieldset') {
                continue;
            }
            if (
                method_exists($this->model, $row_name)
                && get_class($this->model->{$row_name}()) == "Illuminate\Database\Eloquent\Relations\BelongsToMany"
            ) {
                $link_table_name = $this->model->{$row_name}()->getTable();
                if (! Capsule::schema()->hasTable($link_table_name)) {
                    $link_table_foreign_key = $this->model->{$row_name}()->getForeignKey();
                    $link_table_foreign_key = explode('.', $link_table_foreign_key)[1]; // remove the prepended table name
                    $link_table_other_key = $this->model->{$row_name}()->getOtherKey();
                    $link_table_other_key = explode('.', $link_table_other_key)[1]; // remove the prepended table name
                    Capsule::schema()->create($link_table_name, function ($table) use (
                        $link_table_foreign_key,
                        $link_table_other_key,
                    ): void {
                        $table->increments('id');
                        $table->integer($link_table_foreign_key)->index($link_table_foreign_key);
                        $table->integer($link_table_other_key)->index($link_table_other_key);
                    });
                    $result[] = 'Adding "'.$link_table_name.'" link table!';
                }
            } elseif (
                method_exists($this->model, $row_name)
                && get_class($this->model->{$row_name}()) == "Illuminate\Database\Eloquent\Relations\HasMany"
            ) {
                $foreign_key = $this->model->{$row_name}()->getPlainForeignKey();
                $related_model = $this->model->{$row_name}()->getRelated();
                $related_model_table = $related_model->getTable();
                if (! Capsule::schema()->hasColumn($related_model_table, $foreign_key)) {
                    Capsule::schema()->table($related_model_table, function ($table) use ($foreign_key): void {
                        $table->integer($foreign_key)->index($foreign_key);
                    });
                    $result[] = 'Adding "'.$foreign_key.'" (int) column to table "'.$related_model_table.'"!';
                }
                if ($row['type'] == 'images') {
                    if (! Capsule::schema()->hasColumn($related_model_table, 'path')) {
                        Capsule::schema()->table($related_model_table, function ($table): void {
                            $table->string('path');
                        });
                        $result[] = 'Adding "path" (string) column to table "'.$related_model_table.'"!';
                    }
                }
            } elseif (! method_exists($this->model, $row_name)) {
                $exists_in_table = false;
                for ($i = 0; $i < count($schema); ++$i) {
                    if ($schema[$i] == $row_name) {
                        $exists_in_table = true;
                    }
                }

                if (! $exists_in_table) {
                    if ($row['type'] == 'number' || $row['type'] == 'select') {
                        Capsule::schema()->table($this->table_name, function ($table) use ($row_name): void {
                            $table->integer($row_name);
                        });
                        $result[] = 'Adding '.$row_name." (int) column! Don't forget to optimize this!";
                    } elseif ($row['type'] == 'checkbox') {
                        Capsule::schema()->table($this->table_name, function ($table) use ($row_name): void {
                            $table->tinyInteger($row_name);
                        });
                        $result[] = 'Adding '.$row_name." (tinyint) column! Don't forget to optimize this!";
                    } elseif ($row['type'] == 'datetime') {
                        Capsule::schema()->table($this->table_name, function ($table) use ($row_name): void {
                            $table->dateTime($row_name);
                        });
                        $result[] = 'Adding '.$row_name." (datetime) column! Don't forget to optimize this!";
                    } elseif ($row['type'] == 'coordinates') {
                        Capsule::schema()->table($this->table_name, function ($table) use ($row_name): void {
                            $table->string($row_name, 64);
                        });
                        $result[] = 'Adding '.$row_name." (tinyint) column! Don't forget to optimize this!";
                    } elseif ($row['type'] == 'textarea') {
                        Capsule::schema()->table($this->table_name, function ($table) use ($row_name): void {
                            $table->text($row_name);
                        });
                        $result[] = 'Adding '.$row_name." (text) column! Don't forget to optimize this!";
                    } elseif ($row['type'] == 'image' || $row['type'] == 'file') {
                        Capsule::schema()->table($this->table_name, function ($table) use ($row_name): void {
                            $table->string($row_name, 64);
                        });
                        $result[] = 'Adding '.$row_name." (varchar 64) column! Don't forget to optimize this!";
                    } elseif ($row['type'] === 'images' && ! method_exists($this->model, $row_name)) {
                        $message = 'To add an \'images\' multi field make sure to make a '.MODEL_CLASS_NAME.'Images ';
                        if (MODEL_CLASS_NAME.'Images' != MODEL_CLASS_NAME.ucfirst($row_name)) {
                            $message .= 'or '.MODEL_CLASS_NAME.ucfirst($row_name).' ';
                        }
                        $message .=
                            'or similar eloquent model and add the following to the '
                            .MODEL_CLASS_NAME
                            .' class file.<br/><br/>
                        public function '
                            .$row_name
                            .'() {<br/>
                        &nbsp;&nbsp;&nbsp;&nbsp;return $this->hasMany("Mtc\Modules\\'
                            .MODEL_CLASS_NAME
                            .'Image\Classes\\'
                            .MODEL_CLASS_NAME
                            .'Image");<br/>
                        }<br/>';
                        if (MODEL_CLASS_NAME.'Images' != MODEL_CLASS_NAME.ucfirst($row_name)) {
                            $message .=
                                'or<br/>
                            public function '
                                .$row_name
                                .'() {<br/>
                            &nbsp;&nbsp;&nbsp;&nbsp;return $this->hasMany("Mtc\Modules\\'
                                .MODEL_CLASS_NAME
                                .ucfirst($row_name)
                                .'\Classes\\'
                                .MODEL_CLASS_NAME
                                .ucfirst($row_name)
                                .'");<br/>
                            }';
                        }
                        $result[] = $message;
                    } else {
                        Capsule::schema()->table($this->table_name, function ($table) use ($row_name): void {
                            $table->string($row_name, 1024);
                        });
                        $result[] = 'Adding '.$row_name." (varchar 1024) column! Don't forget to optimize this!";
                    }
                }
            }
        }

        // Go through current $schema and remove any table columns not mentioned in $requested_columns
        for ($i = 0; $i < count($schema); ++$i) {
            if (in_array($schema[$i], $this->special_columns)) {
                continue;
            }
            $exists_in_requested_columns = false;
            foreach ($requested_columns as $row_name => $row) {
                if ($schema[$i] == $row_name) {
                    $exists_in_requested_columns = true;
                }
            }

            if (! $exists_in_requested_columns) {
                $row_name = $schema[$i];
                Capsule::schema()->table($this->table_name, function ($table) use ($row_name): void {
                    $table->dropColumn($row_name);
                });
                $result[] = 'Dropping '.$row_name.' column!';
            }
        }

        return $result;
    }
}
