// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {Test} from "forge-std/Test.sol";
import {SymTest} from "halmos-cheatcodes/SymTest.sol";

import {ShortStrings, ShortString} from "@openzeppelin/contracts/utils/ShortStrings.sol";

contract ShortStringsTest is Test, SymTest {
    string _fallback;

    function testRoundtripShort(string memory input) external pure {
        vm.assume(_isShort(input));
        _assertRoundtripShort(input);
    }

    function symbolicRoundtripShort() external pure {
        string memory input = svm.createString(31, "RoundtripShortInput");
        _assertRoundtripShort(input);
    }

    function testRoundtripWithFallback(string memory input, string memory fallbackInitial) external {
        _assertRoundtripWithFallback(input, fallbackInitial);
    }

    function symbolicRoundtripWithFallbackLong() external {
        string memory input = svm.createString(256, "RoundtripWithFallbackInput");
        string memory fallbackInitial = svm.createString(256, "RoundtripWithFallbackFallbackInitial");
        _assertRoundtripWithFallback(input, fallbackInitial);
    }

    function symbolicRoundtripWithFallbackShort() external {
        string memory input = svm.createString(31, "RoundtripWithFallbackInput");
        string memory fallbackInitial = svm.createString(31, "RoundtripWithFallbackFallbackInitial");
        _assertRoundtripWithFallback(input, fallbackInitial);
    }

    function testRevertLong(string memory input) external {
        vm.assume(!_isShort(input));
        _assertRevertLong(input);
    }

    function testLengthShort(string memory input) external pure {
        vm.assume(_isShort(input));
        _assertLengthShort(input);
    }

    function symbolicLengthShort() external pure {
        string memory input = svm.createString(31, "LengthShortInput");
        _assertLengthShort(input);
    }

    function testLengthWithFallback(string memory input, string memory fallbackInitial) external {
        _fallback = fallbackInitial;
        _assertLengthWithFallback(input);
    }

    function symbolicLengthWithFallback() external {
        uint256 length = 256;
        string memory input = svm.createString(length, "LengthWithFallbackInput");
        string memory fallbackInitial = svm.createString(length, "LengthWithFallbackFallbackInitial");
        _fallback = fallbackInitial;
        _assertLengthWithFallback(input);
    }

    /// Assertions

    function _assertRoundtripShort(string memory input) internal pure {
        ShortString short = ShortStrings.toShortString(input);
        string memory output = ShortStrings.toString(short);
        assertEq(input, output);
    }

    function _assertRoundtripWithFallback(string memory input, string memory fallbackInitial) internal {
        _fallback = fallbackInitial; // Make sure that the initial value has no effect
        ShortString short = ShortStrings.toShortStringWithFallback(input, _fallback);
        string memory output = ShortStrings.toStringWithFallback(short, _fallback);
        assertEq(input, output);
    }

    function _assertRevertLong(string memory input) internal {
        vm.expectRevert(abi.encodeWithSelector(ShortStrings.StringTooLong.selector, input));
        this.toShortString(input);
    }

    function _assertLengthShort(string memory input) internal pure {
        ShortString short = ShortStrings.toShortString(input);
        uint256 shortLength = ShortStrings.byteLength(short);
        uint256 inputLength = bytes(input).length;
        assertEq(inputLength, shortLength);
    }

    function _assertLengthWithFallback(string memory input) internal {
        uint256 inputLength = bytes(input).length;
        ShortString short = ShortStrings.toShortStringWithFallback(input, _fallback);
        uint256 shortLength = ShortStrings.byteLengthWithFallback(short, _fallback);
        assertEq(inputLength, shortLength);
    }

    /// Helpers
    function toShortString(string memory input) external pure returns (ShortString) {
        return ShortStrings.toShortString(input);
    }

    function _isShort(string memory input) internal pure returns (bool) {
        return bytes(input).length < 32;
    }
}
