<?php
/**
 * Class ModuleActions
 * Helper functions for Module Actions.
 * @copyright MTC media Ltd.
 * @author Rihards Silins
 * @version 2 14/07/2016
 */
namespace Mtc\Modules\ModuleBuilder\Classes;

use \Mtc\Modules\ModuleBuilder\Classes\ModuleHelper;
use \Illuminate\Support\Facades\Event;
use \DateTime;

class ModuleActions
{
    /*
     * static string[] $image_mime_rules - images mime rules
     */
    static $image_mime_rules = [
        '^image\/jpg$',
        '^image\/jpeg$',
        '^image\/png$',
        '^image\/gif$'
    ];

    /*
     * static string $file_mime_rules - file mime rules
     */
    static $file_mime_rules = '^((?!python|zip|compressed|x-msdownload|x-sh|php|perl|inode|x-dosexec|octet-stream)[\s\S])*$';

    /**
     * Validate and update action
     * @param mixed[] $_post
     * @param mixed[] $_get
     * @param mixed[] $_files
     * @param Object $model
     * @param mixed[][] $model_form
     * @return string[] $errors
     */
    public static function validateUpdate($_post, $_get, $_files, $model, $model_form)
    {
        $errors = array();
        $model_class_name = get_class($model);
        foreach ($model_form as $attribute => $attribute_row) {
            if (isset($attribute_row['rules'])) {
                $attribute_rules = $attribute_row['rules'];
            } else {
                $attribute_rules = [];
            }

            if (empty($attribute_row['label'])) {
                $attribute_row['label'] = str_replace("_", " ", $attribute);
                $attribute_row['label'] = ucwords($attribute_row['label']);
            }

            if (isset($attribute_rules['required']) && $attribute_rules['required'] === true) {
                if (
                    !empty($attribute_row['type']) &&
                    in_array($attribute_row['type'], ["image", "file"])
                ) {
                    if (
                        empty($model->$attribute) &&
                        empty($_files[$model_class_name]['tmp_name'][$attribute]) ||
                        isset($_post[$model_class_name][$attribute]) && $_post[$model_class_name][$attribute] === ""
                    ) {
                        if (isset($attribute_rules['error_message']) && !empty($attribute_rules['error_message'])) {
                            $errors[] = $attribute_rules['error_message'];
                        } else {
                            $errors[] = $attribute_row['label'] . ' is required!';
                        }
                        continue;
                    }
                } elseif (
                    !empty($attribute_row['type']) &&
                    in_array($attribute_row['type'], ["images", "files"])
                ) {
                    if (isset($_files[$model_class_name]['tmp_name'][$attribute])) {
                        $i = -1;
                        $file_is_set = false;
                        foreach ($_files[$model_class_name]['tmp_name'][$attribute] as $single_item_from_a_multi_value) {
                            $i++;
                            if (!empty($_files[$model_class_name]['tmp_name'][$attribute][$i])) {
                                $file_is_set = true;
                                break;
                            }
                        }
                        if (!$file_is_set) {
                            if (isset($attribute_rules['error_message']) && !empty($attribute_rules['error_message'])) {
                                $errors[] = $attribute_rules['error_message'];
                            } else {
                                $errors[] = "At least one field in " . $attribute_row['label'] . ' is required to be filled!';
                            }
                            continue;
                        }
                    }
                } elseif (!isset($_post[$model_class_name][$attribute])) {
                    if (isset($attribute_rules['error_message']) && !empty($attribute_rules['error_message'])) {
                        $errors[] = $attribute_rules['error_message'];
                    } else {
                        $errors[] = $attribute_row['label'] . ' is required!';
                    }
                    continue;
                }
            }
            if (isset($attribute_rules['notEmpty']) && $attribute_rules['notEmpty'] === true) {
                if (empty($_post[$model_class_name][$attribute])) {
                    if (isset($attribute_rules['error_message']) && !empty($attribute_rules['error_message'])) {
                        $errors[] = $attribute_rules['error_message'];
                    } else {
                        $errors[] = $attribute_row['label'] . ' must not be empty!';
                    }
                    continue;
                }
            }
            if (isset($attribute_rules['notEmptyIfOtherNotEmpty']) && !empty($other_field = $attribute_rules['notEmptyIfOtherNotEmpty'])) {
                if (
                    !empty($_post[$model_class_name][$other_field])
                    && empty($_post[$model_class_name][$attribute])
                   ) {
                    if (!empty($attribute_rules['neione_error_message'])) {
                        $errors[] = $attribute_rules['neione_error_message'];
                    } else {
                        $errors[] = $attribute_row['label'] . ' must not be empty when ' . $model_form[$other_field]['label'] . ' is not empty!';
                    }
                    continue;
                }
            }
            if (isset($attribute_rules['regex'])) {
                if (!preg_match($attribute_rules['regex'], $_post[$model_class_name][$attribute])) {
                    if (isset($attribute_rules['error_message']) && !empty($attribute_rules['error_message'])) {
                        $errors[] = $attribute_rules['error_message'];
                    } else {
                        $errors[] = $attribute_row['label'] . ' is invalid (' . $attribute_rules['regex'] . ')!';
                    }
                    continue;
                }
            }
            // If field value has to be unique in table
            if (!empty($attribute_rules['unique'])) {
                $model_with_same_value_in_this_attribute = $model_class_name::query()
                    ->where("id", "!=", $model->id)
                    ->where($attribute, $_post[$model_class_name][$attribute])
                    ->first();
                if ($model_with_same_value_in_this_attribute !== null) {
                    if (isset($attribute_rules['error_message']) && !empty($attribute_rules['error_message'])) {
                        $errors[] = $attribute_rules['error_message'];
                    } else {
                        $errors[] = $attribute_row['label'] . ' value has to be unique across all ' . MODEL_PLURAL . '!';
                    }
                    continue;
                }
            }
            if (!empty($attribute_row['type']) && ($attribute_row['type'] === "images" || $attribute_row['type'] === "files")) {
                $i = -1;
                foreach ($_files[$model_class_name]['tmp_name'][$attribute] as $single_item_from_a_multi_value) {
                    $i++;
                    if (!empty($_files[$model_class_name]['tmp_name'][$attribute][$i])) {
                        $errors = array_merge(
                            self::validateFileMime($_files[$model_class_name]['tmp_name'][$attribute][$i], $attribute_rules, $attribute_row),
                            $errors
                        );
                    }
                }
            } else {
                if (!empty($_files[$model_class_name]['tmp_name'][$attribute])) {
                    $errors = array_merge(
                        self::validateFileMime($_files[$model_class_name]['tmp_name'][$attribute], $attribute_rules, $attribute_row),
                        $errors
                    );
                }
            }
        }
        return $errors;
    }

