// File: src/code/v2/HtmlScriptCode.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

import "@openzeppelin/contracts/access/Ownable.sol";
import "base64-sol/base64.sol";
import "../ICode.sol";
import "./HtmlEthFsCode.sol";
import "./EthFsLibDef.sol";
import "./HtmlCode.sol";

contract HtmlScriptCode is ICode, Ownable{

    ICode[] public subCodes;
    EthFsLibDef[] public ethFsLibs;
    HtmlEthFsCode public ethFsCode;
    string public gunzipScript = "gunzipScripts-0.0.1.js";

    function getCodeEncoded(string calldata params) external view returns(string memory) {
        return string.concat('data:text/html;base64,', Base64.encode(abi.encodePacked(getCode(params))));
    }

    function getCode(string calldata params) public view override returns(string memory) {
        string memory s;

        for (uint8 i = 0; i < subCodes.length; i++) {
            s = string.concat(s, "<script>", ICode(subCodes[i]).getCode(params), "</script>");
        }

        for (uint8 i = 0; i < ethFsLibs.length; i++) {
            s = string.concat(s, ethFsCode.fetch(ethFsLibs[i].scriptName, true, ethFsLibs[i].isBase64Encoded));
        }

        return string.concat(s, ethFsCode.fetch(gunzipScript, false, true));

    }

    function setEthFsAddr(address addr) external virtual onlyOwner {
        ethFsCode = HtmlEthFsCode(addr);
    }

    function addSubCode(address addr) external virtual onlyOwner {
        subCodes.push(ICode(addr));
    }

    function clearSubCodes() external virtual onlyOwner {
        delete subCodes;
    }

    function addEthFsLib(string calldata scriptName, bool isBase64Encoded) external virtual onlyOwner {
        ethFsLibs.push(EthFsLibDef(scriptName, isBase64Encoded));
    }

    function clearEthFsLibs() external virtual onlyOwner {
        delete ethFsLibs;
    }

    function setGunzipScript(string calldata scriptName) external virtual onlyOwner {
        gunzipScript = scriptName;
    }

}

// File: lib/openzeppelin-contracts/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: lib/base64/base64.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0;

