// SPDX-License-Identifier: Unlicence

// memory reserved
#define constant DEBUG_MSLOT = 0x220


/*
 logs a small string (must fit in a single 256 bits word)
 usage example to log "hello":
 1) get encoding via: logStr("hello")))  where:

            function logStr(string memory str) public view {
                console.log(iToHex(abi.encodePacked(str), 32));
                console.log(iToHex(abi.encodePacked(bytes(str).length)));
            }
            function iToHex(bytes memory buffer) public pure returns (string memory) {
                return iToHex(buffer, 0);
            }
            function iToHex(bytes memory buffer, uint256 padTo) public pure returns (string memory) {
                uint256 len = buffer.length > padTo ? buffer.length : padTo;
                bytes memory converted = new bytes(len* 2);
                bytes memory _base = "0123456789abcdef";
                for (uint256 i = 0; i < buffer.length; i++) {
                    converted[i * 2] = _base[uint8(buffer[i]) / _base.length];
                    converted[i * 2 + 1] = _base[uint8(buffer[i]) % _base.length];
                }
                for (uint256 i = buffer.length * 2; i < padTo * 2; i++) {
                    converted[i] = '0';
                }
                return string(abi.encodePacked("0x", converted));
            }

 2) push encoded logged values, and call macro
     0x68656c6c6f000000000000000000000000000000000000000000000000000000
     0x5
     CONSOLE_LOG()
*/
#define macro CONSOLE_LOG() = takes(2) returns (0) {
    // see implementation of foundry console.log(string)
    //  at https://github.com/foundry-rs/foundry/blob/master/testdata/logs/console.sol
    // ________________________________________________________________
    // Ex: console.log(iToHex(abi.encodeWithSignature("log(string)", "hello")));
    // generates:
    //   41304fac  signature (32 bits)
    //   0000000000000000000000000000000000000000000000000000000000000020 string offset location
    //   0000000000000000000000000000000000000000000000000000000000000005 string len
    //   68656c6c6f000000000000000000000000000000000000000000000000000000 string data

   /*
        FYI, to replicate this in solidity/yul...
        - The easy version:
                bytes memory payload = abi.encodeWithSignature("log(string)", "hello");
                // or: bytes memory payload = hex'41304fac0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000568656c6c6f000000000000000000000000000000000000000000000000000000';
                uint256 payloadLength = payload.length;
                address consoleAddress = address(0x000000000000000000636F6e736F6c652e6c6f67);
                /// @solidity memory-safe-assembly
                assembly {
                    let payloadStart := add(payload, 32)
                    let r := staticcall(gas(), consoleAddress, payloadStart, payloadLength, 0, 0)
                }


        - Or the full assembly version:
                assembly {
                    let loc:=0xa0
                    mstore(loc, 0x41304fac00000000000000000000000000000000000000000000000000000000)
                    mstore(add(loc, 4), 0x20) // header
                    mstore(add(add(loc, 4), 0x20), 0x5) // string len
                    mstore(add(add(loc, 4), 0x40), 0x68656c6c6f000000000000000000000000000000000000000000000000000000)
                    let r := staticcall(gas(), 0x000000000000000000636F6e736F6c652e6c6f67, loc, 100, 0, 0)
                }
    */

    //  => We're gonna replicate this behaviour here:

    // 1) Copy args to memory
    //    - store the "log" signature
    0x41304fac00000000000000000000000000000000000000000000000000000000
    [DEBUG_MSLOT]
    mstore
    //    - store the "0x20" offset
    0x20
    [DEBUG_MSLOT]
    0x4 add // 4 is the signature size
    mstore
    //    - store string length at 0x20
    // length is already pushed by caller on the stack... ex: 0x5
    [DEBUG_MSLOT]
    0x20 add
    0x4 add
    mstore
    //    - store data at 0x40
    // data is already pushed by caller on the stack... ex: 0x68656c6c6f000000000000000000000000000000000000000000000000000000
    [DEBUG_MSLOT]
    0x40 add
    0x4 add
    mstore

    // 2) Perform the call
    0x0
    0x0
    0x100 // payload size
    [DEBUG_MSLOT] // payload start
    0x000000000000000000636F6e736F6c652e6c6f67 // console address
    gas
    staticcall
    pop // pop the "success" flag
}