    /**
     * Validates a file against module mime rules
     * @param string $tmp_name
     * @param string[][] $attribute_rules
     * @param mixed[][][] $attribute_row
     * @return string[] $errors
     */
    private static function validateFileMime($tmp_name, $attribute_rules, $attribute_row) {
        $errors = [];
        if (isset($attribute_rules['mime'])) {
            $mime_rules = $attribute_rules['mime'];
        } else {
            if ($attribute_row['type'] === "image" || $attribute_row['type'] === "images") {
                $mime_rules = self::$image_mime_rules;
            } else {
                $mime_rules = self::$file_mime_rules;
            }
        }
        if (!is_array($mime_rules)) {
            $mime_rules = [$mime_rules];
        }
        $valid_file = false;
        foreach ($mime_rules as $mime_rule) {
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $file_mime = finfo_file($finfo, $tmp_name);
            if (preg_match("/{$mime_rule}/", $file_mime)) {
                $valid_file = true;
            }
        }
        if (!$valid_file) {
            if (isset($attribute_rules['error_message']) && !empty($attribute_rules['error_message'])) {
                $errors[] = $attribute_rules['error_message'];
            } else {
                $errors[] = 'The file you attempted to upload for ' . $attribute_row['label'] . ' has an invalid file type!';
            }
            return $errors;
        }
        return $errors;
    }