/// @title Base64
/// @author Brecht Devos - <brecht@loopring.org>
/// @notice Provides functions for encoding/decoding base64
library Base64 {
    string internal constant TABLE_ENCODE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    bytes  internal constant TABLE_DECODE = hex"0000000000000000000000000000000000000000000000000000000000000000"
                                            hex"00000000000000000000003e0000003f3435363738393a3b3c3d000000000000"
                                            hex"00000102030405060708090a0b0c0d0e0f101112131415161718190000000000"
                                            hex"001a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132330000000000";

    function encode(bytes memory data) internal pure returns (string memory) {
        if (data.length == 0) return '';

        // load the table into memory
        string memory table = TABLE_ENCODE;

        // multiply by 4/3 rounded up
        uint256 encodedLen = 4 * ((data.length + 2) / 3);

        // add some extra buffer at the end required for the writing
        string memory result = new string(encodedLen + 32);

        assembly {
            // set the actual output length
            mstore(result, encodedLen)

            // prepare the lookup table
            let tablePtr := add(table, 1)

            // input ptr
            let dataPtr := data
            let endPtr := add(dataPtr, mload(data))

            // result ptr, jump over length
            let resultPtr := add(result, 32)

            // run over the input, 3 bytes at a time
            for {} lt(dataPtr, endPtr) {}
            {
                // read 3 bytes
                dataPtr := add(dataPtr, 3)
                let input := mload(dataPtr)

                // write 4 characters
                mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
                resultPtr := add(resultPtr, 1)
                mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
                resultPtr := add(resultPtr, 1)
                mstore8(resultPtr, mload(add(tablePtr, and(shr( 6, input), 0x3F))))
                resultPtr := add(resultPtr, 1)
                mstore8(resultPtr, mload(add(tablePtr, and(        input,  0x3F))))
                resultPtr := add(resultPtr, 1)
            }

            // padding with '='
            switch mod(mload(data), 3)
            case 1 { mstore(sub(resultPtr, 2), shl(240, 0x3d3d)) }
            case 2 { mstore(sub(resultPtr, 1), shl(248, 0x3d)) }
        }

        return result;
    }

    function decode(string memory _data) internal pure returns (bytes memory) {
        bytes memory data = bytes(_data);

        if (data.length == 0) return new bytes(0);
        require(data.length % 4 == 0, "invalid base64 decoder input");

        // load the table into memory
        bytes memory table = TABLE_DECODE;

        // every 4 characters represent 3 bytes
        uint256 decodedLen = (data.length / 4) * 3;

        // add some extra buffer at the end required for the writing
        bytes memory result = new bytes(decodedLen + 32);

        assembly {
            // padding with '='
            let lastBytes := mload(add(data, mload(data)))
            if eq(and(lastBytes, 0xFF), 0x3d) {
                decodedLen := sub(decodedLen, 1)
                if eq(and(lastBytes, 0xFFFF), 0x3d3d) {
                    decodedLen := sub(decodedLen, 1)
                }
            }

            // set the actual output length
            mstore(result, decodedLen)

            // prepare the lookup table
            let tablePtr := add(table, 1)

            // input ptr
            let dataPtr := data
            let endPtr := add(dataPtr, mload(data))

            // result ptr, jump over length
            let resultPtr := add(result, 32)

            // run over the input, 4 characters at a time
            for {} lt(dataPtr, endPtr) {}
            {
               // read 4 characters
               dataPtr := add(dataPtr, 4)
               let input := mload(dataPtr)

               // write 3 bytes
               let output := add(
                   add(
                       shl(18, and(mload(add(tablePtr, and(shr(24, input), 0xFF))), 0xFF)),
                       shl(12, and(mload(add(tablePtr, and(shr(16, input), 0xFF))), 0xFF))),
                   add(
                       shl( 6, and(mload(add(tablePtr, and(shr( 8, input), 0xFF))), 0xFF)),
                               and(mload(add(tablePtr, and(        input , 0xFF))), 0xFF)
                    )
                )
                mstore(resultPtr, shl(232, output))
                resultPtr := add(resultPtr, 3)
            }
        }

        return result;
    }
}

// File: src/code/ICode.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

interface ICode {
    function getCode(string calldata params) external view returns (string memory);
}

// File: src/code/v2/HtmlEthFsCode.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@holic/ethfs/contracts/src/IFileStore.sol";
import "base64-sol/base64.sol";

contract HtmlEthFsCode is Ownable {

    IFileStore public fileStore;// = IFileStore(0x9746fD0A77829E12F8A9DBe70D7a322412325B91);

    function fetch(string calldata ethFsFilename, bool isZipped, bool isBase64Encoded) external view returns(string memory) {
        return string.concat(
            "<script ",
            'id="', ethFsFilename,'" ',
            (isZipped ? "type=\"text/javascript+gzip\" " : ""),
            "src=\"data:text/javascript;base64,",
            (
                isBase64Encoded
                    ? string(fileStore.getFile(ethFsFilename).read())
                    : Base64.encode(abi.encodePacked(fileStore.getFile(ethFsFilename).read()))
            )
            ,
            "\"></script>"
        );
    }

    function setFilestoreAddr(address addr) external virtual onlyOwner {
        fileStore = IFileStore(addr);
    }
}

// File: src/code/v2/EthFsLibDef.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

struct EthFsLibDef {
    string scriptName;
    bool isBase64Encoded;
}

// File: src/code/v2/HtmlCode.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

import "@openzeppelin/contracts/access/Ownable.sol";
import "base64-sol/base64.sol";
import "../ICode.sol";
import "./HtmlEthFsCode.sol";
import "./HtmlScriptCode.sol";

