contract Presale is OwnableSimple, RandomApi, ERC721 {
    using strings for *;

    // There are 4 batches available for presale.
    // A batch is a set of artworks and
    // we plan to release batches monthly.
    uint256 public batchCount;
    mapping(uint256 => uint256) public prices;
    mapping(uint256 => uint256) public supplies;
    mapping(uint256 => uint256) public sold;

    // Before each batch is released on the main contract,
    // we will disable transfers (including trading)
    // on this contract.
    // This is to prevent someone selling an artwork
    // on the presale contract when we are migrating
    // the artworks to the main contract.
    mapping(uint256 => bool) public isTransferDisabled;

    uint256[] public dnas;
    mapping(address => uint256) public ownerToTokenCount;
    mapping (uint256 => address) public artworkIdToOwner;
    mapping (uint256 => address) public artworkIdToTransferApproved;

    PresaleMarket public presaleMarket;

    bytes4 constant ERC165Signature_ERC165 = bytes4(keccak256('supportsInterface(bytes4)'));

    // Latest version of ERC721 perhaps
    bytes4 constant ERC165Signature_ERC721A =
    bytes4(keccak256('totalSupply()')) ^
    bytes4(keccak256('balanceOf(address)')) ^
    bytes4(keccak256('ownerOf(uint256)')) ^
    bytes4(keccak256('approve(address,uint256)')) ^
    bytes4(keccak256('transfer(address,uint256)')) ^
    bytes4(keccak256('transferFrom(address,address,uint256)')) ^
    bytes4(keccak256('name()')) ^
    bytes4(keccak256('symbol()')) ^
    bytes4(keccak256('tokensOfOwner(address)')) ^
    bytes4(keccak256('tokenMetadata(uint256,string)'));

    // as described in https://github.com/ethereum/eips/issues/721
    // as of January 23, 2018
    bytes4 constant ERC165Signature_ERC721B =
    bytes4(keccak256('name()')) ^
    bytes4(keccak256('symbol()')) ^
    bytes4(keccak256('totalSupply()')) ^
    bytes4(keccak256('balanceOf(address)')) ^
    bytes4(keccak256('ownerOf(uint256)')) ^
    bytes4(keccak256('approve(address,uint256)')) ^
    bytes4(keccak256('takeOwnership(uint256)')) ^
    bytes4(keccak256('transfer(address,uint256)')) ^
    bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) ^
    bytes4(keccak256('tokenMetadata(uint256)'));

    function Presale() public {
        // Artworks are released in batches, which we plan to release
        // every month if possible. New batches might contain new characters,
        // or old characters in new poses. Later batches will definitely be
        // more rare.

        // By buying at presale, you have a chance to buy the
        // artwork at potentially 50% of the public release initial sales price.
        // Note that because the public release uses a sliding price system,
        // once an artwork is in the marketplace, the price will get lower until
        // someone buys it.

        // Example: You bought a batch 1 artwork at presale for 0.05 eth.
        // When the game launches, the first batch 1 artworks are generated
        // on the marketplace with the initial price of 0.1 eth. You sell yours
        // on the marketplace for 0.08 eth which is lower than the public release
        // initial sales price. If someone buys your artwork, you will get profit.

        // Note that we do not guarantee any profit whatsoever. The price of an
        // item we sell will get cheaper until someone buys it. So other people might wait
        // for the public release artworks to get cheaper and buy it instead of
        // buying yours.

        // Distribution of presale artworks:
        // When the game is released, all batch 1 presale artworks
        // will be immediately available for trading.

        // When other batches are released, first we will generate 10 artworks
        // on the marketplace. After that we will distribute the presale
        // artworks with the rate of around 10 every minute.
        // Note that because of mining uncertainties we cannot guarantee any
        // specific timings.

        // public release initial sales price >= 0.1 ether
        _addPresale(0.05 ether, 450);

        // public release initial sales price >= 0.24 ether
        _addPresale(0.12 ether, 325);

        // public release initial sales price >= 0.7 ether
        _addPresale(0.35 ether, 150);

        // public release initial sales price >= 2.0 ether
        _addPresale(1.0 ether, 75);
    }

    function buy(uint256 _batch) public payable {
        require(_batch < batchCount);
        require(msg.value == prices[_batch]); // we don't want to deal with refunds
        require(sold[_batch] < supplies[_batch]);

        sold[_batch]++;
        uint256 dna = _generateRandomDna(_batch);

        uint256 artworkId = dnas.push(dna) - 1;
        ownerToTokenCount[msg.sender]++;
        artworkIdToOwner[artworkId] = msg.sender;

        Transfer(0, msg.sender, artworkId);
    }

    function getArtworkInfo(uint256 _id) external view returns (
        uint256 dna, address owner) {
        require(_id < totalSupply());

        dna = dnas[_id];
        owner = artworkIdToOwner[_id];
    }

    function withdraw() public onlyOwner {
        msg.sender.transfer(this.balance);
    }

    function getBatchInfo(uint256 _batch) external view returns(uint256 price, uint256 supply, uint256 soldAmount) {
        require(_batch < batchCount);

        return (prices[_batch], supplies[_batch], sold[_batch]);
    }

    function setTransferDisabled(uint256 _batch, bool _isDisabled) external onlyOwner {
        require(_batch < batchCount);

        isTransferDisabled[_batch] = _isDisabled;
    }

    function setPresaleMarketAddress(address _address) public onlyOwner {
        PresaleMarket presaleMarketTest = PresaleMarket(_address);
        require(presaleMarketTest.isPresaleMarket());
        presaleMarket = presaleMarketTest;
    }

    function sell(uint256 _artworkId, uint256 _price) external {
        require(_isOwnerOf(msg.sender, _artworkId));
        require(_canTransferBatch(_artworkId));
        _approveTransfer(_artworkId, presaleMarket);
        presaleMarket.createAuction(_artworkId, _price, msg.sender);
    }

    // Helper methods

    function _addPresale(uint256 _price, uint256 _supply) private {
        prices[batchCount] = _price;
        supplies[batchCount] = _supply;

        batchCount++;
    }

    function _generateRandomDna(uint256 _batch) private returns(uint256 dna) {
        uint256 rand = random256() % (10 ** 76);

        // set batch digits
        rand = rand / 100000000 * 100000000 + _batch;

        return rand;
    }

    function _isOwnerOf(address _address, uint256 _tokenId) private view returns (bool) {
        return artworkIdToOwner[_tokenId] == _address;
    }

    function _approveTransfer(uint256 _tokenId, address _address) internal {
        artworkIdToTransferApproved[_tokenId] = _address;
    }

    function _transfer(address _from, address _to, uint256 _tokenId) internal {
        artworkIdToOwner[_tokenId] = _to;
        ownerToTokenCount[_to]++;

        ownerToTokenCount[_from]--;
        delete artworkIdToTransferApproved[_tokenId];

        Transfer(_from, _to, _tokenId);
    }

    function _approvedForTransfer(address _address, uint256 _tokenId) internal view returns (bool) {
        return artworkIdToTransferApproved[_tokenId] == _address;
    }

    function _transferFrom(address _from, address _to, uint256 _tokenId) internal {
        require(_isOwnerOf(_from, _tokenId));
        require(_approvedForTransfer(msg.sender, _tokenId));

        // prevent accidental transfer
        require(_to != address(0));
        require(_to != address(this));

        // perform the transfer and emit Transfer event
        _transfer(_from, _to, _tokenId);
    }

    function _canTransferBatch(uint256 _tokenId) internal view returns(bool) {
        uint256 batch = dnas[_tokenId] % 10;
        return !isTransferDisabled[batch];
    }

    function _tokenMetadata(uint256 _tokenId, string _preferredTransport) internal view returns (string infoUrl) {
        _preferredTransport; // we don't use this parameter

        require(_tokenId < totalSupply());

        strings.slice memory tokenIdSlice = _uintToBytes(_tokenId).toSliceB32();
        return "/http/etherwaifu.com/presale/artwork/".toSlice().concat(tokenIdSlice);
    }

    // Author: pipermerriam
    // MIT license
    // https://github.com/pipermerriam/ethereum-string-utils
    function _uintToBytes(uint256 v) internal pure returns(bytes32 ret) {
        if (v == 0) {
            ret = '0';
        }
        else {
            while (v > 0) {
                ret = bytes32(uint256(ret) / (2 ** 8));
                ret |= bytes32(((v % 10) + 48) * 2 ** (8 * 31));
                v /= 10;
            }
        }
        return ret;
    }

    // Required methods of ERC721

    function totalSupply() public view returns (uint256) {
        return dnas.length;
    }

    function balanceOf(address _owner) public view returns (uint256) {
        return ownerToTokenCount[_owner];
    }

    function ownerOf(uint256 _tokenId) external view returns (address) {
        address theOwner = artworkIdToOwner[_tokenId];
        require(theOwner != address(0));
        return theOwner;
    }

    function approve(address _to, uint256 _tokenId) external {
        require(_canTransferBatch(_tokenId));

        require(_isOwnerOf(msg.sender, _tokenId));

        // MUST throw if _tokenID does not represent an NFT
        // but if it is not NFT, owner is address(0)
        // which means it is impossible because msg.sender is a nonzero address

        require(msg.sender != _to);

        address prevApprovedAddress = artworkIdToTransferApproved[_tokenId];
        _approveTransfer(_tokenId, _to);

        // Don't send Approval event if it is just
        // reaffirming that there is no one approved
        if(!(prevApprovedAddress == address(0) && _to == address(0))) {
            Approval(msg.sender, _to, _tokenId);
        }
    }

    function transfer(address _to, uint256 _tokenId) external {
        require(_canTransferBatch(_tokenId));
        require(_isOwnerOf(msg.sender, _tokenId));

        // prevent accidental transfers
        require(_to != address(0));
        require(_to != address(this));
        require(_to != address(presaleMarket));

        // perform the transfer and emit Transfer event
        _transfer(msg.sender, _to, _tokenId);
    }

    function transferFrom(address _from, address _to, uint256 _tokenId) external {
        require(_canTransferBatch(_tokenId));
        _transferFrom(_from, _to, _tokenId);
    }

    function takeOwnership(uint256 _tokenId) external {
        require(_canTransferBatch(_tokenId));
        address owner = artworkIdToOwner[_tokenId];
        _transferFrom(owner, msg.sender, _tokenId);
    }

    // Optional methods of ERC721

    function tokensOfOwner(address _owner) external view returns (uint256[] tokenIds) {
        uint256 count = balanceOf(_owner);

        uint256[] memory res = new uint256[](count);
        uint256 allArtworkCount = totalSupply();
        uint256 i = 0;

        for(uint256 artworkId = 1; artworkId <= allArtworkCount && i < count; artworkId++) {
            if(artworkIdToOwner[artworkId] == _owner) {
                res[i++] = artworkId;
            }
        }

        return res;
    }

    function tokenMetadata(uint256 _tokenId, string _preferredTransport) external view returns (string infoUrl) {
        return _tokenMetadata(_tokenId, _preferredTransport);
    }

    function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256 tokenId) {
        require(_index < balanceOf(_owner));

        // not strictly needed because if the state is consistent then
        // a match will be found
        uint256 allArtworkCount = totalSupply();

        uint256 i = 0;
        for(uint256 artworkId = 0; artworkId < allArtworkCount; artworkId++) {
            if(artworkIdToOwner[artworkId] == _owner) {
                if(i == _index) {
                    return artworkId;
                } else {
                    i++;
                }
            }
        }
        assert(false); // should never reach here
    }

    function tokenMetadata(uint256 _tokenId) external view returns (string infoUrl) {
        return _tokenMetadata(_tokenId, "http");
    }

    // ERC-165 Standard interface detection (required)

    function supportsInterface(bytes4 _interfaceID) external view returns (bool)
    {
        return _interfaceID == ERC165Signature_ERC165 ||
        _interfaceID == ERC165Signature_ERC721A ||
        _interfaceID == ERC165Signature_ERC721B;
    }
}
function supportsInterface(bytes4 _interfaceID) external view returns (bool)
{
    return _interfaceID == ERC165Signature_ERC165 ||
    _interfaceID == ERC165Signature_ERC721A ||
    _interfaceID == ERC165Signature_ERC721B;
}

// ERC-165 Standard interface detection (required)