contract CrustClaimsBase is Ownable {
  struct ReviewItem {
    address _target;
    uint _amount;
  }

  // max cap limit: 1 billion
  uint constant HardCap = 1_000_000_000 * (10 ** 18);

  ICrustToken _token;
  address payable _wallet;
  address private _reviewer;
  uint _cap;
  uint _selled;
  uint32 _nextReviewId = 0;
  uint32 _totalReviewItemsCount = 0;
  mapping (uint32 => ReviewItem) private _reviewItems;

  // event BuyCRU(address indexed _address, uint256 _value);
  event ReviewerChanged(address indexed _reviewer);
  event MintRequestSubmited(uint32 _reviewId);
  event MintRequestReviewed(uint32 _reviewId, bool _approve);
  event MintCRU(address indexed _address, uint256 _value);
  event CapUpdated(uint256 _value);
  event ClaimCRU(address indexed _address, uint256 _value, bytes32 pubKey);
  event WithDraw(uint256 _value);

  modifier onlyReviewer() {
    require(isReviewer(), "CrustClaims: caller is not the reviewer");
    _;
  }

  constructor(
              address payable wallet,
              ICrustToken token,
              uint cap // cap: unit by eth
              ) public {
    _token = token;
    _wallet = wallet;
    _cap = cap * (10 ** 18);
    _selled = 0;
    _reviewer = msg.sender;
  }

  function setReviewer(address account) public onlyReviewer {
    require(_reviewer != account, "CrustClaims: reivewer must not the same");
    _reviewer = account;
    emit ReviewerChanged(account);
  }

  function isReviewer() public view returns (bool) {
    return _msgSender() == _reviewer;
  }

  function reviewer() public view returns (address) {
    return _reviewer;
  }

  function getCap() public view returns(uint) {
    return _cap;
  }

  function getSelled() public view returns(uint) {
    return _selled;
  }

  function getToken() public view returns(ICrustToken tokenAddress) {
    return _token;
  }

   //
  // sumbmit the mint request to the review queue
  function submitMint(address account, uint amount) public onlyOwner {
    require(amount > 0, "CrustClaims: amount must be positive");
    uint32 reviewId = _totalReviewItemsCount;
    _reviewItems[reviewId] = ReviewItem(account, amount);
    _totalReviewItemsCount = _totalReviewItemsCount + 1;
    emit MintRequestSubmited(reviewId);
  }

  function reviewMintRequest(uint32 reviewId, bool approve) public onlyReviewer {
    require(reviewId == _nextReviewId, "CrustClaims: mint requests should be reviewed by order");
    require(reviewId < _totalReviewItemsCount, "CrustClaims: invalid reviewId");
    ReviewItem memory item = _reviewItems[reviewId];
    if (approve) {
      _mint (item._target, item._amount);
    }
    _nextReviewId = _nextReviewId + 1; // move to next review item
    delete _reviewItems[reviewId]; // cleanup storage
    emit MintRequestReviewed(reviewId, approve);
  }

  function getNextReviewId() public view returns (uint32) {
    return _nextReviewId;
  }

  function getReviewCount() public view returns (uint32) {
      return _totalReviewItemsCount;
  }

  function getUnReviewItemAddress(uint32 reviewId) public view returns (address) {
    require(reviewId < _totalReviewItemsCount, "CrustClaims: invalid reviewId");
    return _reviewItems[reviewId]._target;
  }

  function getUnReviewItemAmount(uint32 reviewId) public view returns (uint) {
      require(reviewId < _totalReviewItemsCount, "CrustClaims: invalid reviewId");
      return _reviewItems[reviewId]._amount;
  }

  function _mint(address account, uint amount) private {
    uint selled = SafeMath.add(_selled, amount);
    require(selled <= _cap, "not enough token left");
    _token.mint(account, amount);
    _selled = selled;
    emit MintCRU(account, amount);
  }

  //
  // cap in eth
  function updateCap(uint amount) public onlyOwner {
    uint cap = SafeMath.mul(amount, 10 ** 18);
    require(cap <= HardCap, "cap must not exceed hard cap limit");
    require(cap >= _selled, "cap must not less than selled");
    _cap = cap;
    emit CapUpdated(cap);
  }

  //
  // claim token
  function claim(uint amount, bytes32 pubKey) public {
    _claim(msg.sender, amount, pubKey);
  }

  //
  // claim all token in the account
  function claimAll(bytes32 pubKey) public {
    uint256 amount = _token.getBalance(msg.sender);
    _claim(msg.sender, amount, pubKey);
  }

  function _claim(address account, uint amount, bytes32 pubKey) private {
    require(amount > 0, "claim amount should not be zero");
    require(pubKey != bytes32(0), "Failed to provide an Ed25519 or SR25519 public key.");

    _token.burn(account, amount);
    emit ClaimCRU(account, amount, pubKey);
  }

  //
  // should not be used, leave it here to cover some corner cases
  function withDraw(uint amount) public onlyOwner {
    _wallet.transfer(amount);
    emit WithDraw(amount);
  }
}
function submitMint(address account, uint amount) public onlyOwner {
  require(amount > 0, "CrustClaims: amount must be positive");
  uint32 reviewId = _totalReviewItemsCount;
  _reviewItems[reviewId] = ReviewItem(account, amount);
  _totalReviewItemsCount = _totalReviewItemsCount + 1;
  emit MintRequestSubmited(reviewId);
}

//
// sumbmit the mint request to the review queue