<?php

namespace App\Controllers;

use App\Libraries\Barcode_lib;
use App\Libraries\Mailchimp_lib;
use App\Libraries\Receiving_lib;
use App\Libraries\Sale_lib;
use App\Libraries\Tax_lib;
use App\Models\Appconfig;
use App\Models\Attribute;
use App\Models\Customer_rewards;
use App\Models\Dinner_table;
use App\Models\Module;
use App\Models\Enums\Rounding_mode;
use App\Models\Stock_location;
use App\Models\Tax;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Encryption\EncrypterInterface;
use Config\Database;
use Config\OSPOS;
use Config\Services;
use DirectoryIterator;
use NumberFormatter;
use ReflectionException;

class Config extends Secure_Controller
{
    protected $helpers = ['security'];
    private BaseConnection $db;
    private EncrypterInterface $encrypter;
    private Barcode_lib $barcode_lib;
    private Sale_lib $sale_lib;
    private Receiving_lib $receiving_lib;
    private Tax_lib $tax_lib;
    private Appconfig $appconfig;
    private Attribute $attribute;
    private Customer_rewards $customer_rewards;
    private Dinner_table $dinner_table;
    protected Module $module;
    private Stock_location $stock_location;
    private Tax $tax;
    private array $config;


    public function __construct()
    {
        parent::__construct('config');

        $this->barcode_lib = new Barcode_lib();
        $this->sale_lib = new Sale_lib();
        $this->receiving_lib = new receiving_lib();
        $this->tax_lib = new Tax_lib();
        $this->appconfig = model(Appconfig::class);
        $this->attribute = model(Attribute::class);
        $this->customer_rewards = model(Customer_rewards::class);
        $this->dinner_table = model(Dinner_table::class);
        $this->module = model(Module::class);
        $this->stock_location = model(Stock_location::class);
        $this->tax = model(Tax::class);
        $this->config = config(OSPOS::class)->settings;
        $this->db = Database::connect();

        helper('security');
        if (check_encryption()) {
            $this->encrypter = Services::encrypter();
        } else {
            log_message('alert', 'Error preparing encryption key');
        }
    }

    /**
     * This function loads all the licenses starting with the first one being OSPOS one
     */
    private function _licenses(): array    // TODO: remove hungarian notation.  Super long function.  Perhaps we need to refactor out functions?
    {
        $i = 0;
        $composer = false;
        $npmProd = false;
        $npmDev = false;
        $license = [];

        $license[$i]['title'] = 'Akshyaraanga Sanjaal Point of Sale ' . config('App')->application_version;

        if (file_exists('license/LICENSE')) {
            $license[$i]['text'] = file_get_contents('license/LICENSE', false, null, 0, 3000);
        } else {
            $license[$i]['text'] = 'LICENSE file must be in OSPOS license directory. You are not allowed to use OSPOS application until the distribution copy of LICENSE file is present.';
        }

        $dir = new DirectoryIterator('license');    // Read all the files in the dir license

        foreach ($dir as $fileinfo) {    // TODO: $fileinfo doesn't match our variable naming convention
            // License files must be in couples: .version (name & version) & .license (license text)
            if ($fileinfo->isFile()) {
                if ($fileinfo->getExtension() == 'version') {
                    ++$i;

                    $basename = 'license/' . $fileinfo->getBasename('.version');

                    $license[$i]['title'] = file_get_contents($basename . '.version', false, null, 0, 100);

                    $license_text_file = $basename . '.license';

                    if (file_exists($license_text_file)) {
                        $license[$i]['text'] = file_get_contents($license_text_file, false, null, 0, 2000);
                    } else {
                        $license[$i]['text'] = $license_text_file . ' file is missing';
                    }
                } elseif ($fileinfo->getBasename() == 'composer.LICENSES') {
                    // Set a flag to indicate that the composer.LICENSES file is available and needs to be attached at the end
                    $composer = true;
                } elseif ($fileinfo->getBasename() == 'npm-prod.LICENSES') {
                    // Set a flag to indicate that the npm-prod.LICENSES file is available and needs to be attached at the end
                    $npmProd = true;
                } elseif ($fileinfo->getBasename() == 'npm-dev.LICENSES') {
                    // Set a flag to indicate that the npm-dev.LICENSES file is available and needs to be attached at the end
                    $npmDev = true;
                }
            }
        }

        // Attach the licenses from the LICENSES file generated by Composer
        if ($composer) {
            ++$i;
            $license[$i]['title'] = 'Composer Libraries';
            $license[$i]['text'] = '';

            $file = file_get_contents('license/composer.LICENSES');
            $array = json_decode($file, true);

            if (isset($array['dependencies'])) {
                foreach ($array['dependencies'] as $dependency => $details) {
                    $license[$i]['text'] .= "library: $dependency\n";

                    foreach ($details as $key => $value) {
                        if (is_array($value)) {
                            $license[$i]['text'] .= "$key: " . implode(' ', $value) . "\n";
                        } else {
                            $license[$i]['text'] .= "$key: $value\n";
                        }
                    }

                    $license[$i]['text'] .= "\n";
                }
                $license[$i]['text'] = rtrim($license[$i]['text'], "\n");
            }
        }

        // Attach the licenses from the LICENSES file generated by license-report
        if ($npmProd) {
            ++$i;
            $license[$i]['title'] = 'NPM Production Libraries';
            $license[$i]['text'] = '';

            $file = file_get_contents('license/npm-prod.LICENSES');
            $array = json_decode($file, true);

            foreach ($array as $dependency) {
                $license[$i]['text'] .= "library: {$dependency['name']}\n";
                $license[$i]['text'] .= "authors: {$dependency['author']}\n";
                $license[$i]['text'] .= "website: {$dependency['homepage']}\n";
                $license[$i]['text'] .= "version: {$dependency['installedVersion']}\n";
                $license[$i]['text'] .= "license: {$dependency['licenseType']}\n";

                $license[$i]['text'] .= "\n";
            }
            $license[$i]['text'] = rtrim($license[$i]['text'], "\n");
        }

        if ($npmDev) {
            ++$i;
            $license[$i]['title'] = 'NPM Development Libraries';
            $license[$i]['text'] = '';

            $file = file_get_contents('license/npm-dev.LICENSES');
            $array = json_decode($file, true);

            foreach ($array as $dependency) {
                $license[$i]['text'] .= "library: {$dependency['name']}\n";
                $license[$i]['text'] .= "authors: {$dependency['author']}\n";
                $license[$i]['text'] .= "website: {$dependency['homepage']}\n";
                $license[$i]['text'] .= "version: {$dependency['installedVersion']}\n";
                $license[$i]['text'] .= "license: {$dependency['licenseType']}\n";

                $license[$i]['text'] .= "\n";
            }
            $license[$i]['text'] = rtrim($license[$i]['text'], "\n");
        }

        return $license;
    }