    /**
     * Datetime row value adjust
     * @param mixed $value
     * @param string $key
     * @param mixed[] $model_form
     * @return mixed $value
     */
    public static function datetimeAdjust($value, $attr_name, $model_form)
    {
        if (empty($model_form[$attr_name]['datetime_db_format'])) {
            $model_form[$attr_name]['datetime_db_format'] = 'Y-m-d H:i:s';
        }

        if (empty($model_form[$attr_name]['datetime_frontend_format'])) {
            $model_form[$attr_name]['datetime_frontend_format'] = 'd/m/Y H:i';
        }

        $datetime = date_create_from_format($model_form[$attr_name]['datetime_frontend_format'], $value);
        if ($datetime === false) {
            $datetime = new DateTime();
            $value = $datetime->format($model_form[$attr_name]['datetime_db_format']);
        } else {
            $value = $datetime->format($model_form[$attr_name]['datetime_db_format']);
        }
        return $value;
    }

    /**
     * Resolve update action
     * @param mixed[] $_post
     * @param mixed[] $_get
     * @param mixed[] $_files
     * @param Object $model
     * @param mixed[][] $model_form
     * @param string $model_name_singular
     * @return string[] $errors
     */
    public static function update($_post, $_get, $_files, $model, $model_form, $image_folders, $model_name_singular)
    {
        // validation
        $errors = self::validateUpdate($_post, $_get, $_files, $model, $model_form);
        if (!empty($errors)) {
            return $errors;
        }

        $model_class_name = get_class($model);

        \HooksAdapter::do_action(
            __CLASS__ . '/' . __FUNCTION__ . '/before_post_loop',
            [
                '_post' => $_post,
                '_get' => $_get,
                '_files' => $_files,
                'model' => $model,
                'model_form' => $model_form,
            ]
        );

        $relationships = array();

        // loop through all the file values
        if (isset($_files[$model_class_name]['tmp_name'])) {
            foreach ($_files as $files_model_name => $files_model_item) {
                foreach ($files_model_item['tmp_name'] as $attr_name => $value) {
                    if ($model_form[$attr_name]['type'] === "images" || $model_form[$attr_name]['type'] === "files") {
                        $i = -1;
                        foreach ($value as $single_item_from_a_multi_value) {
                            $i++;
                            if (empty($single_item_from_a_multi_value)) {
                                if (empty($relationships[get_class($model->{$attr_name}())][$attr_name])) {
                                    $relationships[get_class($model->{$attr_name}())][$attr_name] = array();
                                }
                                continue;
                            }
                            $image_path = add_fullcms_image(
                                $image_folders['cms_images'],
                                $_files[$model_class_name]['size'][$attr_name][$i],
                                $_files[$model_class_name]['name'][$attr_name][$i],
                                $_files[$model_class_name]['tmp_name'][$attr_name][$i]
                            );
                            if (empty($image_path)) {
                                if (empty($relationships[get_class($model->{$attr_name}())][$attr_name])) {
                                    $relationships[get_class($model->{$attr_name}())][$attr_name] = array();
                                }
                                continue;
                            }
                            $related_model = $model->{$attr_name}()->getRelated();
                            $related_model_name = get_class($related_model);
                            $new_related_model = new $related_model_name();
                            $new_related_model->path = $image_path;
                            $new_related_model->save();
                            if (empty($relationships[get_class($model->{$attr_name}())][$attr_name])) {
                                $relationships[get_class($model->{$attr_name}())][$attr_name] = array();
                            }
                            $relationships[get_class($model->{$attr_name}())][$attr_name][] = $new_related_model;
                        }
                    } else {
                        if ($model_form[$attr_name]['type'] == 'image') {
                            $file = add_fullcms_image(
                                $image_folders['cms_images'],
                                $_files[$model_class_name]['size'][$attr_name],
                                $_files[$model_class_name]['name'][$attr_name],
                                $_files[$model_class_name]['tmp_name'][$attr_name]
                            );
                        } else {
                            $file = add_fullcms_file(
                                $_files[$model_class_name]['size'][$attr_name],
                                $_files[$model_class_name]['name'][$attr_name],
                                $_files[$model_class_name]['tmp_name'][$attr_name]
                            );
                        }
                        if ($file !== false) {
                            $model->{$attr_name} = $file;
                        }
                    }
                }
            }
        }

        foreach ($_post[$model_class_name] as $attr_name => $value) {
            \HooksAdapter::do_action(
                __CLASS__ . '/' . __FUNCTION__ . '/in_post_loop',
                [
                    '_post' => $_post,
                    '_get' => $_get,
                    '_files' => $_files,
                    'model' => $model,
                    'model_form' => $model_form,
                    'attr_name' => $attr_name,
                    'value' => $value,
                ]
            );

            // custom data transform before insert into db
            if (isset($model_form[$attr_name]['type']) && $model_form[$attr_name]['type'] === 'datetime') {
                $value = self::datetimeAdjust($value, $attr_name, $model_form);
            }

            \HooksAdapter::do_action(
                __CLASS__ . '/' . __FUNCTION__ . '/in_post_loop_before_setting_attribute',
                [
                    '_post' => $_post,
                    '_get' => $_get,
                    '_files' => $_files,
                    'model' => $model,
                    'model_form' => $model_form,
                    'attr_name' => $attr_name,
                    'value' => $value,
                ]
            );

            if (is_array($value)) {
                $related_model = $model->{$attr_name}()->getRelated();
                $related_model_name = get_class($related_model);
                $values = array_reverse($value);
                foreach ($values as $value) {
                    if (empty($value)) {
                        if (empty($relationships[get_class($model->{$attr_name}())][$attr_name])) {
                            $relationships[get_class($model->{$attr_name}())][$attr_name] = array();
                        }
                        continue;
                    }

                    if (get_class($model->{$attr_name}()) == "Illuminate\Database\Eloquent\Relations\BelongsToMany") {
                        $option = $related_model_name::find($value);
                        if (empty($relationships[get_class($model->{$attr_name}())][$attr_name])) {
                            $relationships[get_class($model->{$attr_name}())][$attr_name] = array();
                        }
                        $relationships[get_class($model->{$attr_name}())][$attr_name][] = $option;
                    } elseif (get_class($model->{$attr_name}()) == "Illuminate\Database\Eloquent\Relations\HasMany") {
                        $new_related_model = new $related_model_name();
                        $new_related_model->path = $value;
                        $new_related_model->save();
                        if (empty($relationships[get_class($model->{$attr_name}())][$attr_name])) {
                            $relationships[get_class($model->{$attr_name}())][$attr_name] = array();
                        }
                        $relationships[get_class($model->{$attr_name}())][$attr_name][] = $new_related_model;
                    }
                }
            } else {
                $model->{$attr_name} = $value;
            }
        }

        if (empty($model->id)) {
            $model->order = 0;
            $top_order_item = $model_class_name::orderBy('order', 'desc')->first();
            if ($top_order_item !== null) {
                $model->order = $top_order_item->order + 1;
            }
        }

        \HooksAdapter::do_action(
            __CLASS__ . '/' . __FUNCTION__ . '/before_save',
            [
                '_post' => $_post,
                '_get' => $_get,
                '_files' => $_files,
                'model' => $model,
                'model_form' => $model_form,
            ]
        );

        if ($model->save()) {
            // remove
            foreach ($relationships as $relationship_type => $type_relationships) {
                if ($relationship_type == "Illuminate\Database\Eloquent\Relations\BelongsToMany") {
                    foreach ($type_relationships as $key => $key_relationships) {
                        $model->{$key}()->detach(); // detach the relationships
                    }
                } elseif ($relationship_type == "Illuminate\Database\Eloquent\Relations\HasMany") {
                    foreach ($type_relationships as $key => $key_relationships) {
                        $existing_relations = $model->{$key}()->get();
                        foreach ($existing_relations as $existing_relation) {
                            $existing_relation->delete();
                        }
                    }
                }
            }
            // add
            foreach ($relationships as $relationship_type => $type_relationships) {
                if ($relationship_type == "Illuminate\Database\Eloquent\Relations\BelongsToMany") {
                    foreach ($type_relationships as $key => $key_relationships) {
                        for ($i = 0; $i < count($key_relationships); $i++) {
                            $model->{$key}()->save($key_relationships[$i]);
                        }
                    }
                } elseif ($relationship_type == "Illuminate\Database\Eloquent\Relations\HasMany") {
                    foreach ($type_relationships as $key => $key_relationships) {
                        for ($i = 0; $i < count($key_relationships); $i++) {
                            $model->{$key}()->save($key_relationships[$i]);
                        }
                    }
                }
            }

            Event::dispatch(
                MODEL_CLASS_NAMESPACE_PREFIX . MODEL_CLASS_NAME . " " . __CLASS__ . "::" . __FUNCTION__ . " saved",
                [
                    $model
                ]
            );

            unset($_post[$model_class_name]);
            if (!isset($_get['id']) || empty($_get['id'])) {
                $_SESSION['model_form']['success'][] = ucwords($model_name_singular) . ' created!';
                header('Location: '.ModuleHelper::generateNondestructiveUrl(
                    [
                        'id' => $model->id
                    ]
                ));
                exit();
            }
            $_SESSION['model_form']['success'][] = ucwords($model_name_singular) . ' updated!';
        }
        return array();
    }

