contract TPirates is ITPirates {
    using Strings for uint256;

    // struct to store each trait's data for metadata and rendering
    struct Trait {
        string name;
        string png;
    }

    // mapping from trait type index to its name
    string[9] _traitTypes = [
        "Sky",
        "Cockpit",
        "Base",
        "Engine",
        "Nose",
        "Wing",
        "Weapon1",
        "Weapon2",
        "Rank"
    ];

    address public auth;

    // storage of each traits name and base64 PNG data
    mapping(uint8 => mapping(uint8 => Trait)) public traitData;


    IPirates public piNFT;

    constructor() {
    auth = msg.sender;
    }
    /** ADMIN */


    modifier onlyOwner() {
        require(msg.sender == auth);
        _;
    }


    function setPirates(address _piNFT) external onlyOwner {
        piNFT = IPirates(_piNFT);
    }

    /**
     * administrative to upload the names and images associated with each trait
     * @param traitType the trait type to upload the traits for (see traitTypes for a mapping)
     * @param traits the names and base64 encoded PNGs for each trait
     */
    function uploadTraits(
        uint8 traitType,
        uint8[] calldata traitIds,
        Trait[] calldata traits
    ) external onlyOwner {
        require(traitIds.length == traits.length, "Mismatched inputs");
        for (uint256 i = 0; i < traits.length; i++) {
            traitData[traitType][traitIds[i]] = Trait(
                traits[i].name,
                traits[i].png
            );
        }
    }

    /** RENDER */

    /**
     * generates an <image> element using base64 encoded PNGs
     * @param trait the trait storing the PNG data
     * @return the <image> element
     */
    function drawTrait(Trait memory trait)
        internal
        pure
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    '<image x="0" y="0" width="64" height="64" image-rendering="pixelated" preserveAspectRatio="xMidYMid" xlink:href="data:image/png;base64,',
                    trait.png,
                    '"/>'
                )
            );
    }

    /**
     * generates an entire SVG by composing multiple <image> elements of PNGs
     * @param tokenId the ID of the token to generate an SVG for
     * @return a valid SVG of the Pirate
     */
    function drawSVG(uint256 tokenId) public view returns (string memory) {
        IPirates.Pirate memory q = piNFT.getTokenTraitsPirate(tokenId);
        string memory svgString = string(
            abi.encodePacked(
                drawTrait(traitData[0][q.sky]),
                drawTrait(traitData[1][q.cockpit]),
                drawTrait(traitData[2][q.base]),
                drawTrait(traitData[3][q.engine]),
                drawTrait(traitData[4][q.nose]),
                drawTrait(traitData[5][q.wing]),
                drawTrait(traitData[6][q.weapon1]),
                drawTrait(traitData[7][q.weapon2]),
                drawTrait(traitData[8][q.rank])
            )
        );

        return
            string(
                abi.encodePacked(
                    '<svg id="galaxyGame" width="100%" height="100%" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">',
                    svgString,
                    "</svg>"
                )
            );
    }

    /**
     * generates an attribute for the attributes array in the ERC721 metadata standard
     * @param traitType the trait type to reference as the metadata key
     * @param value the token's trait associated with the key
     * @return a JSON dictionary for the single attribute
     */
    function attributeForTypeAndValue(
        string memory traitType,
        string memory value
    ) internal pure returns (string memory) {
        return
            string(
                abi.encodePacked(
                    '{"trait_type":"',
                    traitType,
                    '","value":"',
                    value,
                    '"}'
                )
            );
    }

    /**
     * generates an array composed of all the individual traits and values
     * @param tokenId the ID of the token to compose the metadata for
     * @return a JSON array of all of the attributes for given token ID
     */
    function compileAttributes(uint256 tokenId)
        public
        view
        returns (string memory)
    {
        IPirates.Pirate memory q = piNFT.getTokenTraitsPirate(tokenId);
        string memory traits;
        traits = string(
            abi.encodePacked(
                attributeForTypeAndValue(
                    _traitTypes[0],
                    traitData[0][q.sky].name
                ),
                ",",
                attributeForTypeAndValue(
                    _traitTypes[1],
                    traitData[1][q.cockpit].name
                ),
                ",",
                attributeForTypeAndValue(
                    _traitTypes[2],
                    traitData[2][q.base].name
                ),
                ",",
                attributeForTypeAndValue(
                    _traitTypes[3],
                    traitData[3][q.engine].name
                ),
                ",",
                attributeForTypeAndValue(
                    _traitTypes[4],
                    traitData[4][q.nose].name
                ),
                ",",
                attributeForTypeAndValue(
                    _traitTypes[5],
                    traitData[5][q.wing].name
                ),
                ",",
                attributeForTypeAndValue(
                    _traitTypes[6],
                    traitData[6][q.weapon1].name
                ),
                ",",
                attributeForTypeAndValue(
                    _traitTypes[7],
                    traitData[7][q.weapon2].name
                ),
                ",",
                attributeForTypeAndValue(
                    _traitTypes[8], 
                    traitData[8][q.rank].name
                ),
                ","
            )
        );
        return
            string(
                abi.encodePacked(
                    "[",
                    traits,
                    '{"trait_type":"Generation","value":',
                    tokenId <= 888 ? '"Gen 0"' : '"Gen 1"',
                    '},{"trait_type":"Type","value": "Pirate"',
                    "}]"
                )
            );
    }

    /**
     * generates a base64 encoded metadata response without referencing off-chain content
     * @param tokenId the ID of the token to generate the metadata for
     * @return a base64 encoded JSON dictionary of the token's metadata and SVG
     */
    function tokenURI(uint256 tokenId)
        public
        view
        override
        returns (string memory)
    {
        require(msg.sender == address(piNFT));
        string memory tokenName = piNFT.tokenNameByIndex(tokenId);
        bool hasName = piNFT.hasBeenNamed(tokenId);

        string memory metadata = string(
            abi.encodePacked(
                '{"name": "',
                "Pirate  ",
                hasName ? tokenName : tokenId.toString(),
                '", "description": "Ai-Lax Pirates are known for their lethal tactics and an unrelenting dismissal of all Imperial Guild edicts. They will do anything and everything to get their hands on newly mined EON. This includes orbital blockades to strong arm Miners for a piece of their hard work and even kidnapping new ones that enter the Pytheas system. Tread cautiously as you traverse the darkness, you could be their next target. All the metadata and images are generated and stored 100% on-chain. No IPFS. NO API. Just the Ethereum blockchain.", "image": "data:image/svg+xml;base64,',
                base64(bytes(drawSVG(tokenId))),
                '", "attributes":',
                compileAttributes(tokenId),
                "}"
            )
        );
        return
            string(
                abi.encodePacked(
                    "data:application/json;base64,",
                    base64(bytes(metadata))
                )
            );
    }

    /** BASE 64 - Written by Brech Devos */
    string internal constant TABLE =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

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

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

        // 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) {

            } {
                dataPtr := add(dataPtr, 3)

                // read 3 bytes
                let input := mload(dataPtr)

                // write 4 characters
                mstore(
                    resultPtr,
                    shl(248, mload(add(tablePtr, and(shr(18, input), 0x3F))))
                )
                resultPtr := add(resultPtr, 1)
                mstore(
                    resultPtr,
                    shl(248, mload(add(tablePtr, and(shr(12, input), 0x3F))))
                )
                resultPtr := add(resultPtr, 1)
                mstore(
                    resultPtr,
                    shl(248, mload(add(tablePtr, and(shr(6, input), 0x3F))))
                )
                resultPtr := add(resultPtr, 1)
                mstore(
                    resultPtr,
                    shl(248, 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 drawSVG(uint256 tokenId) public view returns (string memory) {
    IPirates.Pirate memory q = piNFT.getTokenTraitsPirate(tokenId);
    string memory svgString = string(
        abi.encodePacked(
            drawTrait(traitData[0][q.sky]),
            drawTrait(traitData[1][q.cockpit]),
            drawTrait(traitData[2][q.base]),
            drawTrait(traitData[3][q.engine]),
            drawTrait(traitData[4][q.nose]),
            drawTrait(traitData[5][q.wing]),
            drawTrait(traitData[6][q.weapon1]),
            drawTrait(traitData[7][q.weapon2]),
            drawTrait(traitData[8][q.rank])
        )
    );

    return
        string(
            abi.encodePacked(
                '<svg id="galaxyGame" width="100%" height="100%" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">',
                svgString,
                "</svg>"
            )
        );
}

/**
 * generates an entire SVG by composing multiple <image> elements of PNGs
 * @param tokenId the ID of the token to generate an SVG for
 * @return a valid SVG of the Pirate
 */