<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 1/10/16
 * Time: 11:47 AM
 */

namespace IMATHUZH\Qfq\Core\Helper;


const SUBSTITUTE = '#%SUB%#';

/**
 * Class OnArray
 * @package qfq
 */
class OnArray {

    /**
     * Builds a string from an assoc array.
     *
     * key/value are combined with $keyValueGlue.
     * values are enclosed by $encloseValue.
     * rows combined with $rowGlue
     *
     * @param array $dataArray
     * @param string $keyValueGlue
     * @param string $rowGlue
     * @param string $encloseValue - char (or string) to enclose the value with.
     *
     * @return string
     */
    public static function toString(array $dataArray, $keyValueGlue = '=', $rowGlue = '&', $encloseValue = '') {

        if (count($dataArray) === 0) {
            return '';
        }

        $dataString = '';

        foreach ($dataArray as $key => $value) {
            if (is_array($value)) {
                $value = self::toString($value);
            }

            $dataString .= $key . $keyValueGlue . $encloseValue . $value . $encloseValue . $rowGlue;
        }

        $glueLength = strlen($rowGlue);

        return substr($dataString, 0, strlen($dataString) - $glueLength);
    }

    /**
     * Sort array by array keys
     *
     * @param array $a
     */
    public static function sortKey(array &$a) {

        $result = array();

        $keys = array_keys($a);
        sort($keys);
        foreach ($keys as $key) {
            $result[$key] = $a[$key];
        }
        $a = $result;

        return;
    }

    /**
     * Trim all elements in an array.
     * The array has to be a 1-dimensional array.
     *
     * @param array $arr
     * @param string $character_mask
     *
     * @return array
     */
    public static function trimArray(array $arr, $character_mask = " \t\n\r\0\x0B") {
        foreach ($arr as $key => $item) {
            $arr[$key] = trim($item, $character_mask);
        }

        return $arr;
    }

    /**
     * Iterates over all records and return those with $row[$column]==$value
     *
     * @param array $dataArray
     * @param       $column
     * @param       $value
     *
     * @return array
     */
    public static function filter(array $dataArray, $column, $value) {
        $result = array();

        foreach ($dataArray as $row) {
            if (isset($row[$column]) && $row[$column] == $value) {
                $result[] = $row;
            }
        }

        return $result;
    }

    /**
     * Iterates over all elements and return those with $needle in $row
     *
     * @param array $dataArray
     * @param $needle
     * @return array
     */
    public static function filterValueSubstring(array $dataArray, $needle) {
        $result = array();

        foreach ($dataArray as $row) {
            if (strpos($row, $needle) !== false) {
                $result[] = $row;
            }
        }

        return $result;
    }


    /**
     * Converts a one-dimensional array by using html entities on all elements.
     *
     * @param array $arr
     *
     * @return array
     */
    public static function htmlentitiesOnArray(array $arr) {
        foreach ($arr as $key => $value) {

            // Error message stays the same
            if ($key !== EXCEPTION_MESSAGE) $arr[$key] = htmlentities($arr[$key], ENT_QUOTES);
        }

        return $arr;
    }

    /**
     * Iterates over an key/value array. Each value will be 'var_export'.
     *
     * @param array $arr
     * @return array
     */
    public static function varExportArray(array $arr) {
        foreach ($arr as $key => $value) {
            if (is_array($value)) {
                $arr[$key] = empty($value) ? 'array ()' : var_export($value, true);
            }
        }

        return $arr;
    }

    /**
     * @param array $arr
     * @param string $title
     * @param string $class
     *
     * @return string
     */
    public static function arrayToHtmlTable(array $arr, $title, $class) {

        $html = '';

        foreach ($arr as $key => $value) {
            $html .= '<tr><td>' . $key . '</td><td>' . $value . '</td></tr>';
        }

        return "<table class='$class'><thead><tr><th colspan='2'>$title</th></tr></thead>$html</table>";
    }