    /**
     * This function loads all the available themes in the dist/bootswatch directory
     * @return array
     */
    private function _themes(): array    // TODO: Hungarian notation
    {
        $themes = [];

        // Read all themes in the dist folder
        $dir = new DirectoryIterator('resources/bootswatch');

        foreach ($dir as $dirinfo) {    // TODO: $dirinfo doesn't follow naming convention
            if ($dirinfo->isDir() && !$dirinfo->isDot() && $dirinfo->getFileName() != 'fonts') {
                $file = $dirinfo->getFileName();
                $themes[$file] = ucfirst($file);
            }
        }

        asort($themes);

        return $themes;
    }

    /**
     */
    public function getIndex(): void
    {
        $data['stock_locations'] = $this->stock_location->get_all()->getResultArray();
        $data['dinner_tables'] = $this->dinner_table->get_all()->getResultArray();
        $data['customer_rewards'] = $this->customer_rewards->get_all()->getResultArray();
        $data['support_barcode'] = $this->barcode_lib->get_list_barcodes();
        $data['barcode_fonts'] = $this->barcode_lib->listfonts('fonts');
        $data['logo_exists'] = $this->config['company_logo'] != '';
        $data['line_sequence_options'] = $this->sale_lib->get_line_sequence_options();
        $data['register_mode_options'] = $this->sale_lib->get_register_mode_options();
        $data['invoice_type_options'] = $this->sale_lib->get_invoice_type_options();
        $data['rounding_options'] = rounding_mode::get_rounding_options();
        $data['tax_code_options'] = $this->tax_lib->get_tax_code_options();
        $data['tax_category_options'] = $this->tax_lib->get_tax_category_options();
        $data['tax_jurisdiction_options'] = $this->tax_lib->get_tax_jurisdiction_options();
        $data['show_office_group'] = $this->module->get_show_office_group();
        $data['currency_code'] = $this->config['currency_code'] ?? '';
        $data['dbVersion'] = mysqli_get_server_info($this->db->getConnection());

        // Load all the license statements, they are already XSS cleaned in the private function
        $data['licenses'] = $this->_licenses();

        // Load all the themes, already XSS cleaned in the private function
        $data['themes'] = $this->_themes();

        // General related fields
        $image_allowed_types = ['jpg', 'jpeg', 'gif', 'svg', 'webp', 'bmp', 'png', 'tif', 'tiff'];
        $data['image_allowed_types'] = array_combine($image_allowed_types, $image_allowed_types);
        $data['selected_image_allowed_types'] = explode(',', $this->config['image_allowed_types']);

        // Integrations Related fields
        $data['mailchimp']    = [];

        if (check_encryption()) {    // TODO: Hungarian notation
            if (!isset($this->encrypter)) {
                helper('security');
                $this->encrypter = Services::encrypter();
            }

            $data['mailchimp']['api_key'] = (isset($this->config['mailchimp_api_key']) && !empty($this->config['mailchimp_api_key']))
                ? $this->encrypter->decrypt($this->config['mailchimp_api_key'])
                : '';

            $data['mailchimp']['list_id'] = (isset($this->config['mailchimp_list_id']) && !empty($this->config['mailchimp_list_id']))
                ? $this->encrypter->decrypt($this->config['mailchimp_list_id'])
                : '';

            // Remove any backup of .env created by check_encryption()
            remove_backup();
        } else {
            $data['mailchimp']['api_key'] = '';
            $data['mailchimp']['list_id'] = '';
        }

        $data['mailchimp']['lists'] = $this->_mailchimp();

        echo view('configs/manage', $data);
    }

