contract NokuCustomERC721 is NokuCustomToken, ERC721Token, Whitelistable {
    using SafeMath for uint256;

    // tokenURI prefix => tokenURI = tokenBaseURI_ + tokenId
    string internal tokenBaseURI_ = "";

    // Number of tokens ever created in this contract
    // tokenId is generate incrementing this counter and assigning that to the minted token
    uint256 private totalTokensCount = 0;

    event LogNokuCustomERC721Created(
        address indexed caller,
        string indexed name,
        string indexed symbol,
        address pricingPlan,
        address serviceProvider,
        string tokenBaseURI,
        uint256 maxWhitelistLength,
        uint256 whitelistThreshold
    );

    event LogInformationChanged(
        address indexed caller, 
        string name, 
        string symbol
    );

    bytes32 public constant BURN_SERVICE_NAME = "NokuCustomERC721.burn";
    bytes32 public constant MINT_SERVICE_NAME = "NokuCustomERC721.mint";

    // bytes32 public constant SETTOKENURI_SERVICE_NAME = "NokuCustomERC721.setTokenURI";
    bytes32 public constant TRANSFERFROM_SERVICE_NAME = "NokuCustomERC721.transferFrom";

    modifier canBurnToken(uint256 _tokenId) {
        require(isApprovedOrOwner(msg.sender, _tokenId));
        _;
    }

    modifier canTransfer(uint256 _tokenId) {
        require(isApprovedOrOwner(msg.sender, _tokenId));
        _;
    }

    modifier isAllowed(address _to) {
        require(isAllowedBalance(_to, balanceOf(_to).add(1)) , "to not in WL: transfer not permitted");
        _;
    }

    constructor(
        string _name,
        string _symbol,
        string _tokenBaseURI,
        uint256 _maxWhitelistLength,
        uint256 _whitelistThreshold,
        address _pricingPlan,
        address _serviceProvider
    )
    NokuCustomToken(_pricingPlan, _serviceProvider)
    ERC721Token(_name, _symbol)
    Whitelistable(_maxWhitelistLength, _whitelistThreshold) public 
    {
        require(bytes(_name).length > 0, "_name is empty");
        require(bytes(_symbol).length > 0, "_symbol is empty");

        //_maxWhitelistLength && _whitelistThreshold already checked in whitelistableConstraints.sol
        
        if(bytes(_tokenBaseURI).length > 0){
            tokenBaseURI_ = _tokenBaseURI;
        }

        emit LogNokuCustomERC721Created(
            msg.sender,
            _name,
            _symbol,
            _pricingPlan,
            _serviceProvider,
            _tokenBaseURI,
            _maxWhitelistLength,
            _whitelistThreshold
        );
    }

    /**
     * Change the maximum whitelist length. New value shall satisfy the #isAllowedWhitelist conditions.
     * Changing maxWhitewListLength is permitted only when no token are allocated. 
     * @param _maxWhitelistLength The maximum whitelist length
     */
    function setMaxWhitelistLength(uint256 _maxWhitelistLength) public onlyOwner  {
        require(totalSupply() == 0, "Supply not zero");
        setMaxWhitelistLengthInternal(_maxWhitelistLength);
    }

       /**
     * Change the whitelist threshold balance. New value shall satisfy the #isAllowedWhitelist conditions.
     * Changing threasholdBalance is permitted only when no token are allocated. 
     * @param _whitelistThreshold The threshold balance (in wei) above which whitelisting is required to invest
     */
    function setWhitelistThresholdBalance(uint256 _whitelistThreshold) public onlyOwner  {
        require(totalSupply() == 0, "Supply not zero");
        setWhitelistThresholdBalanceInternal(_whitelistThreshold);
    }

     /**
     * Add the subscriber to the whitelist.
     * @param _subscriber The subscriber to add to the whitelist.
     */
    function addToWhitelist(address _subscriber) public onlyOwner  {
        addToWhitelistInternal(_subscriber);
    }

     /**
     * Add the subscribers to the whitelist.
     * @param _subscriberList The subscriber list to be added to the whitelist.
     */
    function addToWhitelistBulk(address [] _subscriberList) public onlyOwner  {
        uint256 i;
        uint256 length = _subscriberList.length;

        for (i = 0; i < length; i++) {
           addToWhitelistInternal(_subscriberList[i]);
        }
    }

    /**
     * Removed the subscriber from the whitelist.
     * @param _subscriber The subscriber to remove from the whitelist.
     */
    function removeFromWhitelist(address _subscriber) public onlyOwner  {
        removeFromWhitelistInternal(_subscriber, balanceOf(_subscriber));
    }

    /**
     * Removed the subscribers from the whitelist.
     * @param _subscriberList The subscriber list to be removed from the whitelist.
     */
    function removeFromWhitelistBulk(address [] _subscriberList) public onlyOwner  {
        uint256 i;
        uint256 length = _subscriberList.length;

        for (i = 0; i < length; i++) {
           removeFromWhitelistInternal(_subscriberList[i], balanceOf(_subscriberList[i]));
        }
    }

    /**
    * @dev Change the Custom Token detailed information after creation.
    * @param _name The name to assign to the Custom Token.
    * @param _symbol The symbol to assign to the Custom Token.
    */
    function setInformation(string _name, string _symbol) public onlyOwner returns(bool successful) {
        require(bytes(_name).length > 0, "_name is empty");
        require(bytes(_symbol).length > 0, "_symbol is empty");

        name_ = _name;
        symbol_ = _symbol;

        emit LogInformationChanged(msg.sender, _name, _symbol);

        return true;
    }

    function mint(address _to) public onlyOwner isAllowed(_to) {
        _mint(_to);
    }

    function safeMint(address _to) public onlyOwner isAllowed(_to) {
        _safeMint(_to, "");
    }

    function safeMint(address _to, bytes memory _data) public onlyOwner isAllowed(_to) {
        _safeMint(_to, _data);
    }

    function _mint(address _to) internal returns (uint256) {
        super._mint(_to, totalTokensCount);
        uint256 newTokenId = totalTokensCount;
        totalTokensCount++;

        require(pricingPlan.payFee(MINT_SERVICE_NAME, 1 * 10**18, msg.sender), "mint fee payment failed");
        return newTokenId;
    }

    function burn(uint256 _tokenId) public canBurn canBurnToken(_tokenId) {
        super._burn(ownerOf(_tokenId), _tokenId);

        require(pricingPlan.payFee(BURN_SERVICE_NAME, 1 * 10**18, msg.sender), "burn fee payment failed");
    }

    function updateTokenBaseURI(string memory _newBaseURI) public onlyOwner {
        tokenBaseURI_ = _newBaseURI;
    }

    function tokenBaseURI() public view returns (string) {
        return tokenBaseURI_;
    }

    function tokenURI(uint256 _tokenId) public view returns (string memory) {
        require(exists(_tokenId), "tokenId does not exist");
        return string(abi.encodePacked(tokenBaseURI_, uint2str(_tokenId)));
    }

    //This function is also called from safeTransferFrom!
    function transferFrom(address _from, address _to, uint256 _tokenId) public canTransfer(_tokenId) isAllowed(_to) {
        super.transferFrom(_from, _to, _tokenId);

        require(pricingPlan.payFee(TRANSFERFROM_SERVICE_NAME, 1 * 10**18, msg.sender), "transferFrom fee payment failed");
    }

    /**
     * @dev Internal function to safely mint a new token.
     * Reverts if the given token ID already exists.
     * If the target address is a contract, it must implement `onERC721Received`,
     * which is called upon a safe transfer, and return the magic value
     * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
     * the transfer is reverted.
     * @param to The address that will own the minted token
     * @param _data bytes data to send along with a safe transfer check
     */
    function _safeMint(address to, bytes memory _data) internal returns (uint256) {
        uint256 _tokenId = _mint(to);
        require(checkAndCallSafeTransfer(address(0), to, _tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
        return _tokenId;
    }

    function uint2str(uint i) internal pure returns (string){
        if (i == 0) return "0";
        uint j = i;
        uint length;
        while (j != 0){
            length++;
            j /= 10;
        }
        bytes memory bstr = new bytes(length);
        uint k = length - 1;
        while (i != 0){
            bstr[k--] = byte(48 + i % 10);
            i /= 10;
        }
        return string(bstr);
    }

}
/**
 * @dev The NokuCustomERC721Token contract is a custom ERC721-compliant token available in the Noku Service Platform (NSP).
 * The Noku customer is able to choose the token name, symbol, decimals, initial supply and to administer its lifecycle
 * by minting or burning tokens in order to increase or decrease the token supply.
 */
function addToWhitelistBulk(address [] _subscriberList) public onlyOwner  {
    uint256 i;
    uint256 length = _subscriberList.length;

    for (i = 0; i < length; i++) {
       addToWhitelistInternal(_subscriberList[i]);
    }
}

/**
 * Add the subscribers to the whitelist.
 * @param _subscriberList The subscriber list to be added to the whitelist.
 */