<?php

namespace Snaju\Inception\Util;

use stdClass;

class Util
{

    static function getPHPDocData($className)
    {
        try {
            $a = new \ReflectionClass($className);
            $doc = $a->getDocComment();

            $lines = explode("\n", $doc);

            $vars = [];
            foreach ($lines as $line) {
                if (preg_match('/\@(.*?)\s(.*)/m', $line, $m)) {
                    $vars[$m[1]] = rtrim($m[2]);
                }
            }

            return $vars;
        } catch (\ReflectionException $e) {
        }

        return [];
    }

    static function reArrayFiles(&$file_post)
    {

        $file_ary = array();
        $file_count = count($file_post['name']);
        $file_keys = array_keys($file_post);

        for ($i = 0; $i < $file_count; $i++) {
            foreach ($file_keys as $key) {
                $file_ary[$i][$key] = $file_post[$key][$i];
            }
        }

        return $file_ary;
    }

    public static function isFQDN($domain)
    {
        return preg_match('/^(?!\-)(?:(?:[a-zA-Z\d][a-zA-Z\d\-]{0,61})?[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63}$/m', $domain);
    }


    static function toLocalFS($path)
    {
        return $path;
    }

    /**
     * Converts a color hex code to its corresponding rgb value.
     *
     * @param $hex
     * @return array
     */
    static function hex2rgb($hex)
    {
        $hex = str_replace("#", "", $hex);

        if (strlen($hex) == 3) {
            $r = hexdec(substr($hex, 0, 1) . substr($hex, 0, 1));
            $g = hexdec(substr($hex, 1, 1) . substr($hex, 1, 1));
            $b = hexdec(substr($hex, 2, 1) . substr($hex, 2, 1));
        } else {
            $r = hexdec(substr($hex, 0, 2));
            $g = hexdec(substr($hex, 2, 2));
            $b = hexdec(substr($hex, 4, 2));
        }
        $rgb = array($r, $g, $b);
        //return implode(",", $rgb); // returns the rgb values separated by commas
        return $rgb; // returns an array with the rgb values
    }

    /**
     * Generates a color hex code given its corresponding color name name.
     *
     * @param $name
     * @param bool $hexSymbol
     * @return false|string|string[]
     */
    static function colorFromName($name, $hexSymbol = true)
    {

        $v = array(
            "a", "e", "i", "o", "u"
        );

        $c = array(
            "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z"
        );

        $r = 0;
        $g = 0;
        $b = 0;

        $voules = 0;
        $constants = 0;

        $v = array_flip($v);
        $c = array_flip($c);

        foreach (str_split($name) as $letter) {
            if (isset($v[strtolower($letter)])) {
                $voules++;
            } else if (isset($c[strtolower($letter)])) {
                $constants++;
            }
        }

        $size = strlen($name);

        $a1 = $voules / $size;
        $b1 = 0;
        $c1 = ($size - ($voules + 0)) / $size;

        $r = round($a1 * 255);
        $g = round($b1 * 255);
        $b = round($c1 * 255);

        $hex = self::rgb2hex(array(
            $r,
            $g,
            $b
        ));

        $hex = substr($hex, 0, 7);

        if ($hexSymbol == false) {
            $hex = str_replace("#", "", $hex);
        }

        return $hex;
    }

    /**
     * Converts a rgb color value to its corresponding color hex code.
     *
     * @param $rgb
     * @return string
     */
    static function rgb2hex($rgb)
    {
        $hex = "#";
        $hex .= str_pad(dechex($rgb[0]), 2, "0", STR_PAD_LEFT);
        $hex .= str_pad(dechex($rgb[1]), 2, "0", STR_PAD_LEFT);
        $hex .= str_pad(dechex($rgb[2]), 2, "0", STR_PAD_LEFT);

        return $hex; // returns the hex value including the number sign (#)
    }

    /**
     * Transforms a class to an array.
     *
     * @param $object
     * @return array
     */
    static function classToArray($object)
    {
        $vars = get_object_vars($object);

        foreach ($vars as $k => $v) {
            if (is_object($v)) {
                $vars[$k] = self::classToArray($v);
            }
        }


        return $vars;
    }