    /**
     * @param array $arr
     * @return string
     */
    public static function arrayToLog(array $arr) {

        $output = EXCEPTION_UNIQID . ':: ';
        $output .= (empty($arr[EXCEPTION_UNIQID])) ? ' - ' : $arr[EXCEPTION_UNIQID];
        $output .= PHP_EOL . '------------------------------------------------' . PHP_EOL;

        foreach ($arr as $key => $value) {
            if (!empty($value) && $key != EXCEPTION_UNIQID) {
                $output .= $key . ':: ' . OnArray::implodeR($value) . PHP_EOL;
            }
        }

        $output .= '==================================================' . PHP_EOL;

        return $output;
    }

    /**
     * Split Array around $str to $arr around $delimiter. Escaped $delimiter will be preserved.
     *
     * @param string $delimiter
     * @param string $str
     *
     * @return array
     * @throws \UserReportException
     */
    public static function explodeWithoutEscaped($delimiter, $str) {

        if (strpos($str, SUBSTITUTE) !== false) {
            throw new \UserReportException ("Can't replace token by SUBSTITUTE, cause SUBSTITUTE already exist", ERROR_SUBSTITUTE_FOUND);
        }

        $encodedStr = str_replace('\\' . $delimiter, SUBSTITUTE, $str);

        $arr = explode($delimiter, $encodedStr);

        for ($ii = 0; $ii < count($arr); $ii++) {
//            $arr[$ii] = str_replace(SUBSTITUTE, '\\' . $delimiter, $arr[$ii]);
            $arr[$ii] = str_replace(SUBSTITUTE, $delimiter, $arr[$ii]);
        }

        return $arr;
    }

    /**
     * Iterates over $arr and removes all empty (='') elements. Preserves keys
     *
     * @param array $arr
     *
     * @return array
     */
    public static function removeEmptyElementsFromArray(array $arr) {

        $new = array();

//        for($ii=0; $ii<count($arr); $ii++) {
//            if($arr[$ii]!='') {
//                $new[]=$arr[$ii];
//            }
//        }

        $ii = 0;
        foreach ($arr as $key => $value) {
            if ($value != '') {
                if (is_int($key)) {
                    $key = $ii;
                    $ii++;
                }
                $new[$key] = $value;
            }
        }

        return $new;
    }

    /**
     * Convert all values of an array to lowercase.
     *
     * @param array $arr
     *
     * @return array
     */
    public static function arrayValueToLower(array $arr) {
        $new = array();

        foreach ($arr as $key => $value) {
            $new[$key] = strtolower($value);
        }

        return $new;
    }

    /**
     * Remove from all keynames an optional '_'.
     * If $multiArray == true:
     *
     * @param array $arr
     * @param bool $multiArray
     * @return array
     */
    public static function keyNameRemoveLeadingUnderscore(array $arr): array {
        foreach ($arr as $key => $value) {
            if (($key[0] ?? '') == TOKEN_COLUMN_CTRL) {
                $newKey = substr($key, 1);
                if (!empty($newKey)) {
                    $arr[$newKey] = $value;
                    unset($arr[$key]);
                }
            }
        }

        return $arr;
    }

    /**
     * Search in array $dest for all $keyNames if they exist. If not, check if they exist in $src. If yes, copy.
     *
     * @param array $src
     * @param array $dest
     * @param array $keyNames
     *
     * @return array $dest filled with new values
     */
    public static function copyArrayItemsIfNotAlreadyExist(array $src, array $dest, array $keyNames) {

        foreach ($keyNames as $key) {

            if (!isset($dest[$key])) {
                if (isset($src[$key])) {
                    $dest[$key] = $src[$key];
                }
            }
        }

        return $dest;
    }

    /**
     * Copies all items whose $keyNames are listed to $new and return $new
     *
     * @param array $src
     * @param array $keyNames
     * @param bool $createMissing
     *
     * @param bool $skipZero
     * @param bool $skipEmpty
     * @return array
     */
    public static function getArrayItems(array $src, array $keyNames, $createMissing = false, $skipZero = false, $skipEmpty = false) {
        $new = array();

        // Extract necessary elements
        foreach ($keyNames as $key) {
            if (isset($src[$key])) {

                if (($src[$key] === '' && $skipEmpty) || ($src[$key] == '0' && $skipZero)) {
                    $copy = false;
                } else {
                    $new[$key] = $src[$key];
                }

            } elseif ($createMissing == true) {
                $new[$key] = '';
            }
        }

        return $new;
    }