contract HtmlCode is ICode, Ownable{

    string public startToTitle =
        '<!DOCTYPE html>'
        '<html lang="en">'
        '<head><title>';
    string public title =
        'Skies by BlockMachine';
    string public titleToStyle =
        '</title>'
        '<meta http-equiv="Content-Security-Policy" content="default-src \'self\';'
        'script-src \'self\' \'unsafe-inline\' data:;'
        'style-src \'unsafe-inline\'">'
        '<meta charset="utf-8"/>';
    string public style =
        '<style>'
        'html, body{'
        'margin:0;'
        'padding:0;'
        '}'
        'canvas{'
        'display:block;'
        'height:100vh;'
        'width:100vw;'
        '}'
        '</style>';
    string public headerToBody =
        '</head>'
        '<body>';

    string public bodyToEnd =
        '</body>'
        '</html>';

    HtmlScriptCode public scriptCode;

    function getCodeEncoded(string calldata params) external view returns(string memory) {
        return string.concat('data:text/html;base64,', Base64.encode(abi.encodePacked(getCode(params))));
    }

    function getCode(string calldata params) public view override returns(string memory) {
        return string.concat(
            startToTitle,
            title,
            titleToStyle,
            style,
            headerToBody,
            scriptCode.getCode(params),
            bodyToEnd
        );
    }

    function setScriptCode(address addr) external virtual onlyOwner {
        scriptCode = HtmlScriptCode(addr);
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: lib/ethfs/packages/contracts/src/IFileStore.sol



// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.13;

import {File} from "./File.sol";
import {IContentStore} from "./IContentStore.sol";

interface IFileStore {
    event FileCreated(
        string indexed indexedFilename,
        bytes32 indexed checksum,
        string filename,
        uint256 size,
        bytes metadata
    );
    event FileDeleted(
        string indexed indexedFilename,
        bytes32 indexed checksum,
        string filename
    );

    error FileNotFound(string filename);
    error FilenameExists(string filename);
    error EmptyFile();

    function contentStore() external view returns (IContentStore);

    function files(string memory filename)
        external
        view
        returns (bytes32 checksum);

    function fileExists(string memory filename) external view returns (bool);

    function getChecksum(string memory filename)
        external
        view
        returns (bytes32 checksum);

    function getFile(string memory filename)
        external
        view
        returns (File memory file);

    function createFile(string memory filename, bytes32[] memory checksums)
        external
        returns (File memory file);

    function createFile(
        string memory filename,
        bytes32[] memory checksums,
        bytes memory extraData
    ) external returns (File memory file);

    function deleteFile(string memory filename) external;
}

// File: lib/ethfs/packages/contracts/src/File.sol



// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.13;

struct Content {
    bytes32 checksum;
    address pointer;
}

struct File {
    uint256 size; // content length in bytes, max 24k
    Content[] contents;
}

function read(File memory file) view returns (string memory contents) {
    Content[] memory chunks = file.contents;

    // Adapted from https://gist.github.com/xtremetom/20411eb126aaf35f98c8a8ffa00123cd
    assembly {
        let len := mload(chunks)
        let totalSize := 0x20
        contents := mload(0x40)
        let size
        let chunk
        let pointer

        // loop through all pointer addresses
        // - get content
        // - get address
        // - get data size
        // - get code and add to contents
        // - update total size

        for { let i := 0 } lt(i, len) { i := add(i, 1) } {
            chunk := mload(add(chunks, add(0x20, mul(i, 0x20))))
            pointer := mload(add(chunk, 0x20))

            size := sub(extcodesize(pointer), 1)
            extcodecopy(pointer, add(contents, totalSize), 1, size)
            totalSize := add(totalSize, size)
        }

        // update contents size
        mstore(contents, sub(totalSize, 0x20))
        // store contents
        mstore(0x40, add(contents, and(add(totalSize, 0x1f), not(0x1f))))
    }
}

using {read} for File global;

// File: lib/ethfs/packages/contracts/src/IContentStore.sol



// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.13;

interface IContentStore {
    event NewChecksum(bytes32 indexed checksum, uint256 contentSize);

    error ChecksumExists(bytes32 checksum);
    error ChecksumNotFound(bytes32 checksum);

    function pointers(bytes32 checksum)
        external
        view
        returns (address pointer);

    function checksumExists(bytes32 checksum) external view returns (bool);

    function contentLength(bytes32 checksum)
        external
        view
        returns (uint256 size);

    function addPointer(address pointer) external returns (bytes32 checksum);

    function addContent(bytes memory content)
        external
        returns (bytes32 checksum, address pointer);

    function getPointer(bytes32 checksum)
        external
        view
        returns (address pointer);
}