    /**
     * Propertly formats USD currency and returns the value as a string with or without the symbol.
     *
     * @param $bool
     * @param int $dec
     * @param false $addCurencySign
     * @param false $allowNegative
     * @return string
     * @see Util::formatUSD()
     *
     * @deprecated Changed in favor of better naming conventions and support for other currencies other than USD.
     */
    static function formatCurency($bool, $dec = 2, $addCurencySign = false, $allowNegative = false)
    {
        if ($bool < 0 && !$allowNegative) {
            $bool = 0;
        }

        $bool = number_format(round($bool, $dec), $dec);

        if ($addCurencySign) {
            $bool = "$" . $bool;
        }

        return $bool;
    }

    /**
     * Propertly formats USD currency and returns the value as a string with or without the symbol.
     *
     * @param $bool
     * @param int $dec
     * @param false $addCurencySign
     * @param false $allowNegative
     * @return string
     * @todo Add support for formatting currencies other than USD.
     *
     */
    static function formatUSD($bool, $dec = 2, $addCurencySign = false, $allowNegative = false)
    {
        if ($bool < 0 && !$allowNegative) {
            $bool = 0;
        }

        $bool = number_format(round($bool, $dec), $dec);

        if ($addCurencySign) {
            $bool = "$" . $bool;
        }

        return $bool;
    }

    /**
     * @param $doc
     * @return array
     * @todo
     *
     */
    static function parsePhpDocs($doc)
    {
        try {
            $lines = explode("\n", $doc);

            $vars = [];
            foreach ($lines as $line) {
                if (preg_match('/\@(.*?)\s(.*)/m', $line, $m)) {
                    $vars[$m[1]] = rtrim($m[2]);
                } else if (preg_match('/\@(.*)/m', $line, $m)) {
                    $vars[$m[1]] = "";
                }
            }

            return $vars;
        } catch (\ReflectionException $e) {
        }

        return [];
    }

    /**
     * Cleans an array, removing any empty values.
     *
     * @param $array
     * @return array
     */
    static function cleanArray($array)
    {
        $a = [];
        foreach ($array as $k => $value) {
            if ($value != "" && !empty($value)) {
                $a[] = $value;
            }
        }

        return $a;
    }

    /**
     * @param $filePath
     * @return string|string[]|null
     * @todo
     *
     */
    static function convertToOSPath($filePath)
    {
        return self::toLocalFS($filePath);
    }

    /**
     * @param $obj
     * @param null $default
     * @return mixed|null
     * @todo
     *
     */
    static function setIfExist(&$obj, $default = null)
    {
        if (isset($obj)) {
            return $obj;
        }

        return $default;
    }