    /**
     * Copies all items whose keyNames starts with $keyName.
     * Remove Prefix '$keyName' from all keys.
     * Return $new
     *
     * E.g. [ 'mode' => 'pdf', '1_pageId' => 123, '2_file' => 'example.pdf' ], with $keyName='1_' >> [ 'pageId' => 123 ]
     *
     * @param array $src
     * @param string $keyName
     *
     * @return array
     */
    public static function getArrayItemKeyNameStartWith(array $src, $keyName) {
        $new = array();
        $length = strlen($keyName);

        // Extract necessary elements
        foreach ($src as $key => $value) {
            $keyTmp = substr($key, 0, $length);
            if ($keyTmp == $keyName) {
                $newKey = substr($key, $length);
                $new[$newKey] = $value;
            }
        }

        return $new;
    }

    /**
     * Iterates over an array and replaces $search with $replace in all elements. Returns the new array.
     *
     * @param array $src
     * @param       $search
     * @param       $replace
     *
     * @return array
     */
    public static function arrayValueReplace(array $src, $search, $replace) {
        $new = array();

        foreach ($src as $key => $element) {
            $new[$key] = str_replace($search, $replace, $element);
        }

        return $new;
    }

    /**
     * Performs escapeshellarg() on all elements of an array.
     *
     * @param array $src
     *
     * @return array
     */
    public static function arrayEscapeshellarg(array $src) {
        $new = array();

        foreach ($src as $key => $value) {
            if (is_array($value)) {
                $new[$key] = self::arrayEscapeshellarg($value);
            } else {
                $new[$key] = escapeshellarg($value);
            }
        }

        return $new;
    }

    /**
     * @param array $data
     *
     * @return string md5 of implode $data
     */
    public static function getMd5(array $data) {
        return md5(implode($data));
    }

    /**
     * Converts a one dimensional array to JSON array. The 'key' and 'value' will names are hardcoded:
     *
     * Return: [ { 'key': $key[0], 'value': $value[0] }, { 'key': $key[1], 'value': $value[2] }, ... ]
     *
     * @param array $arr
     * @param bool $flagHtmlEntity true|false
     * @return string
     */
    public static function arrayToQfqJson(array $arr, $flagHtmlEntity = false) {

        $json = '';
        foreach ($arr as $arrKey => $arrValue) {
            if ($flagHtmlEntity) {
                $arrKey = htmlentities($arrKey, ENT_QUOTES);
                $arrValue = htmlentities($arrValue, ENT_QUOTES);
            }
            $json .= ',' . json_encode(["key" => $arrKey, "value" => $arrValue]);
        }

        return '[' . substr($json, 1) . ']';
    }

    /**
     * return subset of keys in $keys which are not set in $array.
     *
     * @param array $keys
     * @param array $array
     * @return array
     */
    public static function keysNotSet(array $keys, array $array) {
        return array_filter($keys, function ($key) use ($array) {
            return !isset($array[$key]);
        });
    }

    /**
     * Rename keys according to the associative array given as first argument (only if the key exists).
     * Return changed array.
     *
     * @param array $renameMap old-key => new-key
     * @param array $array
     * @param bool $overwrite overwrite value from existing new-key with value of old-key
     * @return array
     */
    public static function renameKeys(array $renameMap, array $array, bool $overwrite = false) {
        foreach ($renameMap as $keyOld => $keyNew) {
            if (isset($array[$keyOld]) && (!isset($array[$keyNew]) || $overwrite)) {
                $array[$keyNew] = $array[$keyOld];
                unset($array[$keyOld]);
            }
        }
        return $array;
    }

    /**
     * Searches for $needle in $arr via strpos. If found, return the whole found string. Else FALSE
     *
     * @param array $arr
     * @param string $needle
     * @return false|mixed
     */
    public static function strposArr(array $arr, string $needle) {

        foreach ($arr as $val) {
            if (false !== (strpos($val, $needle))) {
                return ($val);
            }
        }
        return false;
    }

    /**
     * Return first value from given array
     *
     * @param $arr
     * @return string
     */
    public static function getFirstValueFromArray($arr, $reverse = false): string {

        if (!is_array($arr)) {
            return $arr;
        }

        if (self::is_multi_array($arr)) {
            $arr = array_pop($arr);
        }

        if ($reverse) {
            $arr = array_reverse($arr);
        }

        $values = array_values($arr);
        if (count($values) > 1) {
            $value = $values[1];
        } else if (!empty($values[0])) {
            $value = $values[0];
        }

        if (empty($value)) {
            $value = '';
        }

        return $value;
    }

