contract Normies is ERC1155, ERC1155Supply {
    using NormieLib for uint8;

    // Constants
    bytes32 private constant SKIN_TYPE_ENCODED = 0x5e47b691d06cb279a2b83088501f7f7b0810b33cdbc8844c9fdb87a16a66b4d2; 
    bytes32 private constant HAIR_ENCODED = 0x0aaac4442b72fa7fe81a222e1228a95232016724605a031667e60f7fce199df4;
    bytes32 private constant EYES_ENCODED = 0x64d4144b555b7316ce2973ff7e3cc9b8820c041e28a9c9439849589862f663d1;
    bytes32 private constant MOUTH_ENCODED = 0xd908a9a3716170d54dd91b434bdf0688b2c9d7dd05496173e40a5b3c359e0a5d;
    bytes32 private constant TORSO_ENCODED = 0x1feffdcc0b7d6d36bc3fbde6c0358074c13f6df6cbb4c3635167e516f9842361;
    bytes32 private constant PANTS_ENCODED = 0x6954a6332b7373d68e9dc351bffce9caf76b16493bad3034481d431b6add2dee;
    bytes32 private constant SHOES_ENCODED = 0x737a05110ae7219d78dc103c20b859c24da0b24052049edf853a13a0ef55b3a8;
    bytes32 private constant ACCESSORY_ENCODED = 0xea1a852ad5cf1fce5321823cddedf8aca1177f0c1acfb21e5eb86211bb8da1a7;
    bytes32 private constant ACCESSORY_ONE_ENCODED = 0xf9a9f51bd5106f9b945fe92fa2e7e1257dc60ef3bedd55ddba22a2be68c08f4d;
    bytes32 private constant ACCESSORY_TWO_ENCODED = 0xfc913118f916d1f1b5b00fee9486a2e642821127817edff3ed98980ed99d49bf;
    bytes32 private constant ACCESSORY_THREE_ENCODED = 0x76bbb549a0b8d61781a306d7d1deabaab974ad0f09eec4fea6fa556565443c2d;

    // Private variables
    address private owner;
    address payable private paymentAddress;

    // Public variables
    bool public paused = false;
    bool public allowMint = true;
    bool public allowWhitelistMint = true;
    bool public assetMint = true;
    bool public freeAssetMint = true;
    uint256 public mintNormiePrice = 0.02 ether;
    uint256 public currentNormieSupply = 0;
    uint256 public maxNormieSupply = 5000; // Will increment this as we do more releases
    uint256 public mintAssetsPrice = 0.01 ether;
    uint256 public currentAssetsSupply = 0;
    uint256 public maxAssetsSupply = 2000; // Will increment this as we do more releases

    bytes32 public merkleRoot; //Used for veryifing whitelist, must be hardcoded before hand
    mapping(address => bool) public whitelistClaimed; //used to verify whether or not this person actually claimed their whitelist

    // Basic Mappings 
    mapping(uint256 => NormieLib.Normie) public normieIdToNormie;
    mapping(uint256 => address) public normieOwner;

    mapping(uint256 => bool) public freeTokenMap;
    uint256[] public claimedFreeTokens; // Used to clean the freeTokenMap mapping for next free asset mint
    
    NormieTraits traits;

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner == msg.sender, "Not the owner");
        _;
    }

    constructor(address traitsContract, address payable _paymentaddr) ERC1155("") {
        owner = msg.sender;
        traits = NormieTraits(traitsContract);
        paymentAddress = payable(_paymentaddr);
    }

    ///////////////////////////// MINT FUNCTIONS /////////////////////////////
    /*
     * @dev Mint function for normies. Before calling make sure "maxNormieSupply" and "mintNormiePrice" have been updated.
     * @param amount Amount of mints being done in a transaction.
     */
    function mintNormie(uint256 amount)
    public payable {
        require(!allowMint, "Mint00");
        require(amount > 0, "Mint01");
        require(msg.sender == tx.origin, "Mint02");
        
        if (msg.sender != owner){
            require(msg.value >= mintNormiePrice * amount, "Not enough eth");
        }

        uint256 localCurrentNormieSupply = currentNormieSupply;
        require(localCurrentNormieSupply + amount <= maxNormieSupply, "Mint04");

        for (uint256 i = 1; i <= amount; i++){
            // Create each normie here.
            NormieLib.Normie memory newNormie = traits.generateNormie(localCurrentNormieSupply + i, msg.sender);
            normieIdToNormie[localCurrentNormieSupply + i] = newNormie;
            normieOwner[localCurrentNormieSupply + i] = msg.sender;
            _mint(msg.sender, localCurrentNormieSupply + i, 1, "");
        }
        currentNormieSupply = localCurrentNormieSupply + amount;
    }

    /*
     * @dev Whitelist Mint function for normies using MerkleTrees. Before calling make sure "maxNormieSupply" and "mintNormiePrice" have been updated.
     * @param amount Amount of mints being done in a transaction.
     * @param _merkleProof The hashed version of a user's address.
     */
    function whitelistMint(uint256 amount, bytes32[] calldata _merkleProof) 
    external payable {
        require(!allowWhitelistMint, "Mint00");

        //You can only mint up to 5 for whitelist
        require(amount > 0 && amount <= 5, "Mint01");

        //Make sure the person hasn't claimed yet
        require(!whitelistClaimed[msg.sender], "Address has already been claimed");

        //Verify the provided _merkleProof, given through the api call on our website.
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        require(MerkleProof.verify(_merkleProof, merkleRoot, leaf), "Invalid Proof");

        //Person has claimed their token.
        whitelistClaimed[msg.sender] = true;
        
        if (msg.sender != owner){
            require(msg.value >= mintNormiePrice * amount, "Not enough eth");
        }

        //Minting the token        
        uint256 localCurrentNormieSupply = currentNormieSupply;
        require(localCurrentNormieSupply + amount <= maxNormieSupply, "Mint04");

        for (uint256 i = 1; i <= amount; i++){
            // Create each normie here.
            NormieLib.Normie memory newNormie = traits.generateNormie(localCurrentNormieSupply + i, msg.sender);
            normieIdToNormie[localCurrentNormieSupply + i] = newNormie;
            normieOwner[localCurrentNormieSupply + i] = msg.sender;
            _mint(msg.sender, localCurrentNormieSupply + i, 1, "");
        }
        currentNormieSupply = localCurrentNormieSupply + amount;
    }

    /*
     * @dev Mint function for assets. Before calling make sure "maxAssetSupply" and "mintAssetPrice" have been updated.
     * @param amount Amount of mints being done in a transaction.
     */
    function mintAssets(uint256 amount)
    external payable {
        require(!assetMint, "Mint00");
        require(amount > 0, "Mint01");
        require(msg.sender == tx.origin, "Mint02");
        if (msg.sender != owner){
            require(msg.value >= mintAssetsPrice * amount, "Not enough eth");
        }

        uint256 localCurrentAssetsSupply = currentAssetsSupply;
        require(localCurrentAssetsSupply + amount <= maxAssetsSupply, "Mint04");

        uint256[] memory generatedHash;
        uint256 j = 0;
        uint256 currentTraitID;
        for (uint256 i = 1; i <= amount; i++){
            generatedHash = traits.hash(localCurrentAssetsSupply + i, msg.sender);
            for (j = 0; j < 10; j++){ 
                currentTraitID = traits.currentCollectionTraits(j,generatedHash[j]);
                if (currentTraitID != 1000001){
                    _mint(msg.sender, currentTraitID, 1, "");
                }
            }
        }

        currentAssetsSupply = localCurrentAssetsSupply + amount;
    }

    /*
     * @dev Mint function for token holders to get free assets.
     * @param tokenID Normie token ID. Used to verify that the person claiming the free asset actually owns the Normie.
     */
    function normieHolderMint(uint256 tokenID)
    external {
        require(!freeAssetMint, "Mint00");
        require(normieOwner[tokenID] == msg.sender, "Normie not owned by you");
        require(!freeTokenMap[tokenID], "Already claimed");

        uint256 localCurrentAssetsSupply = currentAssetsSupply;
        require(localCurrentAssetsSupply + 1 <= maxAssetsSupply, "Mint04");

        uint256[] memory generatedHash = traits.hash(localCurrentAssetsSupply + 1, msg.sender);
        uint256 currentTraitID;
        for (uint256 j = 0; j < 10; j++){ 
            currentTraitID = traits.currentCollectionTraits(j,generatedHash[j]);
            if (currentTraitID != 1000001){
                _mint(msg.sender, currentTraitID, 1, "");
            }
        }

        currentAssetsSupply = localCurrentAssetsSupply + 1;
        freeTokenMap[tokenID] = true;
        claimedFreeTokens.push(tokenID);
    }

    /*
     * @dev Overriden transfer function. When normies are transfered we update the normieOwner map.
     */
    function _beforeTokenTransfer(address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)
    internal override(ERC1155, ERC1155Supply) {
        require(!paused, "Paused");
        for (uint256 i = 0; i < ids.length; i++) {
            uint256 tokenID = ids[i];
            if (tokenID <= 1000000) {
                normieOwner[tokenID] = to;
            }
        }
        super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
    }

    ///////////////////////////// NORMIE CUSTOMIZE FUNCTIONS /////////////////////////////

    /*
     * @dev Public facing function that allows someone to remove traits from a normie.
     * @param normieID Normie token ID to remove trait from.
     * @param traitType Trait type to remove from. (Ex. Shoes, Bag, etc).
     */
    function removeTrait(uint256 normieID, string calldata traitType) public {
        require(!paused, "Paused");
        require(msg.sender == normieOwner[normieID], "Not owner");
        uint256 removedTraitID = removeTraitHelper(normieID, traitType, false);
        if (removedTraitID != 1000001) {
            _mint(msg.sender, removedTraitID, 1, "");
        }
    }

    /*
     * @dev Public facing function that allows someone to equip traits to a normie.
     * @param normieID Normie token ID to remove trait from.
     * @param traitType Trait type to remove from. (Ex. Shoes, Bag, etc).
     * @param traitID ID of Trait to equip. Should be greater than 1,000,000.
     */
    function equipTrait(uint256 normieID, string calldata traitType, uint256 traitID) external {
        require(!paused, "Paused");
        require(msg.sender == normieOwner[normieID], "Not owner");
        require(balanceOf(msg.sender, traitID) >= 1, "Do not own traitID");
        require(traitID > 1000000, "Not a traitID");
        uint256 removedTraitID = removeTraitHelper(normieID, traitType, true);
        if (removedTraitID != 1000001) {
            _mint(msg.sender, removedTraitID, 1, "");
        }
        equipTraitHelper(normieID, traitType, traitID);
        _burn(msg.sender, traitID, 1);
    }

    /**
     * @dev Removes a trait from a given "normie" using the provided "traitType". 
     * @param normieID ID of the normie being edited.
     * @param traitType The trait type being edited (Ex. Shoes).
     * @param equipAfter Boolean that indicates if this function was called to just remove or will also equip after. 
                         Provides extra functionality to ensure that a Normie cannot be completely blank and must keep its skin on.
     * @return (uint256) traitID to mint to user's wallet.
     */
    function removeTraitHelper(uint256 normieID, string calldata traitType, bool equipAfter) private
    returns (uint256) {
        NormieLib.Normie storage normie = normieIdToNormie[normieID];
        uint256 traitID;
        bytes32 encodedTraitType = keccak256(abi.encodePacked(traitType));
        if (encodedTraitType == SHOES_ENCODED){
            traitID = normie.shoes;
            normie.shoes = 1000001;
        } else if (encodedTraitType == PANTS_ENCODED) {
            traitID = normie.pants;
            normie.pants = 1000001;
        } else if (encodedTraitType == TORSO_ENCODED) {
            traitID = normie.torso;
            normie.torso = 1000001;
        } else if (encodedTraitType == SKIN_TYPE_ENCODED
                    && equipAfter) {
            traitID = normie.skinType;
            normie.skinType = 1000001;
        } else if (encodedTraitType == MOUTH_ENCODED) {
            traitID = normie.mouth;
            normie.mouth = 1000001;
        } else if (encodedTraitType == EYES_ENCODED) {
            traitID = normie.eyes;
            normie.eyes = 1000001;
        } else if (encodedTraitType == HAIR_ENCODED) {
            traitID = normie.hair;
            normie.hair = 1000001;
        } else if (encodedTraitType == ACCESSORY_ONE_ENCODED) {
            traitID = normie.accessoryOne;
            normie.accessoryOne = 1000001;
        } else if (encodedTraitType == ACCESSORY_TWO_ENCODED) {
            traitID = normie.accessoryTwo;
            normie.accessoryTwo = 1000001;
        } else if (encodedTraitType == ACCESSORY_THREE_ENCODED) {
            traitID = normie.accessoryThree;
            normie.accessoryThree = 1000001;
        } else {
            revert();
        }
        return traitID;
    }

    /**
     * @dev Equips a new trait to a given "normie" using the provided "traitID". 
     *      Checks to make sure "traitType" provided is the same as the traitType being equipped.
     *      Returns the updated normie. Used to update normieIdToNormie mapping in Normies.sol.
     * @param normieID ID of the normie being edited.
     * @param traitType The trait type being edited (Ex. Shoes).
     * @param traitID The traitID of the trait to equip.
     */
    function equipTraitHelper(uint256 normieID, string calldata traitType, uint256 traitID) private {
        uint32 newTraitID = uint32(traitID);
        string memory newTraitType = traits.getTraitTypeByTraitID(traitID);
        bytes32 encodedTraitType = keccak256(abi.encodePacked(traitType));
        bytes32 newTraitEncoded = keccak256(abi.encodePacked(newTraitType));

        if (newTraitEncoded == ACCESSORY_ENCODED){
            require(encodedTraitType == ACCESSORY_ONE_ENCODED
                || encodedTraitType == ACCESSORY_TWO_ENCODED
                || encodedTraitType == ACCESSORY_THREE_ENCODED, "Accessory mismatch");
        } else {
            require(encodedTraitType == newTraitEncoded, "Not the same trait type");
        }

        NormieLib.Normie storage normie = normieIdToNormie[normieID];
        if (encodedTraitType == SHOES_ENCODED){
            normie.shoes = newTraitID;
        } else if (encodedTraitType == PANTS_ENCODED) {
            normie.pants = newTraitID;
        } else if (encodedTraitType == TORSO_ENCODED) {
            normie.torso = newTraitID;
        } else if (encodedTraitType == SKIN_TYPE_ENCODED) {
            normie.skinType = newTraitID;
        } else if (encodedTraitType == MOUTH_ENCODED) {
            normie.mouth = newTraitID;
        } else if (encodedTraitType == EYES_ENCODED) {
            normie.eyes = newTraitID;
        } else if (encodedTraitType == HAIR_ENCODED) {
            normie.hair = newTraitID;
        } else if (encodedTraitType == ACCESSORY_ONE_ENCODED) {
            normie.accessoryOne = newTraitID;
        } else if (encodedTraitType == ACCESSORY_TWO_ENCODED) {
            normie.accessoryTwo = newTraitID;
        } else if (encodedTraitType == ACCESSORY_THREE_ENCODED) {
            normie.accessoryThree = newTraitID;
        } else {
            revert();
        }
    }


    ///////////////////////////// OPENSEA READ FUNCTIONS /////////////////////////////

    /*
     * @dev Name function used by OpenSea to name a collection.
     */
    function name() external pure returns (string memory) {
        return "NORMIES";
    }

    /*
     * @dev Name function used by OpenSea to set the symbol for a collection.
     */
    function symbol() external pure returns (string memory) {
        return "NORM";
    }

    /*
     * @dev URI function used by OpenSea to display NFTs.
     * @param _id Gets URI of a given token ID.
     * @return URI of a given token ID.
     */
    function uri(uint256 _id) 
    public view override 
    returns (string memory) {
        require(exists(_id), "URI: nonexistent token");
        if (_id <= 1000000){
            NormieLib.Normie storage normie = normieIdToNormie[_id];
            return traits.getNormieURI(normie, _id);
        } else {
            return traits.getAssetURI(_id);
        }
    }

    ///////////////////////////// OWNER FUNCTIONS /////////////////////////////
    /**
     * @dev Resets the freeTokenMap so that every address has a chance of claiming the free traits again.
     */
    function cleanFreeTokenMap() external onlyOwner {
        for (uint256 i = 0; i < claimedFreeTokens.length; i++){
            freeTokenMap[claimedFreeTokens[i]] = false;
        }
        delete claimedFreeTokens;
    }

    /**
     * @dev Sets normie mint price. Input is in wei and should be converted properly to eth before calling.
     * @param _mintNormiePrice New mint Normie price.
     */
    function setMintNormiePrice(uint256 _mintNormiePrice) external onlyOwner {
        require(_mintNormiePrice >= 0.01 ether, "Price less than 0.01 eth");
        mintNormiePrice = _mintNormiePrice;
    }

    /**
     * @dev Sets asset mint price. Input is in wei and should be converted propertly to eth before calling.
     * @param _mintAssetsPrice New mint assets price.
     */
    function setMintAssetsPrice(uint256 _mintAssetsPrice) external onlyOwner {
        require(_mintAssetsPrice >= 0.01 ether, "Price less than 0.01 eth");
        mintAssetsPrice = _mintAssetsPrice;
    }

    /**
     * @dev Sets the max normie supply. Altering this will allow us to increase the number of normies allowed to be minted.
     * @param _maxNormieSupply New max Normie supply.
     */
    function setMaxNormieSupply(uint256 _maxNormieSupply) external onlyOwner {
        maxNormieSupply = _maxNormieSupply;
    }

    /**
     * @dev Sets the max asset collection supply. Altering this will allow us to increase the number of asset packs allowed to be minted.
     * @param _maxAssetsSupply New max assets supply.
     */
    function setMaxAssetsSupply(uint256 _maxAssetsSupply) external onlyOwner {
        maxAssetsSupply = _maxAssetsSupply;
    }

    /**
     * @dev Owner mint function to be used to mint and giveaway assets.
     * @param tokenID An array of token IDs to mint to owner of contract.
     * @param amount An array of amounts for each associated token ID to mint.
     */
    function ownerMintAssets(uint256[] memory tokenID, uint256[] memory amount) external onlyOwner {
        require(tokenID.length == amount.length);
        for (uint256 i = 0; i < tokenID.length; i++){
            _mint(msg.sender, tokenID[i], amount[i], "");
        }
    }

    /**
     * @dev Flips the paused variable to pause/ unpause the contract.
     */
    function flipPaused() external onlyOwner {
        paused = !paused;
    }

    /**
     * @dev Flips the assetMint variable to pause/ unpause the contract.
     */
    function flipAllowMint() external onlyOwner {
        allowMint = !allowMint;
    }

    /**
     * @dev Flips the whitelistPaused variable to pause/ unpause the contract.
     */
    function flipAllowWhitelistMint() external onlyOwner {
        allowWhitelistMint = !allowWhitelistMint;
    }

    /**
     * @dev Flips the assetMint variable to pause/ unpause asset minting.
     */
    function flipAssetMint() external onlyOwner {
        assetMint = !assetMint;
    }

    /**
     * @dev Flips the freeAssetMint variable to pause/ unpause free asset minting.
     */
    function flipFreeAssetMint() external onlyOwner {
        freeAssetMint = !freeAssetMint;
    }

    /**
     * @dev Update the payment address.
     * @param _addr The new payable address.
     */
    function setPaymentAddr(address payable _addr) external onlyOwner {
        paymentAddress = _addr;
    }

    /**
     * @dev Updates the merkle root.
     * @param _merkleroot The new merkle root.
     */
    function setMerkleRoot(bytes32 _merkleroot) external onlyOwner {
        merkleRoot = _merkleroot;   
    }

    /**
     * @dev Withdraw the ETH from the contract to the payments address.
     */
    function withdraw() public payable onlyOwner {
        (bool os, ) = payable(paymentAddress).call{value: address(this).balance}("");
        require(os);
    }
}
// @author TheNormiesNFT (https://twitter.com/thenormiesnft)
function setPaymentAddr(address payable _addr) external onlyOwner {
    paymentAddress = _addr;
}

/**
 * @dev Update the payment address.
 * @param _addr The new payable address.
 */