contract DERP is ERC721, IERC721Receiver, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    event SoldToVault(uint256 txId, address user, uint256 tokenId, uint256 price);
    event BoughtFromVault(uint256 txId, address user, uint256 tokenId, uint256 price);
    event IssueLoan(uint256 loanId, address debtor, uint256 term, uint256 principle, uint256 interest, uint256 tokenId);
    event LoanPaidInFull(uint256 loanId, address debtor, uint256 value, uint256 tokenId);

    struct Loan {
      uint256 loanId;
      address debtor;
      uint256 term;
      uint256 total;
      uint256 startBlock;
      uint256 interest;
    }

    struct VaultStats {
      uint256 loanId;
      uint256 txId;
      uint256 totalLoanPrinciple;
      uint256 totalLoanInterest;
      uint256 paidLoanPrinciple;
      uint256 paidLoanInterest;
      uint256 totalVaultSellVolume;
      uint256 totalVaultBuyVolume;
    }

    struct TokenData{
        bytes32 dataTxHash;
        uint8[4] traits;
    }

    mapping(uint256 => Loan) public _currentLoans;
    bytes32 _dataHash;
    uint8 _dataLocked = 0;
    uint8 _vaultOpen = 0;
    string _currentBaseURI = "https://www.derpdao.com/api/";
    uint256 _maxSupply = 10000;
    address payable _a1 = payable(0xA7058206694Ad85467aF11B8A7ADe4a1a8ebe62D);
    address payable _a2 = payable(0x81a5F4c1718BC4C177E9F4b362Ddf2CEa53D7C63);
    uint256 _loanTermBlocks = 44500; // 1week ~= 44500 blocks *********


    VaultStats vStats;

    constructor() payable ERC721("Derp DAO", "DERP") {
      vStats.loanId = 1;
      vStats.txId = 1;

      _dataHash = 0;

      uint8 i = 0;
      for(i=0; i< 50; i++){
        _tokenIds.increment();
        super._safeMint(msg.sender, _tokenIds.current());
        _tokenIds.increment();
        super._safeMint(_a1, _tokenIds.current());
        _tokenIds.increment();
         super._safeMint(_a2, _tokenIds.current());
      }
    }


    function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) public override returns (bytes4) {
        return this.onERC721Received.selector;
    }


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

    function updateDataHash(bytes32 dHash) public onlyOwner{
        require(_dataLocked == 0);
        _dataHash = dHash;
    }

    // Allows for the traits to be locked after they are all properly setup
    function lockdata(uint8 lock) public onlyOwner {
        if(lock == 1)
            _dataLocked = 1;
    }

    function openVault(uint8 open) public onlyOwner{
      _vaultOpen = open;
    }

    function getDataHash() public view returns (bytes32){
        return _dataHash;
    }

    function updateBaseURI(string memory newBaseURI) public onlyOwner{
      require(_dataLocked == 0);
      _currentBaseURI = newBaseURI;
    }

    function getUserSellPrice() public view returns(uint256){
      return (address(this).balance/500);
    }

    function getUserBuyPrice() public view returns(uint256){
      return (address(this).balance/400);
    }

    function getVaultStatistics() public view returns(VaultStats memory){
      return vStats;
    }


    function takeLoan(uint256 tokenId, uint256 term) public payable {
      require(term < 25, "Term must be 24 weeks or less"); // max loan term is 6 months
      uint256 loanValue = getUserSellPrice()/4; // loans are 25% of token value
      uint256 interest = loanValue*3*term/1000; //loans are 0.3% per term (1wk terms, ~1.2%/month)
      // take the collateral
      safeTransferFrom(msg.sender ,address(this) ,tokenId);
      // create the loan
      Loan memory currentLoan;
      currentLoan.debtor = msg.sender;
      currentLoan.term = term*_loanTermBlocks;
      currentLoan.total = loanValue + interest;
      currentLoan.interest = interest;
      currentLoan.startBlock = block.number;
      currentLoan.loanId = vStats.loanId;
      vStats.totalLoanPrinciple = vStats.totalLoanPrinciple + loanValue;
      vStats.totalLoanInterest = vStats.totalLoanInterest + interest;
      // issue the loan
      payable(msg.sender).transfer(currentLoan.total);
      _currentLoans[tokenId] = currentLoan;
      emit IssueLoan(vStats.loanId, msg.sender,currentLoan.term,loanValue,interest, tokenId);
      vStats.loanId = vStats.loanId + 1;
    }

    function payLoan(uint256 tokenId) public payable {
      // If the loan hasnt expired, ensure the debtor is trying to pay, otherwise let anyone with the cash claim it
      if(block.number < (_currentLoans[tokenId].term +  _currentLoans[tokenId].startBlock))
        require(_currentLoans[tokenId].debtor == msg.sender, "This is not your loan");
      // ensure the value is greater than the loan total
      require(msg.value >= _currentLoans[tokenId].total, "You must pay your loan in full");
      // send their collateral back to them
      IERC721 derpContract = IERC721(address(this));
      derpContract.safeTransferFrom(address(this) ,msg.sender ,tokenId);
      vStats.paidLoanPrinciple = vStats.paidLoanPrinciple + _currentLoans[tokenId].total - _currentLoans[tokenId].interest;
      vStats.paidLoanInterest = vStats.paidLoanInterest + _currentLoans[tokenId].interest;
      _currentLoans[tokenId].term = 0;
      _currentLoans[tokenId].total = 0;
      _currentLoans[tokenId].debtor = address(0);
      _currentLoans[tokenId].startBlock = 0;
      _currentLoans[tokenId].interest = 0;
      emit LoanPaidInFull(_currentLoans[tokenId].loanId, msg.sender, msg.value, tokenId);
      _currentLoans[tokenId].loanId = 0;
    }

    function viewLoan(uint256 tokenId) public view returns(Loan memory){
      return _currentLoans[tokenId];
    }

    function sellToken(uint256 tokenId, uint256 minPrice) public payable {
      // The user is selling their NFT, and this vault is buying it
      uint256 price = getUserSellPrice();
      require(getUserSellPrice() >= minPrice, "Price is too low");
      if(_vaultOpen == 0)
        require(totalSupply() == _maxSupply, "The vault doesnt open until all 10K tokens are sold");
      payable(msg.sender).transfer(price);
      vStats.totalVaultBuyVolume = vStats.totalVaultBuyVolume + price; // user selling, vault buying
      safeTransferFrom(msg.sender ,address(this) ,tokenId);
      emit SoldToVault(vStats.txId, msg.sender, tokenId, price);
      vStats.txId = vStats.txId + 1;
    }

    function buyToken(uint256 tokenId) public payable {
      // The user is buying our NFT and this vault is selling it
      require(tokenId <= _maxSupply, "Invalid tokenId");
      require(msg.value >= getUserBuyPrice(), "Purchase price too low");
      require(_currentLoans[tokenId].term == 0, "Cannot buy this token, it is collateral for a loan"); // require there is no loan against the token this user is trying to buy
      vStats.totalVaultSellVolume = vStats.totalVaultSellVolume + msg.value; // user buying, vault selling
      IERC721 derpContract = IERC721(address(this));
      derpContract.safeTransferFrom(address(this) ,msg.sender ,tokenId);
      emit BoughtFromVault(vStats.txId, msg.sender, tokenId, msg.value);
      vStats.txId = vStats.txId + 1;
    }


   /**
   * @dev Mints a new NFT.
   */
    function mint(uint256 mints) public payable {
        require(msg.value == (5* (10 ** 15))*mints); // 0.005 eth
        require( mints <= 20);
        uint8 i = 0;
        for(i=0; i< mints; i++){
          _tokenIds.increment();
          require(_tokenIds.current() <= _maxSupply);
          super._safeMint(msg.sender, _tokenIds.current());
        }
    }




    function getNftTraits(uint256 reqTokenId) public view returns(uint8[4] memory){
      require(ownerOf(reqTokenId) != address(0));
      uint16 [4] memory tmp;
      tmp[0] = uint16(uint256(keccak256(abi.encodePacked(reqTokenId*177601))) % 32768);
      tmp[1] = uint16(uint256(keccak256(abi.encodePacked(tmp[0])))%32768);
      tmp[2] = uint16(uint256(keccak256(abi.encodePacked(tmp[1])))%32768);
      tmp[3] = uint16(uint256(keccak256(abi.encodePacked(tmp[2])))%32768);

      uint16 [15] memory numbers = [328,983,1802,2621,3604,4915,6389,8355,10649,13271,16220,19496,23265,27688,32769];

      uint8 i = 0;
      uint8 j = 0;

      uint8 [4] memory traits = [0,0,0,0];

      for(j=0; j < 4; j++)
          for(i=0; i < 14; i++){
              if(tmp[j] < numbers[i]){
                traits[j] = i;
                break;
              }

          }

      return traits;
    }

    function totalSupply() public view returns (uint256){
      return _tokenIds.current();
    }

    function tokenData(uint256 reqTokenId) public view returns (TokenData memory){
        TokenData memory currentTokenData;
        currentTokenData.traits = getNftTraits(reqTokenId);
        currentTokenData.dataTxHash = _dataHash;
        return currentTokenData;
    }

    function uploadData(string[15] calldata textColor, string[15] calldata backgroundColor, string[15] calldata font, string[15] calldata data) external {}
}
function lockdata(uint8 lock) public onlyOwner {
        if(lock == 1)
            _dataLocked = 1;
    }
// Allows for the traits to be locked after they are all properly setup