contract MagicalUnicornCoalition is ERC721, Ownable {
    
    using SafeMath for uint256;

    string public UNICORN_PROVENANCE = ""; // IPFS URL WILL BE ADDED WHEN UNICORNS ARE ALL SOLD OUT
    
    string public LICENSE_TEXT = ""; 
    
    bool licenseLocked = false; 

    uint256 public constant unicornPrice = 55500000000000000; // 0.0555 ETH

    uint public constant maxUnicornPurchase = 20;
    
    mapping(address => bool) public whitelist;
    
    modifier onlyWhitelisted() {
    require(whitelist[msg.sender]);
    _;
  }

    uint256 public constant MAX_UNICORNS = 7777;

    bool public saleIsActive = false;

    bool public presaleIsActive = false;
    
    // Reserve 77 unicorns for giveaways
    uint public unicornReserve = 77;
    
    //payout wallets
    address private constant ADDR1_8 = 0x3057313c83eB036b631aB87422697D569320Ee2C;
    address private constant ADDR2_19 = 0x1B3637900285168adAbe6Ed458BDefF1e4548aD9;
    address private constant ADDR3_28 = 0xbc78E58c65a58C04B42BCb1e35a1C07f2057Bc04;
    address private constant ADDR4_18 = 0x0f53045a4b50b269954caE726cFDB03C9aA73D11;
    address private constant ADDR5_18 = 0xC27f2F8Adf67f8bce6b3a1dB5192fF3Ac845cAc0;
    address private constant ADDR6_4 = 0x1E82E5fF137D4e0ecf3589e9fB03428155Ffbfd5;
    address private constant ADDR7_5 = 0xDbbF29eF198b106408C70afa8D67a8d39146f1D3;
    
    event licenseisLocked(string _licenseText);

    constructor() ERC721("Magical Unicorn Coalition", "MUC") { }
    
    function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        msg.sender.transfer(balance);
    }
    
    function withdrawAll() public payable onlyOwner {
        uint256 balance = address(this).balance;
        require(balance > 0);
        _widthdraw(ADDR1_8, balance.mul(8).div(100));
        _widthdraw(ADDR2_19, balance.mul(19).div(100));
        _widthdraw(ADDR3_28, balance.mul(28).div(100));
        _widthdraw(ADDR4_18, balance.mul(18).div(100));
        _widthdraw(ADDR5_18, balance.mul(18).div(100));
        _widthdraw(ADDR6_4, balance.mul(4).div(100));
        _widthdraw(ADDR7_5, balance.mul(5).div(100));
    }

    function _widthdraw(address _address, uint256 _amount) private {
        (bool success,) = _address.call{value : _amount}("");
        require(success, "Transfer failed.");
    }
    
    function reserveUnicorns(address _to, uint256 _reserveAmount) public onlyOwner {        
        uint supply = totalSupply();
        require(_reserveAmount > 0 && _reserveAmount <= unicornReserve, "Not enough reserve left for team");
        for (uint i = 0; i < _reserveAmount; i++) {
            _safeMint(_to, supply + i);
        }
        unicornReserve = unicornReserve.sub(_reserveAmount);
    }

    function setProvenanceHash(string memory provenanceHash) public onlyOwner {
        UNICORN_PROVENANCE = provenanceHash;
    }

    function setBaseURI(string memory baseURI) public onlyOwner {
        _setBaseURI(baseURI);
    }

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

    function flipPresaleState() public onlyOwner {
        presaleIsActive = !presaleIsActive;
    }
    
    function addAddressToWhitelist(address addr) onlyOwner public returns(bool success) {
        if (!whitelist[addr]) {
          whitelist[addr] = true;
          success = true; 
        }
     }
  
    function addAddressesToWhitelist(address[] memory addrs) onlyOwner public returns(bool success) {
        for (uint256 i = 0; i < addrs.length; i++) {
          if (addAddressToWhitelist(addrs[i])) {
            success = true;
          }
        }
      }     
    
    function tokensOfOwner(address _owner) external view returns(uint256[] memory ) {
        uint256 tokenCount = balanceOf(_owner);
        if (tokenCount == 0) {
            // Return an empty array
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](tokenCount);
            uint256 index;
            for (index = 0; index < tokenCount; index++) {
                result[index] = tokenOfOwnerByIndex(_owner, index);
            }
            return result;
        }
    }
    
    // Returns the license for tokens
    function tokenLicense(uint _id) public view returns(string memory) {
        require(_id < totalSupply(), "CHOOSE A UNICORN WITHIN RANGE");
        return LICENSE_TEXT;
    }
    
    // Locks the license to prevent further changes 
    function lockLicense() public onlyOwner {
        licenseLocked =  true;
        emit licenseisLocked(LICENSE_TEXT);
    }
    
    // Change the license
    function changeLicense(string memory _license) public onlyOwner {
        require(licenseLocked == false, "License already locked");
        LICENSE_TEXT = _license;
    }
    
    function mintUnicorn(uint numberOfTokens) public payable {
        require(saleIsActive, "Sale must be active to mint Unicorn");
        require(numberOfTokens > 0 && numberOfTokens <= maxUnicornPurchase, "Can only mint 20 tokens at a time");
        require(totalSupply().add(numberOfTokens) <= MAX_UNICORNS, "Purchase would exceed max supply of Unicorns");
        require(msg.value >= unicornPrice.mul(numberOfTokens), "Ether value sent is not correct");
        
        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_UNICORNS) {
                _safeMint(msg.sender, mintIndex);
            }
        }
    }

    function mintPresale(uint numberOfTokens) onlyWhitelisted public payable {
        require(presaleIsActive, "Presale must be active to mint Unicorn");
        require(numberOfTokens > 0 && numberOfTokens <= maxUnicornPurchase, "Can only mint 20 tokens at a time");
        require(totalSupply().add(numberOfTokens) <= MAX_UNICORNS, "Purchase would exceed max supply of Unicorns");
        require(msg.value >= unicornPrice.mul(numberOfTokens), "Ether value sent is not correct");
        
        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_UNICORNS) {
                _safeMint(msg.sender, mintIndex);
            }
        }
    }
}
function lockLicense() public onlyOwner {
    licenseLocked =  true;
    emit licenseisLocked(LICENSE_TEXT);
}

// Locks the license to prevent further changes 