    static function validate(...$elm)
    {
        foreach ($elm as $e) {
            if (!isset($e)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Validates a string and returns true if its decoded representation is a JSON object.
     *
     * @param $string
     * @return bool
     */
    static function isJson($string)
    {
        if (is_string($string)) {
            json_decode($string);
            return (json_last_error() == JSON_ERROR_NONE);
        }

        return false;
    }

    /**
     * Validates a string and returns true if its decoded value is a phone number.
     *
     * @param $string
     * @return false|int
     * @todo Add this method as a ValidatorRule and deprecate this method.
     *
     */
    static function isPhoneNumber($string)
    {
        return preg_match('/^(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(\d+))?$/', $string);
    }

    /**
     * Validates a string and returns true if its decoded value is an email.
     *
     * @param $string
     * @return false|int
     * @todo Add this method as a ValidatorRule and deprecate this method.
     *
     */
    static function isEmail($string)
    {
        return preg_match('/^(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))$/iD', $string);
    }

    /**
     * Accepts a string and reformats it to match the phone number format: (---) --- ----.
     *
     * @param $number
     * @param false $hidden
     * @return string
     * @todo Add this method as a ValidatorRule and depcrecate this method.
     *
     */
    static function formatPhoneNumber($number, $hidden = false)
    {
        if (preg_match('/\+(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)(\d{3})(\d{3})(\d{4})\:([0-9]*)/', $number, $m)) {
            return (!$hidden) ? "+" . $m[1] . " (" . $m[2] . ") " . $m[3] . " " . $m[4] . " Ex:" . $m[5] : "*** *** " . $m[4];
        } else if (preg_match('/(\d{3})(\d{3})(\d{4})\:([0-9]*)/', $number, $m)) {
            return (!$hidden) ? "+1 (" . $m[1] . ") " . $m[2] . " " . $m[3] . " Ex:" . $m[4] : "*** *** " . $m[3];
        } else if (preg_match('/\+(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)(\d{3})(\d{3})(\d{4})/', $number, $m)) {
            return (!$hidden) ? "+" . $m[1] . " (" . $m[2] . ") " . $m[3] . " " . $m[4] : "*** *** " . $m[4];
        } else if (preg_match('/(\d{3})(\d{3})(\d{4})/', $number, $m)) {
            return (!$hidden) ? "+1 (" . $m[1] . ") " . $m[2] . " " . $m[3] : "*** *** " . $m[3];
        } else {
            return "(---) --- ----";
        }
    }

    static function parsePhoneNumber($number)
    {
        $phoneNumber = Util::stringToNumber($number);
        if (strpos($number, ":") !== false) {
            $num = explode(":", $number);
            $phoneNumber = Util::stringToNumber($num[0]) . ":" . $num[1];
        }

        return "+" . $phoneNumber;
    }

    static function stringToNumber($string)
    {
        if ($string == "") {
            $string = 0;
        }
        $negative = false;
        if (strpos($string, "-") !== false) {
            $negative = true;
        }
        $string = preg_replace("/[^0-9\\.]+/", "", $string);
        if ($negative) {
            $string = $string * -1;
        }
        return $string;
    }

    public
    static function formatRelativeTime($ts)
    {
        if (!is_numeric($ts) && $ts != null && !empty($ts)) {
            $ts = strtotime($ts);
        } else if ($ts == null || $ts == 0 || empty($ts)) {
            return "never";
        }

        $diff = time() - $ts;
        if ($diff == 0) {
            return 'now';
        } elseif ($diff > 0) {
            $day_diff = floor($diff / 86400);
            if ($day_diff == 0) {
                if ($diff < 60) return 'just now';
                if ($diff < 120) return '1 minute ago';
                if ($diff < 3600) return floor($diff / 60) . ' minutes ago';
                if ($diff < 7200) return '1 hour ago';
                if ($diff < 86400) return floor($diff / 3600) . ' hours ago';
            }
            if ($day_diff == 1) {
                return 'Yesterday';
            }
            if ($day_diff < 7) {
                return $day_diff . ' days ago';
            }
            if ($day_diff < 31) {
                return ceil($day_diff / 7) . ' weeks ago';
            }
            if ($day_diff < 60) {
                return 'last month';
            }
            return date('F Y', $ts);
        } else {
            $diff = abs($diff);
            $day_diff = floor($diff / 86400);
            if ($day_diff == 0) {
                if ($diff < 120) {
                    return 'in a minute';
                }
                if ($diff < 3600) {
                    return 'in ' . floor($diff / 60) . ' minutes';
                }
                if ($diff < 7200) {
                    return 'in an hour';
                }
                if ($diff < 86400) {
                    return 'in ' . floor($diff / 3600) . ' hours';
                }
            }
            if ($day_diff == 1) {
                return 'Tomorrow';
            }
            if ($day_diff < 4) {
                return date('l', $ts);
            }
            if ($day_diff < 7 + (7 - date('w'))) {
                return 'next week';
            }
            if (ceil($day_diff / 7) < 4) {
                return 'in ' . ceil($day_diff / 7) . ' weeks';
            }
            if (date('n', $ts) == date('n') + 1) {
                return 'next month';
            }
            return date('F Y', $ts);
        }
    }

    static function returnFinicialStatment($number, $currencySign = "$")
    {
        $abs = abs($number);
        if ($number < 0) {
            $s = "($currencySign" . Util::formatCurency($abs) . ")";
        } else if ($number > 0) {
            $s = "$currencySign" . Util::formatCurency($abs) . "";
        } else {
            $s = " - ";
        }

        return $s;
    }

    static function isBase64($string)
    {
        return base64_decode($string, true);
    }

    static function requireAndGetClassName($filePath)
    {
        $before = get_declared_classes();
        require $filePath;
        $after = get_declared_classes();

        $diff = array_diff($after, $before);

        return $diff;
    }

    static function getFirstKey($array)
    {
        foreach ($array as $k => $v) {
            return $k;
        }

        return null;
    }

    /**
     * Converts a given string from camelCase to snake_case.
     *
     * @param $string
     * @return string
     */
    static function fromCamelToSnakeCase($string)
    {
        return strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $string));
    }

    static function arrayToObject(array $array): object
    {
        $obj = new stdClass;

        foreach ($array as $k => $v) {
            if (strlen($k)) {
                if (is_array($v)) {
                    $obj->{$k} = self::arrayToObject($v); //RECURSION
                } else {
                    $obj->{$k} = $v;
                }
            }
        }

        return $obj;
    }

}