    /**
     * Check if array is multidimensional
     *
     * @param $arr
     * @return bool
     */
    public static function is_multi_array($arr): bool {
        rsort($arr);
        return isset($arr[0]) && is_array($arr[0]);
    }

    /**
     * Set value in one- or multidimensional array
     *
     * @param $arr
     * @param $value
     */
    public static function setFirstValueInArray(&$arr, $key, $value): void {
        if (self::is_multi_array($arr)) {
            foreach ($arr as &$element) {
                if (isset($element[$key])) {
                    $element[$key] = $value;
                    break;
                }
            }
        } else if (isset($arr[$key])) {
            $arr[$key] = $value;
        }
    }


    /** Map columnNames from given string to existing array dbColumns
     * Example string: [newColumnName1:key1,newColumnName2:key2,...]
     * Example given array: $dbColumns['key1' => 'currentColumnName1', 'key2' => 'currentColumnName2', ...]
     * Example result: $dbColumns['key1' => 'newColumnName1', 'key2' => 'newColumnName2', ...]
     * @param $dbColumnNames
     * @param $mappingString
     * @return void
     */
    public static function mapColumns(&$dbColumnNames, $mappingString): void {
        // Remove the square brackets from the string
        $mappingString = trim($mappingString, "[]");

        if (empty($mappingString)) {
            return;
        }
        // Split the string into individual mappings
        $mappings = explode(',', $mappingString);

        // Process each mapping
        foreach ($mappings as $mapping) {
            // Split each mapping into its key and value
            list($value, $key) = explode(':', $mapping);

            // Check if the old key exists in the $dbColumnNames array
            if (array_key_exists($key, $dbColumnNames)) {
                // Map the new key to the same value as the old key
                $dbColumnNames[$key] = $value;

            }
        }
    }

    /** Reorder an array based on a reference array
     * If a key from the reference array is not present in the array to reorder, a placeholder will be used
     * @param $referenceArray
     * @param $arrayToReorder
     * @return array
     */
    public static function reorderArray($referenceArray, $arrayToReorder, $clientCompatibility = false): array {
        $result = [];
        $placeholder = '';

        // Process all keys from the reference array
        foreach ($referenceArray as $key => $value) {
            if (array_key_exists($key, $arrayToReorder)) {
                if ($clientCompatibility) {
                    $result[] = [$key, $arrayToReorder[$key]];
                } else {
                    $result[$key] = $arrayToReorder[$key];
                }
            } else {
                if ($clientCompatibility) {
                    $result[] = [$key, $placeholder];
                } else {
                    $result[$key] = $placeholder;
                }
            }
        }

        return $result;
    }

    /**
     * Iterate through all elementes of an array and implode them. If an element is an array, implode those too (recursive).
     *
     * @param $array
     * @return string
     */
    public static function implodeR($array): string {

        if (!is_array($array)) {
            return $array;
        }

        $result = '';
        foreach ($array as $item) {
            if (is_array($item)) {
                $item = self::implodeR($item);
            }
            if ($result != '' && $item != '') {
                $result .= ',' . $item;
            } else {
                $result .= $item;
            }
        }
        return $result;
    }

    /**
     * Explode a string '4000x5000' into [ 4000, 5000 ].
     * Valid: '4000x5000' | ' 4000 x 5000 '.
     * Invalid: '4000' | '4000x' | 'x4000' | '1x2x3'
     *
     * @param string $str
     * @return array|null
     * @throws \UserReportException
     */
    public static function explodeDimension(string $str): ?array {
        if ($str === '') {
            return null;
        }

        $arr = explode('x', $str);
        if (count($arr) == 2) {
            $arr[0] = trim($arr[0]);
            $arr[1] = trim($arr[1]);
            if (ctype_digit($arr[0]) && ctype_digit($arr[1])) {
                return $arr;
            }
        }
        throw new \UserReportException ("Dimension has to be numerical like '1000x1000': ". $str, ERROR_UNEXPECTED_TYPE);
    }
}