    /**
     * Resolve module edit actions
     * @param mixed[] $_post
     * @param mixed[] $_get
     * @param mixed[] $_files
     * @param Object $model
     * @param mixed[][] $model_form
     * @param mixed[][] $image_folders
     * @param string $model_name_singular
     * @return string[] $errors
     */
    public static function resolveEdit($_post, $_get, $_files, $model, $model_form, $image_folders, $model_name_singular = MODEL_SINGULAR)
    {
        $errors = array();
        // Process and update action request.
        if (!empty($_post['action']) && $_post['action'] == 'update') {
            $errors = self::update($_post, $_get, $_files, $model, $model_form, $image_folders, $model_name_singular);
        }
        return $errors;
    }

    /**
     * Resolve module manage actions
     * @param mixed[] $_post
     * @param mixed[] $_get
     * @param mixed[] $_files
     * @param Object $model
     * @param mixed[][] $model_form
     * @param mixed[][] $image_folders
     * @param string $model_name_singular
     * @return string[] $errors
     */
    public static function resolveManage($_post, $_get, $_files, $model, $model_form, $image_folders, $model_name_singular = MODEL_SINGULAR)
    {
        $errors = array();
        // Process and update action request.
        if (!empty($_get['action']) && $_get['action'] == 'delete' && !empty($_get['action_id']) && is_numeric($_get['action_id'])) {
            $errors = self::delete($_get, $model, $model_name_singular);
        }
        return $errors;
    }

    /**
     * Resolve delete action
     * @param mixed[] $_get
     * @param Object $model
     * @param string $model_name_singular
     * @return void
     */
    public static function delete($_get, $model, $model_name_singular)
    {
        $class_name = get_class($model);
        $object = $class_name::find($_get['action_id']);
        if ($object !== null) {
            $_SESSION['model_form']['success'][] =  ucwords($model_name_singular)." deleted!";
            $object->delete();
            header(
                "Location: ".
                ModuleHelper::generateNondestructiveUrl(
                    array(),
                    array('action', 'action_id')
                )
            );
            exit();
        } else {
            $_SESSION['model_form']['alert'][] =  "The ".ucwords($model_name_singular)." you wanted to delete already doesn't exist!";
            header(
                "Location: ".
                ModuleHelper::generateNondestructiveUrl(
                    array(),
                    array('action', 'action_id')
                )
            );
            exit();
        }
    }
}
