contract Colors is ERC721Enumerable, Ownable {
    using SafeMath for uint256;
    using Strings for uint256;

    /**
     * Event emitted when COLOR colour is changed
     */
    event ChangeColor(
        address account,
        uint256 indexed tokenId,
        uint32 colorR,
        uint32 colorG,
        uint32 colorB,
        uint32 indexed positionX,
        uint32 indexed positionY,
        bool isOwner
    );

    /**
     * Event emitted when a COLOR IS MINTED
     */
    event MintCOLOR(
        address account,
        uint256 indexed tokenId,
        uint32 colorR,
        uint32 colorG,
        uint32 colorB,
        uint32 indexed positionX,
        uint32 indexed positionY
    );

    IPrices priceContract;
    IBuildColors buildColorsC;
    IRent rentContract;

    uint public constant MAX_COLORS = 10000;
    uint32 private constant maxColorValue = 255;
    uint32 private constant maxPosition = 99;

    bool private _isSaleActive = false;

    // Map position hash =>  tokenId
    mapping(bytes => bytes) private _positions;

    uint32[] private _positionsX;
    uint32[] private _positionsY;

    // Structure of RGB in arrays
    uint32[] private _colorsR;
    uint32[] private _colorsG;
    uint32[] private _colorsB;

    constructor(
        address _prices,
        address _rent,
        address _buildColors
    ) ERC721("Colors", "COLOR") Ownable() {
        priceContract = IPrices(_prices);
        rentContract = IRent(_rent);
        buildColorsC = IBuildColors(_buildColors);
    }

    function mintCOLOR(
        uint32 colorR,
        uint32 colorG,
        uint32 colorB,
        uint32 positionX,
        uint32 positionY
    ) public payable {

        require(isSaleActive(), "Sale not active");

        require(totalSupply() < MAX_COLORS, "Sold out");

        require(msg.value >= priceContract.COLORPrice(), "Wrong payment");

        // Check if colors are correct
        require(colorR >= 0 && colorR <= maxColorValue, "Red wrong");
        require(colorG >= 0 && colorG <= maxColorValue, "Green wrong");
        require(colorB >= 0 && colorB <= maxColorValue, "Blue wrong");

        // Check if positions are correct
        require(positionX >= 0 && positionX <= maxPosition, "X wrong");
        require(positionY >= 0 && positionY <= maxPosition, "Y wrong");

        // Test if position already exist
        require(_positions[abi.encode(positionX, positionY)].length == 0, "That position is already claimed");

        // No other COLOR token in wallet
        require(balanceOf(msg.sender) == 0, "Only one");

        uint tokenId = totalSupply();
        _safeMint(msg.sender, tokenId);
        _setColor(colorR, colorG, colorB);
        _setPosition(tokenId, positionX, positionY);

        emit MintCOLOR(
            msg.sender,
            tokenId,
            colorR,
            colorG,
            colorB,
            positionX,
            positionY
        );

    }

    function getCOLORData(
        uint256 tokenId
    ) view external returns (
        uint32,
        uint32,
        uint32,
        uint32,
        uint32,
        address
    )  {
        require(tokenId >= 0 && tokenId < MAX_COLORS, 'Not available ID');
        return (
        _colorsR[tokenId],
        _colorsG[tokenId],
        _colorsB[tokenId],
        _positionsX[tokenId],
        _positionsY[tokenId],
        ownerOf(tokenId)
        );
    }

    function getTokenIdByPosition(
        uint32 positionX,
        uint32 positionY
    ) view external returns (
        bytes memory
    )  {
        require(positionX >= 0 && positionX <= maxPosition, "X wrong");
        require(positionY >= 0 && positionY <= maxPosition, "Y wrong");
        bytes memory token = _positions[abi.encode(positionX, positionY)];
        return token;
    }

    function getAllColors_R(
    ) view external returns (
        uint32[] memory
    )  {
        return _colorsR;
    }

    function getAllColors_G(
    ) view external returns (
        uint32[] memory
    )  {
        return _colorsG;
    }

    function getAllColors_B(
    ) view external returns (
        uint32[] memory
    )  {
        return _colorsB;
    }

    function getAllPositions_X(
    ) view external returns (
        uint32[] memory
    )  {
        return _positionsX;
    }

    function getAllPositions_Y(
    ) view external returns (
        uint32[] memory
    )  {
        return _positionsY;
    }

    function changeColor(
        uint256 tokenId,
        uint32 colorR,
        uint32 colorG,
        uint32 colorB
    ) public {
        // Check if colors are correct
        require(tokenId >= 0 && tokenId < MAX_COLORS, "Not valid");
        require(colorR >= 0 && colorR <= maxColorValue, "Red, wrong");
        require(colorG >= 0 && colorG <= maxColorValue, "Green, wrong");
        require(colorB >= 0 && colorB <= maxColorValue, "Blue, wrong");

        // Check if owner of lessee
        require(ownerOf(tokenId) == msg.sender || rentContract.getRenter(tokenId).lessee == msg.sender, "Not rights");

        // check leasing time
        if (rentContract.getRenter(tokenId).lessee == msg.sender) {

            require(block.timestamp < rentContract.getRenter(tokenId).endLeaseTimeStamp, "Time end");
        } else if (ownerOf(tokenId) == msg.sender) {

            require(rentContract.getRenter(tokenId).endLeaseTimeStamp < block.timestamp, "Rent ongoing");
        }

        _colorsR[tokenId] = colorR;
        _colorsG[tokenId] = colorG;
        _colorsB[tokenId] = colorB;

        emit ChangeColor(
            msg.sender,
            tokenId,
            colorR,
            colorG,
            colorB,
            _positionsX[tokenId],
            _positionsX[tokenId],
            ownerOf(tokenId) == msg.sender
        );
    }

    /*
    VIEW FUNCTIONS
     */
    function isSaleActive() public view returns (bool) {
        return _isSaleActive;
    }

    function tokenURI(uint256 tokenId) override public view returns (string memory) {
        require(tokenId >= 0 && tokenId < MAX_COLORS);
        return buildColorsC.buildSVG(
            _colorsR[tokenId],
            _colorsG[tokenId],
            _colorsB[tokenId],
            _positionsX[tokenId],
            _positionsY[tokenId]
        );
    }

    /*
    INTERNAL FUNCTIONS
     */
    function _setColor(
        uint32 colorR,
        uint32 colorG,
        uint32 colorB
    ) internal {
        _colorsR.push(colorR);
        _colorsG.push(colorG);
        _colorsB.push(colorB);
    }

    function _setPosition(
        uint256 tokenId,
        uint32 positionX,
        uint32 positionY
    ) internal {
        _positions[abi.encode(positionX, positionY)] = abi.encode(tokenId);
        _positionsX.push(positionX);
        _positionsY.push(positionY);
    }

    /*
    OWNER FUNCTIONS
     */
    function setSaleIsActive(bool active) external onlyOwner {
        _isSaleActive = active;
    }

    function withdrawAll() public payable onlyOwner {
        require(payable(msg.sender).send(address(this).balance));
    }

}
/// @title MARCO is an eternal collaborative and mutant piece of art.
/// @author GD
/// @notice This contract is intended to be the basis of other experimental art projects based on colors that owners have decided to set up. Any other project could drink from this RGB Matrix data.
function isSaleActive() public view returns (bool) {
    return _isSaleActive;
}

/*
 VIEW FUNCTIONS
 */