<?php

declare(strict_types=1);

namespace _JchOptimizeVendor\Laminas\Stdlib\StringWrapper;

use _JchOptimizeVendor\Laminas\Stdlib\Exception;
use _JchOptimizeVendor\Laminas\Stdlib\StringUtils;

abstract class AbstractStringWrapper implements StringWrapperInterface
{
    /**
     * The character encoding working on.
     *
     * @var null|string
     */
    protected $encoding = 'UTF-8';

    /**
     * An optionally character encoding to convert to.
     *
     * @var null|string
     */
    protected $convertEncoding;

    /**
     * Check if the given character encoding is supported by this wrapper
     * and the character encoding to convert to is also supported.
     *
     * @param string      $encoding
     * @param null|string $convertEncoding
     *
     * @return bool
     */
    public static function isSupported($encoding, $convertEncoding = null)
    {
        $supportedEncodings = static::getSupportedEncodings();
        if (!\in_array(\strtoupper($encoding), $supportedEncodings)) {
            return \false;
        }
        if (null !== $convertEncoding && !\in_array(\strtoupper($convertEncoding), $supportedEncodings)) {
            return \false;
        }

        return \true;
    }

    /**
     * Set character encoding working with and convert to.
     *
     * @param string      $encoding        The character encoding to work with
     * @param null|string $convertEncoding The character encoding to convert to
     *
     * @return StringWrapperInterface
     */
    public function setEncoding($encoding, $convertEncoding = null)
    {
        $supportedEncodings = static::getSupportedEncodings();
        $encodingUpper = \strtoupper($encoding);
        if (!\in_array($encodingUpper, $supportedEncodings)) {
            throw new Exception\InvalidArgumentException('Wrapper doesn\'t support character encoding "'.$encoding.'"');
        }
        if (null !== $convertEncoding) {
            $convertEncodingUpper = \strtoupper($convertEncoding);
            if (!\in_array($convertEncodingUpper, $supportedEncodings)) {
                throw new Exception\InvalidArgumentException('Wrapper doesn\'t support character encoding "'.$convertEncoding.'"');
            }
            $this->convertEncoding = $convertEncodingUpper;
        } else {
            $this->convertEncoding = null;
        }
        $this->encoding = $encodingUpper;

        return $this;
    }

    /**
     * Get the defined character encoding to work with.
     *
     * @return null|string
     *
     * @throws Exception\LogicException if no encoding was defined
     */
    public function getEncoding()
    {
        return $this->encoding;
    }

    /**
     * Get the defined character encoding to convert to.
     *
     * @return null|string
     */
    public function getConvertEncoding()
    {
        return $this->convertEncoding;
    }

    /**
     * Convert a string from defined character encoding to the defined convert encoding.
     *
     * @param string $str
     * @param bool   $reverse
     *
     * @return false|string
     */
    public function convert($str, $reverse = \false)
    {
        $encoding = $this->getEncoding();
        $convertEncoding = $this->getConvertEncoding();
        if (null === $convertEncoding) {
            throw new Exception\LogicException('No convert encoding defined');
        }
        if ($encoding === $convertEncoding) {
            return $str;
        }
        $from = $reverse ? $convertEncoding : $encoding;
        $to = $reverse ? $encoding : $convertEncoding;

        throw new Exception\RuntimeException(\sprintf('Converting from "%s" to "%s" isn\'t supported by this string wrapper', $from ?? '', $to ?? ''));
    }

    /**
     * Wraps a string to a given number of characters.
     *
     * @param string $string
     * @param int    $width
     * @param string $break
     * @param bool   $cut
     *
     * @return false|string
     */
    public function wordWrap($string, $width = 75, $break = "\n", $cut = \false)
    {
        $string = (string) $string;
        if ('' === $string) {
            return '';
        }
        $break = (string) $break;
        if ('' === $break) {
            throw new Exception\InvalidArgumentException('Break string cannot be empty');
        }
        $width = (int) $width;
        if (0 === $width && $cut) {
            throw new Exception\InvalidArgumentException('Cannot force cut when width is zero');
        }
        if (null === $this->getEncoding() || StringUtils::isSingleByteEncoding($this->getEncoding())) {
            return \wordwrap($string, $width, $break, $cut);
        }
        $stringWidth = $this->strlen($string);
        $breakWidth = $this->strlen($break);
        $result = '';
        $lastStart = $lastSpace = 0;
        for ($current = 0; $current < $stringWidth; ++$current) {
            $char = $this->substr($string, $current, 1);
            $possibleBreak = $char;
            if (1 !== $breakWidth) {
                $possibleBreak = $this->substr($string, $current, $breakWidth);
            }
            if ($possibleBreak === $break) {
                $result .= $this->substr($string, $lastStart, $current - $lastStart + $breakWidth);
                $current += $breakWidth - 1;
                $lastStart = $lastSpace = $current + 1;

                continue;
            }
            if (' ' === $char) {
                if ($current - $lastStart >= $width) {
                    $result .= $this->substr($string, $lastStart, $current - $lastStart).$break;
                    $lastStart = $current + 1;
                }
                $lastSpace = $current;

                continue;
            }
            if ($current - $lastStart >= $width && $cut && $lastStart >= $lastSpace) {
                $result .= $this->substr($string, $lastStart, $current - $lastStart).$break;
                $lastStart = $lastSpace = $current;

                continue;
            }
            if ($current - $lastStart >= $width && $lastStart < $lastSpace) {
                $result .= $this->substr($string, $lastStart, $lastSpace - $lastStart).$break;
                $lastStart = ++$lastSpace;

                continue;
            }
        }
        if ($lastStart !== $current) {
            $result .= $this->substr($string, $lastStart, $current - $lastStart);
        }

        return $result;
    }

    /**
     * Pad a string to a certain length with another string.
     *
     * @param string $input
     * @param int    $padLength
     * @param string $padString
     * @param int    $padType
     *
     * @return string
     */
    public function strPad($input, $padLength, $padString = ' ', $padType = \STR_PAD_RIGHT)
    {
        if (null === $this->getEncoding() || StringUtils::isSingleByteEncoding($this->getEncoding())) {
            return \str_pad($input, $padLength, $padString, $padType);
        }
        $lengthOfPadding = $padLength - $this->strlen($input);
        if ($lengthOfPadding <= 0) {
            return $input;
        }
        $padStringLength = $this->strlen($padString);
        if (0 === $padStringLength) {
            return $input;
        }
        $repeatCount = (int) \floor($lengthOfPadding / $padStringLength);
        if (\STR_PAD_BOTH === $padType) {
            $repeatCountLeft = $repeatCountRight = ($repeatCount - $repeatCount % 2) / 2;
            $lastStringLength = $lengthOfPadding - 2 * $repeatCountLeft * $padStringLength;
            $lastStringLeftLength = $lastStringRightLength = (int) \floor($lastStringLength / 2);
            $lastStringRightLength += $lastStringLength % 2;
            $lastStringLeft = $this->substr($padString, 0, $lastStringLeftLength);
            $lastStringRight = $this->substr($padString, 0, $lastStringRightLength);

            return \str_repeat($padString, $repeatCountLeft).$lastStringLeft.$input.\str_repeat($padString, $repeatCountRight).$lastStringRight;
        }
        $lastString = $this->substr($padString, 0, $lengthOfPadding % $padStringLength);
        if (\STR_PAD_LEFT === $padType) {
            return \str_repeat($padString, $repeatCount).$lastString.$input;
        }

        return $input.\str_repeat($padString, $repeatCount).$lastString;
    }
}
