contract ERC721Tradable is ERC721, Ownable{
    using SafeMath for uint256;
    using Strings for string;
    
    event NFTGenerated(address indexed _owner, uint256 indexed _nftId, uint256 _series);
    event NewNFTPrice(uint256 indexed _newprice);
    
    string internal baseMetadataURI;
	address proxyRegistryAddress;
	uint256 public totalNFTs;
	uint256 NFTAvaliable;
	uint256 public currentNFTid = 0;
	uint256 public NFTprice = 0.05 * 10**18;
	
	uint256 totalAirDrop;
	uint256 totalAuthorize;
	uint256 totalPerverse;
	uint256 totalFreeBuy;
	
    uint256 series = 0;
	
	mapping (address => uint256) public MarketingQuota;
	mapping (address => bool) public PartnerTokenCheck;
	mapping (address => bool) public IfWhiteList;
	mapping (address => bool) public Authorized;
	
	bytes4 constant InterfaceSignature_ERC721 = 0x80ac58cd;
	ERC721Token public CryptoPunks;
	ERC721Token public LOSTPOETS;
	ERC721Token public PakCube;
	ERC721Token public TheCurrency;
	
	bool public canMint = true;
	modifier CanMint() {
	    require(canMint);
	    _;
	}
    
	constructor(
		string memory  _name,
		string memory _symbol,
		uint256 _totalNFTs,
		uint256 _airdrop,
		uint256 _authorize,
		uint256 _perserve,
		uint256 _freebuy,
		address _proxyRegistryAddress
	) public {
		name = _name;
		symbol = _symbol;
		totalNFTs = _totalNFTs;
		totalAirDrop = _airdrop;
		totalAuthorize = _authorize;
		totalPerverse = _perserve;
		totalFreeBuy = _freebuy;
		NFTAvaliable = _totalNFTs - _airdrop - _authorize - _perserve;
		proxyRegistryAddress = _proxyRegistryAddress;
	}
	
	function mintDisable() public onlyOwner {
	    canMint = false;
	}
	
	function mintEnable() public onlyOwner {
	    canMint = true;
	}
	
	// Regular Purchase
	function buyNFT(uint256 _amount) public payable CanMint {
	    require(msg.value >= NFTprice.mul(_amount), "Insufficient ETH");
	    require(totalFreeBuy >= _amount);
	    require(currentNFTid.add(_amount) <= totalNFTs+1, "Total Amount Exceed!");
	    for(uint i=0; i< _amount; i++) {
	        _mint(msg.sender, currentNFTid);
	        currentNFTid = currentNFTid.add(1);
	        emit NFTGenerated(msg.sender, currentNFTid, series);
	    }
	    totalFreeBuy = totalFreeBuy.sub(_amount);
	}
	
	function adminGenerator(uint256 _amount, address receiver) public onlyOwner CanMint {
	    require(currentNFTid.add(_amount) <= totalNFTs+1, "Total Amount Exceed!");
	    require(NFTAvaliable >= _amount, "NFT not available");
	    require(totalPerverse >= _amount, "Perserve Amount Exceed!");
	    for(uint i=0; i< _amount; i++) {
	        _mint(receiver, currentNFTid);
	        currentNFTid = currentNFTid.add(1);
	        emit NFTGenerated(receiver, currentNFTid, series);
	    }
	    NFTAvaliable = NFTAvaliable.sub(_amount);
	    totalPerverse = totalPerverse.sub(_amount);
	}
	
	function marketGenerator(uint256 _amount, address receiver) public CanMint {
	    require(MarketingQuota[msg.sender] >= _amount, "Marketing Quota is not enough!");
	    require(currentNFTid.add(_amount) <= totalNFTs+1, "Total Amount Exceed!");
	    for(uint i=0; i< _amount; i++) {
	        _mint(receiver, currentNFTid);
	        currentNFTid = currentNFTid.add(1);
	        emit NFTGenerated(receiver, currentNFTid, series);
	    }
	    MarketingQuota[msg.sender] = MarketingQuota[msg.sender].sub(_amount);
	}
	
	function authorizedGenerator(uint256 _amount, address receiver) public CanMint {
	    require(Authorized[msg.sender]);
	    require(totalAuthorize >= _amount);
	    require(currentNFTid.add(_amount) <= totalNFTs+1, "Total Amount Exceed!");
	    for(uint i=0; i< _amount; i++) {
	        _mint(receiver, currentNFTid);
	        currentNFTid = currentNFTid.add(1);
	        emit NFTGenerated(receiver, currentNFTid, series);
	    }
	    totalAuthorize = totalAuthorize.sub(_amount);
	}
	
	function authorizeAddress(address _vp) public onlyOwner {
	    Authorized[_vp] = true;
	}
	
	function cancelAuthorized(address _vp) public onlyOwner {
	    Authorized[_vp] = false;
	}
	
	function setSeries(uint256 _series) public onlyOwner {
	    series = _series;
	}
	
	function setMarketQuota(address _spender, uint256 _amount) public onlyOwner {
	    require(_amount >0);
	    require(NFTAvaliable >= _amount);
	    MarketingQuota[_spender] = MarketingQuota[_spender] + _amount;
	    NFTAvaliable = NFTAvaliable.sub(_amount);
	}
	
	function airdrop() public CanMint {
	    require(_canAirdrop(msg.sender) || _ifWhiteListed(msg.sender), "Unqualified!");
	    require(currentNFTid <= totalNFTs, "Total Amount Exceed!");
	    require(totalAirDrop > 0);
	    _mint(msg.sender, currentNFTid);
	    currentNFTid = currentNFTid.add(1);
	    emit NFTGenerated(msg.sender, currentNFTid, series);
	    if(IfWhiteList[msg.sender]) {
	        IfWhiteList[msg.sender] = false;
	    } else {
	        PartnerTokenCheck[msg.sender] = true;
	    }
	    totalAirDrop = totalAirDrop.sub(1);
	}
	
	function setWhitelist(address[] memory _users) public onlyOwner {
	    uint userLength = _users.length;
	    for (uint i = 0; i < userLength; i++) {
	        IfWhiteList[_users[i]] = true;
	    }
	}
	
	function initialPartnerNFT(address _cryptoPunksAddress, address _lostpoetsAddress, address _pakCube, address _theCurrencyAddress) public onlyOwner {
	    ERC721Token cryptoPunks = ERC721Token(_cryptoPunksAddress);
        CryptoPunks = cryptoPunks;
        
        ERC721Token lostpoets = ERC721Token(_lostpoetsAddress);
        LOSTPOETS = lostpoets;
        
        ERC721Token pakCube = ERC721Token(_pakCube);
        PakCube = pakCube;
        
        ERC721Token theCurrencyAddress = ERC721Token(_theCurrencyAddress);
        TheCurrency = theCurrencyAddress;
	}

	function _canAirdrop(address _user) private view returns(bool) {
	    if (PartnerTokenCheck[_user]){
	        return false;
	    }
	    if(CryptoPunks.balanceOf(_user) > 0) {
	        return true;
	    } else if (LOSTPOETS.balanceOf(_user) > 0) {
	        return true;
	    } else if (PakCube.balanceOf(_user) > 0) {
	        return true;
	    } else if (TheCurrency.balanceOf(_user) > 0) {
	        return true;
	    } else{
	        return false;
	    }
	}
	
	function _ifWhiteListed(address _user) private view returns(bool) {
	    return IfWhiteList[_user];
	}
	
	function setNFTPrice(uint256 _newPrice) public onlyOwner {
	    require(_newPrice > 0);
	    NFTprice = _newPrice;
	    emit NewNFTPrice(_newPrice);
	}
	
	function setProxyAddress(address _proxyAddress) public onlyOwner {
	    proxyRegistryAddress = _proxyAddress;
	}

    function tokenURI(uint256 _tokenId) public view returns (string memory) {
        require(_exists(_tokenId), "ERC721Tradable#uri: NONEXISTENT_TOKEN");
        return Strings.strConcat(baseMetadataURI, Strings.uint2str(_tokenId));
    }
    
    function _setBaseMetadataURI(string memory _newBaseMetadataURI) internal {
        baseMetadataURI = _newBaseMetadataURI;
    }

	function setBaseMetadataURI(string memory _newBaseMetadataURI) public onlyOwner {
		_setBaseMetadataURI(_newBaseMetadataURI);
	}

	function isApprovedForAll(address _owner, address _operator) public view returns (bool isOperator) {
		ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
		if (address(proxyRegistry.proxies(_owner)) == _operator) {
			return true;
		}

		return ERC721.isApprovedForAll(_owner, _operator);
	}
}
function buyNFT(uint256 _amount) public payable CanMint {
    require(msg.value >= NFTprice.mul(_amount), "Insufficient ETH");
    require(totalFreeBuy >= _amount);
    require(currentNFTid.add(_amount) <= totalNFTs+1, "Total Amount Exceed!");
    for(uint i=0; i< _amount; i++) {
        _mint(msg.sender, currentNFTid);
        currentNFTid = currentNFTid.add(1);
        emit NFTGenerated(msg.sender, currentNFTid, series);
    }
    totalFreeBuy = totalFreeBuy.sub(_amount);
}

// Regular Purchase