contract ToshiDojo is Ownable {
  using SafeMath for uint256;

  ToshimonMinter public toshimonMinter;
  ToshiCoin public toshiCoin;
  uint256 public minterPackId;
  uint256 public packPriceInToshiCoin;
  uint256 public packsPurchased;
  uint256 public packsRedeemed;
  bytes private prevHash;

  uint256[] public probabilities;
  uint256[][] public cardRanges;
  uint256[] public probabilitiesRare;
  uint256[][] public cardRangesRare; 


  event Redeemed(
    address indexed _user,
    uint256[] indexed _cardIds,
    uint256[] indexed _quantities
  );

  constructor() public {
    toshimonMinter = ToshimonMinter(0xd2d2a84f0eB587F70E181A0C4B252c2c053f80cB);
    toshiCoin = ToshiCoin(0x3EEfF4487F64bF73cd9D99e83D837B0Ef1F58247);
    minterPackId = 0;
    packPriceInToshiCoin = 1000000000000000000;
    prevHash = abi.encodePacked(block.timestamp, msg.sender);
    probabilities = [350,600,780,930,980,995,1000];
    cardRanges = [[uint256(1),uint256(102)],[uint256(103),uint256(180)],[uint256(181),uint256(226)],[uint256(227),uint256(248)],[uint256(249),uint256(258)],[uint256(259),uint256(263)],[uint256(264),uint256(264)]];
    probabilitiesRare = [700,930,980,995,1000];
    cardRangesRare = [[265,291],[292,307],[308,310],[311,311],[312,312]];


  }

  modifier onlyEOA() {
    require(msg.sender == tx.origin, 'Not eoa');
    _;
  }

  function setMinterPackId(uint256 _minterPackId) external onlyOwner {
    minterPackId = _minterPackId;
  }


  function setPackPriceInToshiCoin(uint256 _packPriceInToshiCoin)
    external
    onlyOwner
  {
    packPriceInToshiCoin = _packPriceInToshiCoin;
  }

  function tokenMaxSupply(uint256 _cardId) external view returns (uint256) {
    return toshimonMinter.tokenMaxSupply(_cardId);
  }

  function totalSupply(uint256 _cardId) external view returns (uint256) {
    return toshimonMinter.totalSupply(_cardId);
  }


  function addPack(
    uint256[] memory _probabilities,
    uint256[][] memory _cardRanges,
    uint256[] memory _probabilitiesRare,
    uint256[][] memory _cardRangesRare
    
  ) public onlyOwner {
    require(_probabilities.length > 0, 'probabilities cannot be empty');
    require(_cardRanges.length > 0, 'cardRanges cannot be empty');
    require(_probabilitiesRare.length > 0, 'probabilities rare cannot be empty');
    require(_cardRangesRare.length > 0, 'cardRanges rare cannot be empty');


    probabilities = _probabilities;
    cardRanges = _cardRanges;
    probabilitiesRare = _probabilitiesRare;
    cardRangesRare = _cardRangesRare;


  }

  function updateprobabilities(uint256[] memory _probabilities)
    external
    onlyOwner
  {
    probabilities = _probabilities;
  }

  function updateCardRanges(uint256[][] memory _cardRanges)
    external
    onlyOwner
  {
    cardRanges = _cardRanges;
  }
    function updateProbabilitiesRare(uint256[] memory _probabilitiesRare)
    external
    onlyOwner
  {
    probabilitiesRare = _probabilitiesRare;
  }

  function updateCardRangesRare(uint256[][] memory _cardRangesRare)
    external
    onlyOwner
  {
    cardRangesRare = _cardRangesRare;
  }





  // Purchase one or more card packs for the price in ToshiCoin
  function purchasePack(uint256 amount) public {
    require(packPriceInToshiCoin > 0, 'Pack does not exist');
    require(
      toshiCoin.balanceOf(msg.sender) >= packPriceInToshiCoin.mul(amount),
      'Not enough toshiCoin for pack'
    );

    toshiCoin.burn(msg.sender, packPriceInToshiCoin.mul(amount));
    packsPurchased = packsPurchased.add(amount);
    toshimonMinter.mint(msg.sender, minterPackId, amount, '');
  }
  
  // Redeem a random card pack (Not callable by contract, to prevent exploits on RNG)

  function redeemPack(uint256 _packsToRedeem) external {
     require(
      toshimonMinter.balanceOf(msg.sender, minterPackId) >= _packsToRedeem,
      'Not enough pack tokens'
    );

    toshimonMinter.burn(msg.sender, minterPackId, _packsToRedeem);

    uint256 probability;
    uint256 max;
    uint256 min; 
    uint256[] memory _cardsToMint = new uint256[](312);
    uint256[] memory _cardsToMintCount = new uint256[](312);
    uint256 cardIdWon;
    uint256 rng = _rngSimple(_rng());


    for (uint256 i = 0; i < _packsToRedeem; ++i) {

      for (uint256 j = 0; j < 7; ++j) {
          probability = rng % 1000;
          for (uint256 _probIndex = 0; _probIndex < probabilities.length; ++_probIndex) {
            if(probability < probabilities[_probIndex]){
              max = cardRanges[_probIndex][1];
              min = cardRanges[_probIndex][0];
              break;
            }
          }
          rng = _rngSimple(rng);
          cardIdWon = (rng % (max + 1 - min)) + min;
          _cardsToMint[cardIdWon - 1] = cardIdWon;
          _cardsToMintCount[cardIdWon - 1] = _cardsToMintCount[cardIdWon - 1] + 1;
      }
      
      // run for rare packs start
      probability = rng % 1000;
      for (uint256 _probIndex = 0; _probIndex < probabilitiesRare.length; ++_probIndex) {
        if(probability < probabilitiesRare[_probIndex]){
          max = cardRangesRare[_probIndex][1];
          min = cardRangesRare[_probIndex][0];
          break;
        }
      }
      rng = _rngSimple(rng);
      cardIdWon = (rng % (max + 1 - min)) + min;
      _cardsToMint[cardIdWon - 1] = cardIdWon;
      _cardsToMintCount[cardIdWon - 1] = _cardsToMintCount[cardIdWon - 1] + 1;
    }
    
    
    emit Redeemed(msg.sender,_cardsToMint,_cardsToMintCount);
    toshimonMinter.mintBatch(msg.sender,_cardsToMint,_cardsToMintCount);
  }
  
  
  // Utility function to check if a value is inside an array
  function _isInArray(uint256 _value, uint256[] memory _array)
    internal
    pure
    returns (bool)
  {
    uint256 length = _array.length;
    for (uint256 i = 0; i < length; ++i) {
      if (_array[i] == _value) {
        return true;
      }
    }

    return false;
  }


  // This is a pseudo random function, but considering the fact that redeem function is not callable by contract,
  // and the fact that ToshiCoin is not transferable, this should be enough to protect us from an attack
  // I would only expect a miner to be able to exploit this, and the attack cost would not be worth it in our case
  function _rng() internal returns (uint256) {
    bytes32 ret = keccak256(prevHash);
    prevHash = abi.encodePacked(ret,block.coinbase,msg.sender);
    return uint256(ret);
  }
  function _rngSimple(uint256 seed) internal pure returns (uint256) {

    return uint256(keccak256(abi.encodePacked(seed)));
  }
}
function _isInArray(uint256 _value, uint256[] memory _array)
  internal
  pure
  returns (bool)
{
  uint256 length = _array.length;
  for (uint256 i = 0; i < length; ++i) {
    if (_array[i] == _value) {
      return true;
    }
  }

  return false;
}

// Utility function to check if a value is inside an array