<?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 \DateTime;
use \Illuminate\Support\Facades\Event;
use \Mtc\Modules\ModuleBuilder\Classes\ModuleHelper;

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_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();
        }
    }
}