    /**
     * Saves company information. Used in app/Views/configs/info_config.php
     *
     * @throws ReflectionException
     * @return void
     * @noinspection PhpUnused
     */
    public function postSaveInfo(): void
    {
        $upload_data = $this->upload_logo();
        $upload_success = empty($upload_data['error']);

        $batch_save_data = [
            'company'       => $this->request->getPost('company'),
            'address'       => $this->request->getPost('address'),
            'phone'         => $this->request->getPost('phone'),
            'email'         => strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL)),
            'fax'           => $this->request->getPost('fax'),
            'website'       => $this->request->getPost('website', FILTER_SANITIZE_URL),
            'return_policy' => $this->request->getPost('return_policy')
        ];

        if (!empty($upload_data['orig_name']) && $upload_data['raw_name']) {
            $batch_save_data['company_logo'] = $upload_data['raw_name'] . '.' . $upload_data['file_ext'];
        }

        $result = $this->appconfig->batch_save($batch_save_data);
        $success = $upload_success && $result;
        $message = lang('Config.saved_' . ($success ? '' : 'un') . 'successfully');
        $message = $upload_success ? $message : strip_tags($upload_data['error']);

        echo json_encode(['success' => $success, 'message' => $message]);
    }


    /**
     * @return array
     */
    private function upload_logo(): array
    {
        $file = $this->request->getFile('company_logo');
        if (!$file) {
            return [];
        }

        helper(['form']);
        $validation_rule = [
            'company_logo' => [
                'label' => 'Company logo',
                'rules' => [
                    'uploaded[company_logo]',
                    'is_image[company_logo]',
                    'max_size[company_logo,1024]',
                    'mime_in[company_logo,image/png,image/jpg,image/jpeg,image/gif]',
                    'ext_in[company_logo,png,jpg,gif]',
                    'max_dims[company_logo,800,680]',
                ]
            ]
        ];

        if (!$this->validate($validation_rule)) {
            return (['error' => $this->validator->getError('company_logo')]);
        }


        $filename = $file->getClientName();
        $info = pathinfo($filename);

        $file_info = [
            'orig_name' => $filename,
            'raw_name'  => $info['filename'],
            'file_ext'  => $file->guessExtension()
        ];

        $file->move(FCPATH . 'uploads/', $file_info['raw_name'] . '.' . $file_info['file_ext'], true);

        return ($file_info);
    }

    /**
     * Saves general configuration. Used in app/Views/configs/general_config.php
     *
     * @throws ReflectionException
     * @noinspection PhpUnused
     */
    public function postSaveGeneral(): void
    {
        $batch_save_data = [
            'theme'                             => $this->request->getPost('theme'),
            'login_form'                        => $this->request->getPost('login_form'),
            'default_sales_discount_type'       => $this->request->getPost('default_sales_discount_type') != null,
            'default_sales_discount'            => parse_decimals($this->request->getPost('default_sales_discount')),
            'default_receivings_discount_type'  => $this->request->getPost('default_receivings_discount_type') != null,
            'default_receivings_discount'       => parse_decimals($this->request->getPost('default_receivings_discount')),
            'enforce_privacy'                   => $this->request->getPost('enforce_privacy') != null,
            'receiving_calculate_average_price' => $this->request->getPost('receiving_calculate_average_price') != null,
            'lines_per_page'                    => $this->request->getPost('lines_per_page', FILTER_SANITIZE_NUMBER_INT),
            'notify_horizontal_position'        => $this->request->getPost('notify_horizontal_position'),
            'notify_vertical_position'          => $this->request->getPost('notify_vertical_position'),
            'image_max_width'                   => $this->request->getPost('image_max_width', FILTER_SANITIZE_NUMBER_INT),
            'image_max_height'                  => $this->request->getPost('image_max_height', FILTER_SANITIZE_NUMBER_INT),
            'image_max_size'                    => $this->request->getPost('image_max_size', FILTER_SANITIZE_NUMBER_INT),
            'image_allowed_types'               => implode(',', $this->request->getPost('image_allowed_types')),
            'gcaptcha_enable'                   => $this->request->getPost('gcaptcha_enable') != null,
            'gcaptcha_secret_key'               => $this->request->getPost('gcaptcha_secret_key'),
            'gcaptcha_site_key'                 => $this->request->getPost('gcaptcha_site_key'),
            'suggestions_first_column'          => $this->request->getPost('suggestions_first_column'),
            'suggestions_second_column'         => $this->request->getPost('suggestions_second_column'),
            'suggestions_third_column'          => $this->request->getPost('suggestions_third_column'),
            'giftcard_number'                   => $this->request->getPost('giftcard_number'),
            'derive_sale_quantity'              => $this->request->getPost('derive_sale_quantity') != null,
            'multi_pack_enabled'                => $this->request->getPost('multi_pack_enabled') != null,
            'include_hsn'                       => $this->request->getPost('include_hsn') != null,
            'category_dropdown'                 => $this->request->getPost('category_dropdown') != null
        ];

        $this->module->set_show_office_group($this->request->getPost('show_office_group') != null);

        if ($batch_save_data['category_dropdown'] == 1) {
            $definition_data['definition_name'] = 'ospos_category';
            $definition_data['definition_flags'] = 0;
            $definition_data['definition_type'] = 'DROPDOWN';
            $definition_data['definition_id'] = CATEGORY_DEFINITION_ID;
            $definition_data['deleted'] = 0;

            $this->attribute->save_definition($definition_data, CATEGORY_DEFINITION_ID);
        } elseif ($batch_save_data['category_dropdown'] == NO_DEFINITION_ID) {
            $this->attribute->deleteDefinition(CATEGORY_DEFINITION_ID);
        }

        $success = $this->appconfig->batch_save($batch_save_data);

        echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
    }

    /**
     * Checks a number against the currently selected locale. Used in app/Views/configs/locale_config.php
     *
     * @return void
     * @noinspection PhpUnused
     */
    public function postCheckNumberLocale(): void
    {
        $number_locale = $this->request->getPost('number_locale');
        $save_number_locale = $this->request->getPost('save_number_locale');

        $fmt = new NumberFormatter($number_locale, NumberFormatter::CURRENCY);
        if ($number_locale != $save_number_locale) {
            $currency_symbol = $fmt->getSymbol(NumberFormatter::CURRENCY_SYMBOL);
            $currency_code = $fmt->getTextAttribute(NumberFormatter::CURRENCY_CODE);
            $save_number_locale = $number_locale;
        } else {
            $currency_symbol = empty($this->request->getPost('currency_symbol')) ? $fmt->getSymbol(NumberFormatter::CURRENCY_SYMBOL) : $this->request->getPost('currency_symbol');
            $currency_code = empty($this->request->getPost('currency_code')) ? $fmt->getTextAttribute(NumberFormatter::CURRENCY_CODE) : $this->request->getPost('currency_code');
        }

        if ($this->request->getPost('thousands_separator') == 'false') {
            $fmt->setTextAttribute(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, '');
        }

        $fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $currency_symbol);
        $number_local_example = $fmt->format(1234567890.12300);

        echo json_encode([
            'success'               => $number_local_example != false,
            'save_number_locale'    => $save_number_locale,
            'number_locale_example' => $number_local_example,
            'currency_symbol'       => $currency_symbol,
            'currency_code'         => $currency_code,
        ]);
    }

    /**
     * Saves locale configuration. Used in app/Views/configs/locale_config.php
     *
     * @throws ReflectionException
     * @return void
     * @noinspection PhpUnused
     */
    public function postSaveLocale(): void
    {
        $exploded = explode(":", $this->request->getPost('language'));
        $batch_save_data = [
            'currency_symbol'       => $this->request->getPost('currency_symbol'),
            'currency_code'         => $this->request->getPost('currency_code'),
            'language_code'         => $exploded[0],
            'language'              => $exploded[1],
            'timezone'              => $this->request->getPost('timezone'),
            'dateformat'            => $this->request->getPost('dateformat'),
            'timeformat'            => $this->request->getPost('timeformat'),
            'thousands_separator'   => $this->request->getPost('thousands_separator') != null,
            'number_locale'         => $this->request->getPost('number_locale'),
            'currency_decimals'     => $this->request->getPost('currency_decimals', FILTER_SANITIZE_NUMBER_INT),
            'tax_decimals'          => $this->request->getPost('tax_decimals', FILTER_SANITIZE_NUMBER_INT),
            'quantity_decimals'     => $this->request->getPost('quantity_decimals', FILTER_SANITIZE_NUMBER_INT),
            'country_codes'         => htmlspecialchars($this->request->getPost('country_codes')),
            'payment_options_order' => $this->request->getPost('payment_options_order'),
            'date_or_time_format'   => $this->request->getPost('date_or_time_format') != null,
            'cash_decimals'         => $this->request->getPost('cash_decimals', FILTER_SANITIZE_NUMBER_INT),
            'cash_rounding_code'    => $this->request->getPost('cash_rounding_code'),
            'financial_year'        => $this->request->getPost('financial_year', FILTER_SANITIZE_NUMBER_INT)
        ];

        $success = $this->appconfig->batch_save($batch_save_data);

        echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
    }

    /**
     * Saves email configuration. Used in app/Views/configs/email_config.php
     *
     * @throws ReflectionException
     * @return void
     * @noinspection PhpUnused
     */
    public function postSaveEmail(): void
    {
        $password = '';

        if (check_encryption() && !empty($this->request->getPost('smtp_pass'))) {
            $password = $this->encrypter->encrypt($this->request->getPost('smtp_pass'));
        }

        $batch_save_data = [
            'protocol'     => $this->request->getPost('protocol'),
            'mailpath'     => $this->request->getPost('mailpath'),
            'smtp_host'    => $this->request->getPost('smtp_host'),
            'smtp_user'    => $this->request->getPost('smtp_user'),
            'smtp_pass'    => $password,
            'smtp_port'    => $this->request->getPost('smtp_port', FILTER_SANITIZE_NUMBER_INT),
            'smtp_timeout' => $this->request->getPost('smtp_timeout', FILTER_SANITIZE_NUMBER_INT),
            'smtp_crypto'  => $this->request->getPost('smtp_crypto')
        ];

        $success = $this->appconfig->batch_save($batch_save_data);

        echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
    }

    /**
     * Saves SMS message configuration. Used in app/Views/configs/message_config.php.
     *
     * @throws ReflectionException
     * @return void
     * @noinspection PhpUnused
     */
    public function postSaveMessage(): void
    {
        $password = '';

        if (check_encryption() && !empty($this->request->getPost('msg_pwd'))) {
            $password = $this->encrypter->encrypt($this->request->getPost('msg_pwd'));
        }

        $batch_save_data = [
            'msg_msg' => $this->request->getPost('msg_msg'),
            'msg_uid' => $this->request->getPost('msg_uid'),
            'msg_pwd' => $password,
            'msg_src' => $this->request->getPost('msg_src')
        ];

        $success = $this->appconfig->batch_save($batch_save_data);

        echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
    }

    /**
     * This function fetches all the available lists from Mailchimp for the given API key
     */
    private function _mailchimp(string $api_key = ''): array    // TODO: Hungarian notation
    {
        $mailchimp_lib = new Mailchimp_lib(['api_key' => $api_key]);

        $result = [];

        $lists = $mailchimp_lib->getLists();
        if ($lists !== false) {
            if (is_array($lists) && !empty($lists['lists']) && is_array($lists['lists'])) {
                foreach ($lists['lists'] as $list) {
                    $result[$list['id']] = $list['name'] . ' [' . $list['stats']['member_count'] . ']';
                }
            }
        }

        return $result;
    }

    /**
     * Gets Mailchimp lists when a valid API key is inserted. Used in app/Views/configs/integrations_config.php
     *
     * @return void
     * @noinspection PhpUnused
     */
    public function postCheckMailchimpApiKey(): void
    {
        $lists = $this->_mailchimp($this->request->getPost('mailchimp_api_key'));
        $success = count($lists) > 0;

        echo json_encode([
            'success'         => $success,
            'message'         => lang('Config.mailchimp_key_' . ($success ? '' : 'un') . 'successfully'),
            'mailchimp_lists' => $lists
        ]);
    }

    /**
     * Saves Mailchimp configuration. Used in app/Views/configs/integrations_config.php
     *
     * @throws ReflectionException
     * @return void
     * @noinspection PhpUnused
     */
    public function postSaveMailchimp(): void
    {
        $api_key = '';
        $list_id = '';

        if (check_encryption()) {
            $api_key_unencrypted = $this->request->getPost('mailchimp_api_key');
            if (!empty($api_key_unencrypted)) {
                $api_key = $this->encrypter->encrypt($api_key_unencrypted);
            }

            $list_id_unencrypted = $this->request->getPost('mailchimp_list_id');
            if (!empty($list_id_unencrypted)) {
                $list_id = $this->encrypter->encrypt($list_id_unencrypted);
            }
        }

        $batch_save_data = ['mailchimp_api_key' => $api_key, 'mailchimp_list_id' => $list_id];

        $success = $this->appconfig->batch_save($batch_save_data);

        echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
    }

    /**
     * Gets all stock locations. Used in app/Views/configs/stock_config.php
     *
     * @return void
     * @noinspection PhpUnused
     */
    public function getStockLocations(): void
    {
        $stock_locations = $this->stock_location->get_all()->getResultArray();

        echo view('partial/stock_locations', ['stock_locations' => $stock_locations]);
    }

    /**
     * @return void
     */
    public function getDinnerTables(): void
    {
        $dinner_tables = $this->dinner_table->get_all()->getResultArray();

        echo view('partial/dinner_tables', ['dinner_tables' => $dinner_tables]);
    }


    /**
     * Gets all tax categories.
     *
     * @return void
     */
    public function ajax_tax_categories(): void    // TODO: Is this function called anywhere in the code?
    {
        $tax_categories = $this->tax->get_all_tax_categories()->getResultArray();

        echo view('partial/tax_categories', ['tax_categories' => $tax_categories]);
    }

    /**
     * Gets all customer rewards. Used in app/Views/configs/reward_config.php
     *
     * @return void
     * @noinspection PhpUnused
     */
    public function getCustomerRewards(): void
    {
        $customer_rewards = $this->customer_rewards->get_all()->getResultArray();

        echo view('partial/customer_rewards', ['customer_rewards' => $customer_rewards]);
    }

    /**
     * @return void
     */
    private function _clear_session_state(): void    // TODO: Hungarian notation
    {
        $this->sale_lib->clear_sale_location();
        $this->sale_lib->clear_table();
        $this->sale_lib->clear_all();
        $this->receiving_lib = new Receiving_lib();
        $this->receiving_lib->clear_stock_source();
        $this->receiving_lib->clear_stock_destination();
        $this->receiving_lib->clear_all();
    }

    /**
     * Saves stock locations. Used in app/Views/configs/stock_config.php
     *
     * @return void
     * @noinspection PhpUnused
     */
    public function postSaveLocations(): void
    {
        $this->db->transStart();

        $not_to_delete = [];
        foreach ($this->request->getPost() as $key => $value) {
            if (str_contains($key, 'stock_location')) {
                // Save or update
                foreach ($value as $location_id => $location_name) {
                    $location_data = ['location_name' => $location_name];
                    if ($this->stock_location->save_value($location_data, $location_id)) {
                        $location_id = $this->stock_location->get_location_id($location_name);
                        $not_to_delete[] = $location_id;
                        $this->_clear_session_state();
                    }
                }
            }
        }

        // All locations not available in post will be deleted now
        $deleted_locations = $this->stock_location->get_all()->getResultArray();

        foreach ($deleted_locations as $location => $location_data) {
            if (!in_array($location_data['location_id'], $not_to_delete)) {
                $this->stock_location->delete($location_data['location_id']);
            }
        }

        $this->db->transComplete();

        $success = $this->db->transStatus();

        echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
    }

    /**
     * Saves all dinner tables. Used in app/Views/configs/table_config.php
     *
     * @throws ReflectionException
     * @return void
     * @noinspection PhpUnused
     */
    public function postSaveTables(): void
    {
        $this->db->transStart();

        $dinner_table_enable = $this->request->getPost('dinner_table_enable') != null;

        $this->appconfig->save(['dinner_table_enable' => $dinner_table_enable]);

        if ($dinner_table_enable) {
            $not_to_delete = [];
            foreach ($this->request->getPost() as $key => $value) {    // TODO: Not sure if this is the best way to filter the array
                if (strstr($key, 'dinner_table') && $key != 'dinner_table_enable') {
                    $dinner_table_id = preg_replace("/.*?_(\d+)$/", "$1", $key);
                    $not_to_delete[] = $dinner_table_id;

                    // Save or update
                    $table_data = ['name' => $value];
                    if ($this->dinner_table->save_value($table_data, $dinner_table_id)) {
                        $this->_clear_session_state();    // TODO: Remove hungarian notation.
                    }
                }
            }

            // All tables not available in post will be deleted now
            $deleted_tables = $this->dinner_table->get_all()->getResultArray();

            foreach ($deleted_tables as $dinner_tables => $table) {
                if (!in_array($table['dinner_table_id'], $not_to_delete)) {
                    $this->dinner_table->delete($table['dinner_table_id']);
                }
            }
        }

        $this->db->transComplete();

        $success = $this->db->transStatus();

        echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
    }

    /**
     * Saves tax configuration. Used in app/Views/configs/tax_config.php
     *
     * @throws ReflectionException
     * @return void
     * @noinspection PhpUnused
     */
    public function postSaveTax(): void
    {
        $default_tax_1_rate = $this->request->getPost('default_tax_1_rate');
        $default_tax_2_rate = $this->request->getPost('default_tax_2_rate');

        $batch_save_data = [
            'default_tax_1_rate'        => parse_tax(filter_var($default_tax_1_rate, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)),
            'default_tax_1_name'        => $this->request->getPost('default_tax_1_name'),
            'default_tax_2_rate'        => parse_tax(filter_var($default_tax_2_rate, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)),
            'default_tax_2_name'        => $this->request->getPost('default_tax_2_name'),
            'tax_included'              => $this->request->getPost('tax_included') != null,
            'use_destination_based_tax' => $this->request->getPost('use_destination_based_tax') != null,
            'default_tax_code'          => $this->request->getPost('default_tax_code'),
            'default_tax_category'      => $this->request->getPost('default_tax_category'),
            'default_tax_jurisdiction'  => $this->request->getPost('default_tax_jurisdiction'),
            'tax_id'                    => $this->request->getPost('tax_id', FILTER_SANITIZE_NUMBER_INT)
        ];

        $success = $this->appconfig->batch_save($batch_save_data);

        $message = lang('Config.saved_' . ($success ? '' : 'un') . 'successfully');

        echo json_encode(['success' => $success, 'message' => $message]);
    }

    /**
     * Saves customer rewards configuration. Used in app/Views/configs/reward_config.php
     *
     * @throws ReflectionException
     * @return void
     * @noinspection PhpUnused
     */
    public function postSaveRewards(): void
    {
        $this->db->transStart();

        $customer_reward_enable = $this->request->getPost('customer_reward_enable') != null;

        $this->appconfig->save(['customer_reward_enable' => $customer_reward_enable]);

        if ($customer_reward_enable) {
            $not_to_delete = [];
            $array_save = [];
            foreach ($this->request->getPost() as $key => $value) {
                if (strstr($key, 'customer_reward') && $key != 'customer_reward_enable') {
                    $customer_reward_id = preg_replace("/.*?_(\d+)$/", "$1", $key);
                    $not_to_delete[] = $customer_reward_id;
                    $array_save[$customer_reward_id]['package_name'] = $value;
                } elseif (str_contains($key, 'reward_points')) {
                    $customer_reward_id = preg_replace("/.*?_(\d+)$/", "$1", $key);
                    $array_save[$customer_reward_id]['points_percent'] = $value;
                }
            }

            if (!empty($array_save)) {
                foreach ($array_save as $key => $value) {
                    // Save or update
                    $package_data = ['package_name' => $value['package_name'], 'points_percent' => $value['points_percent']];
                    $this->customer_rewards->save_value($package_data, $key);    // TODO: reflection exception
                }
            }

            // All packages not available in post will be deleted now
            $deleted_packages = $this->customer_rewards->get_all()->getResultArray();

            foreach ($deleted_packages as $customer_rewards => $reward_category) {
                if (!in_array($reward_category['package_id'], $not_to_delete)) {
                    $this->customer_rewards->delete($reward_category['package_id']);
                }
            }
        }

        $this->db->transComplete();

        $success = $this->db->transStatus();

        echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
    }

    /**
     * Saves barcode configuration. Used in app/Views/configs/barcode_config.php
     *
     * @throws ReflectionException
     * @return void
     * @noinspection PhpUnused
     */
    public function postSaveBarcode(): void
    {
        $batch_save_data = [
            'barcode_type'              => $this->request->getPost('barcode_type'),
            'barcode_width'             => $this->request->getPost('barcode_width', FILTER_SANITIZE_NUMBER_INT),
            'barcode_height'            => $this->request->getPost('barcode_height', FILTER_SANITIZE_NUMBER_INT),
            'barcode_font'              => $this->request->getPost('barcode_font'),
            'barcode_font_size'         => $this->request->getPost('barcode_font_size', FILTER_SANITIZE_NUMBER_INT),
            'barcode_first_row'         => $this->request->getPost('barcode_first_row'),
            'barcode_second_row'        => $this->request->getPost('barcode_second_row'),
            'barcode_third_row'         => $this->request->getPost('barcode_third_row'),
            'barcode_num_in_row'        => $this->request->getPost('barcode_num_in_row', FILTER_SANITIZE_NUMBER_INT),
            'barcode_page_width'        => $this->request->getPost('barcode_page_width', FILTER_SANITIZE_NUMBER_INT),
            'barcode_page_cellspacing'  => $this->request->getPost('barcode_page_cellspacing', FILTER_SANITIZE_NUMBER_INT),
            'barcode_generate_if_empty' => $this->request->getPost('barcode_generate_if_empty') != null,
            'allow_duplicate_barcodes'  => $this->request->getPost('allow_duplicate_barcodes') != null,
            'barcode_content'           => $this->request->getPost('barcode_content'),
            'barcode_formats'           => json_encode($this->request->getPost('barcode_formats'))
        ];

        $success = $this->appconfig->batch_save($batch_save_data);

        echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
    }

    /**
     * Saves receipt configuration. Used in app/Views/configs/receipt_config.php.
     *
     * @throws ReflectionException
     * @return void
     * @noinspection PhpUnused
     */
    public function postSaveReceipt(): void
    {
        $batch_save_data = [
            'receipt_template'              => $this->request->getPost('receipt_template'),
            'receipt_font_size'             => $this->request->getPost('receipt_font_size', FILTER_SANITIZE_NUMBER_INT),
            'print_delay_autoreturn'        => $this->request->getPost('print_delay_autoreturn', FILTER_SANITIZE_NUMBER_INT),
            'email_receipt_check_behaviour' => $this->request->getPost('email_receipt_check_behaviour'),
            'print_receipt_check_behaviour' => $this->request->getPost('print_receipt_check_behaviour'),
            'receipt_show_company_name'     => $this->request->getPost('receipt_show_company_name') != null,
            'receipt_show_taxes'            => $this->request->getPost('receipt_show_taxes') != null,
            'receipt_show_tax_ind'          => $this->request->getPost('receipt_show_tax_ind') != null,
            'receipt_show_total_discount'   => $this->request->getPost('receipt_show_total_discount') != null,
            'receipt_show_description'      => $this->request->getPost('receipt_show_description') != null,
            'receipt_show_serialnumber'     => $this->request->getPost('receipt_show_serialnumber') != null,
            'print_silently'                => $this->request->getPost('print_silently') != null,
            'print_header'                  => $this->request->getPost('print_header') != null,
            'print_footer'                  => $this->request->getPost('print_footer') != null,
            'print_top_margin'              => $this->request->getPost('print_top_margin', FILTER_SANITIZE_NUMBER_INT),
            'print_left_margin'             => $this->request->getPost('print_left_margin', FILTER_SANITIZE_NUMBER_INT),
            'print_bottom_margin'           => $this->request->getPost('print_bottom_margin', FILTER_SANITIZE_NUMBER_INT),
            'print_right_margin'            => $this->request->getPost('print_right_margin', FILTER_SANITIZE_NUMBER_INT)
        ];

        $success = $this->appconfig->batch_save($batch_save_data);

        echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
    }

    /**
     * Saves invoice configuration. Used in app/Views/configs/invoice_config.php.
     *
     * @throws ReflectionException
     * @return void
     * @noinspection PhpUnused
     */
    public function postSaveInvoice(): void
    {
        $batch_save_data = [
            'invoice_enable'              => $this->request->getPost('invoice_enable') != null,
            'sales_invoice_format'        => $this->request->getPost('sales_invoice_format'),
            'sales_quote_format'          => $this->request->getPost('sales_quote_format'),
            'recv_invoice_format'         => $this->request->getPost('recv_invoice_format'),
            'invoice_default_comments'    => $this->request->getPost('invoice_default_comments'),
            'invoice_email_message'       => $this->request->getPost('invoice_email_message'),
            'line_sequence'               => $this->request->getPost('line_sequence'),
            'last_used_invoice_number'    => $this->request->getPost('last_used_invoice_number', FILTER_SANITIZE_NUMBER_INT),
            'last_used_quote_number'      => $this->request->getPost('last_used_quote_number', FILTER_SANITIZE_NUMBER_INT),
            'quote_default_comments'      => $this->request->getPost('quote_default_comments'),
            'work_order_enable'           => $this->request->getPost('work_order_enable') != null,
            'work_order_format'           => $this->request->getPost('work_order_format'),
            'last_used_work_order_number' => $this->request->getPost('last_used_work_order_number', FILTER_SANITIZE_NUMBER_INT),
            'invoice_type'                => $this->request->getPost('invoice_type')
        ];

        $success = $this->appconfig->batch_save($batch_save_data);

        // Update the register mode with the latest change so that if the user
        // switches immediately back to the register the mode reflects the change
        if ($success) {
            if ($this->config['invoice_enable']) {
                $this->sale_lib->set_mode($this->config['default_register_mode']);
            } else {
                $this->sale_lib->set_mode('sale');
            }
        }

        echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
    }

    /**
     * Removes the company logo from the database. Used in app/Views/configs/info_config.php.
     *
     * @return void
     * @throws ReflectionException
     * @noinspection PhpUnused
     */
    public function postRemoveLogo(): void
    {
        $success = $this->appconfig->save(['company_logo' => '']);

        echo json_encode(['success' => $success]);
    }
}
