contract Weapon is ERC721Enumerable, Ownable{
    using SafeMath for uint256;
    using Address for address;
    using Strings for uint256;

    address public PFPContract;
    uint256 public NFT_PRICE = 0; // 0 ETH
    uint public constant MAX_NFT_PURCHASE = 50;
    uint256 public MAX_SUPPLY = 5000;
    bool public saleIsActive = false;
    bool public composeIsActive = false;
      
    address public  AdvTextContractAddress;

    uint256 public startingIndex;
    uint256 public startingIndexBlock;

    string private _baseURIExtended;
    mapping (uint256 => string) _tokenURIs;

    constructor() ERC721("The Adventurers Weapon","Weapon"){
    }

    function setPFPContractAddress(address contractAddress) public onlyOwner {
        PFPContract = contractAddress;
    }

    function flipSaleState() public onlyOwner {
        saleIsActive = !saleIsActive;
    }

    function flipComposeState() public onlyOwner {
        composeIsActive = !composeIsActive;
    }
    
    function setMintPrice(uint price) external onlyOwner {
        NFT_PRICE = price;
    }

    function setMaxHeros(uint maxhero) external onlyOwner {
        MAX_SUPPLY = maxhero;
    }

    function setTextContractAddress(address contractAddress) public onlyOwner {
        AdvTextContractAddress = contractAddress;
    }

    function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }

    function reserveTokens(uint256 num) public onlyOwner {
        uint supply = totalSupply();
        uint i;
        for (i = 0; i < num; i++) {
            _safeMint(msg.sender, supply + i);
        }

        if (startingIndexBlock == 0) {
            startingIndexBlock = block.number;
        }
    }

    function trySetStartingIndexBlock() private {
        if (
            startingIndexBlock == 0 &&
            totalSupply() == MAX_SUPPLY)
        {
            startingIndexBlock = block.number;
        }
    }
      
    
    function mintbyowner(address owner, uint256 tokenId) public onlyOwner {
      require(saleIsActive, "Not allowed");
         
        require(!_exists(tokenId), "Token already minted");
        require(tokenId<= MAX_SUPPLY, "Token ID too high");
        _mint(owner, tokenId);
    }
    
function Mintweapon(uint256 tokenId) public {
    require(saleIsActive, "Sale must be active to mint a weapon");
    require(totalSupply() < MAX_SUPPLY, "Purchase would exceed max supply of weapon");
    require(tokenId < MAX_SUPPLY, "Requested tokenId exceeds upper bound");
    

    AdvText advText = AdvText(AdvTextContractAddress);

    require(advText.ownerOf(tokenId) == msg.sender, "Must own the Text for requested tokenId to mint a weapon");
    require(!_exists(tokenId), "Token already minted");
            
    _safeMint(msg.sender, tokenId);
    trySetStartingIndexBlock();
  }

  function Mintweapons(uint256 startingIndex, uint256 numDHeros) public {
    require(saleIsActive, "Sale must be active to mint a weapon");
    require(numDHeros > 0, "Must mint at least one weapon");
    require(numDHeros <= MAX_NFT_PURCHASE, "Cannot mint more than 50 weapon at once");

    AdvText advText = AdvText(AdvTextContractAddress);

    uint balance = advText.balanceOf(msg.sender);
    require(balance > 0, "Must hold at least one Text to mint a weapon");
    require(balance >= startingIndex + numDHeros, "Must hold at least as many Text as the number of weapons you intend to mint");
    
    for(uint i = 0; i < balance && i < numDHeros && i < MAX_NFT_PURCHASE; i++) {
      require(totalSupply() < MAX_SUPPLY, "Cannot exceed max supply of weapon.");
      uint tokenId = advText.tokenOfOwnerByIndex(msg.sender, i + startingIndex);
      if (!_exists(tokenId)) {
        _safeMint(msg.sender, tokenId);
      }
    }

    trySetStartingIndexBlock();
  }
  
    function compose(uint256 useTokenId, uint256 PFPtokenId, uint256 newPFPtokenId) public {
        require(composeIsActive, "Not allowed");
        require(PFPContract != address(0), "PFP contract address need be set");

       address from = msg.sender;
        AdventurersPFP pfp = AdventurersPFP(PFPContract);
        bool result = pfp.composePFP(from, useTokenId, PFPtokenId, newPFPtokenId);
        require(result, "PFP compose failed");
        
        _burnSave(msg.sender, useTokenId);

    }

    function calcStartingIndex() public onlyOwner {
        require(startingIndex == 0, "Starting index has already been set");
        require(startingIndexBlock != 0, "Starting index has not been set yet");

        startingIndex = uint(blockhash(startingIndexBlock)) % MAX_SUPPLY;
        // Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
        if(block.number.sub(startingIndexBlock) > 255) {
            startingIndex = uint(blockhash(block.number - 1)) % MAX_SUPPLY;
        }

        // To prevent original sequence
        if (startingIndex == 0) {
            startingIndex = startingIndex.add(1);
        }
    }

    function emergencySetStartingIndexBlock() public onlyOwner {
        require(startingIndex == 0, "Starting index is already set");
        startingIndexBlock = block.number;
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return _baseURIExtended;
    }

    // Sets base URI for all tokens, only able to be called by contract owner
    function setBaseURI(string memory baseURI_) external onlyOwner {
        _baseURIExtended = baseURI_;
    }

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        string memory _tokenURI = _tokenURIs[tokenId];
        string memory base = _baseURI();

        // If there is no base URI, return the token URI.
        if (bytes(base).length == 0) {
            return _tokenURI;
        }
        // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
        if (bytes(_tokenURI).length > 0) {
            return string(abi.encodePacked(base, _tokenURI));
        }
        // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI.
        return string(abi.encodePacked(base, tokenId.toString()));
    }
}
function setBaseURI(string memory baseURI_) external onlyOwner {
    _baseURIExtended = baseURI_;
}

// Sets base URI for all tokens, only able to be called by contract owner