/// @dev Logs the top stack element
#define macro CONSOLE_LOG_UINT() = takes(1) returns (1) {
    // see implementation of foundry console.log(uint)
    //  at https://github.com/foundry-rs/foundry/blob/master/testdata/logs/console.sol
    // ________________________________________________________________
    // Ex: console.log(iToHex(abi.encodeWithSignature("log(uint)", 42)));
    // generates:
    //   f5b1bba9   signature (32 bits)
    //   000000000000000000000000000000000000000000000000000000000000002a value

    //  => We're gonna replicate this behaviour here:

    dup1 // replicate the top stack => [val]

    // 1) Copy args to memory
    //    - store the "log" signature
    0xf5b1bba900000000000000000000000000000000000000000000000000000000
    [DEBUG_MSLOT]
    mstore
    //    - store the value
    // value is already pushed by caller on the stack: ex: 0x2a
    [DEBUG_MSLOT] 0x4 add // 4 is the signature size
    mstore // => []

    // 2) Perform the call
    0x0
    0x0
    0x24 // payload size
    [DEBUG_MSLOT] // payload start
    0x000000000000000000636F6e736F6c652e6c6f67 // console address
    gas
    staticcall
    pop // pop the "success" flag
}

/// @dev Push the function name which is not implemented, then call this.
///      (see console_log())
#define macro CONSOLE_NOTIMPLEMENTED_FN() = takes(2) returns (0) {
    // console.log('not implemented:')
    0x6e6f7420696d706c656d656e7465643a00000000000000000000000000000000
    0x10
    CONSOLE_LOG()
    CONSOLE_LOG()
}

#define macro CONSOLE_LOG_NUM() = takes(1) returns (0) {
    0x0 // => [i, x]
    swap1 // => [x, i]
    whilenotzero:
        dup1 // => [x, x, i]
        iszero // => [is zero, x, i]
        endlognum jumpi

        // compute modulo 16
        dup1 // => [x, x, i]
        0x10 // => [16, x, x, i]
        swap1 // => [x, 16, x, i]
        mod // => [dec, x, i]

        // substract to x
        dup1 // => [dec, dec, x, i]
        swap2 // => [x, dec, dec, i]
        swap1 // => [dec, x, dec, i]
        sub // => [new x, dec, i]
        swap1// => [i, new x, dec]

        // jump to "isaf" if is a num a to f
        dup1 // => [dec, dec, x, i]
        0xA gt // => [is A-F, dec, x, i]
        isaf jumpi // => [dec, x, i]

        // push char 0 to 9 (0x30=48='0')
        0x30 add //  => [char 0-9, x, i]
        after jump


        // push char 0 to F (0x41=65='A')
        isaf:
        0x41 add //  => [char 0-F, x, i]


        // append to string
        after:
        dup3  //  => [i,  char 0-F, x, i]
        shl  //  => [char 0-F shifted, x, i]
        [DEBUG_MSLOT] mload  //  => [str, char 0-F shifted, x, i]
        or  //  => [new str, x, i]
        [DEBUG_MSLOT] mstore//  => [x, i]

        // increment counter
        swap1
        0x1 add
        swap1

        // loop
        whilenotzero jump

    endlognum: // we're at [x, i]
        swap1 // =>  [i, x]
        swap1 // => [x, i]
        dup1 // => [i, i, x]
        swap2 // => [i, x, i]
        0x20 sub // => [32-i, x, i]
        shl // => [str, i]
        swap1 // => [i, str]

        CONSOLE_LOG()
}

#define macro CONSOLE_LOG_HELLO() = takes(0) returns (0) {
    // console.log('hello')
    0x68656c6c6f000000000000000000000000000000000000000000000000000000
    0x5
    CONSOLE_LOG()
}