contract MNY is StandardToken, MintableToken, BurnableToken {
  using SafeMath for uint256;

  string public constant name = "MNY by Monkey Capital";
  string public constant symbol = "MNY";
  uint8 public constant decimals = 18;
  uint public constant SWAP_CAP = 21000000 * (10 ** uint256(decimals));
  uint public cycleMintSupply = 0;
  MNYTiers public tierContract;

  event SwapStarted(uint256 endTime);
  event MiningRestart(uint16 tier);

  uint public offset = 10**8;
  uint public decimalOffset = 10 ** uint256(decimals);
  uint public baseRate = 1 ether;
  mapping(address => uint) public exchangeRatios;
  mapping(address => uint) public unPaidFees;
  address[] public miningTokens;

  //initial state
  uint16 public currentTier = 1;
  uint public mnyLeftInCurrent = 6.597 ether;
  uint public miningTokenLeftInCurrent = 0.0369 ether;
  uint public currentRate = mnyLeftInCurrent.div(miningTokenLeftInCurrent.div(offset));
  bool public isMiningOpen = false;
  bool public miningActive = false;
  uint16 public lastTier = 2856;

  constructor() public {
    totalSupply_ = 0;
    //only the contract itself can mint as the owner
    owner = this;
  }

  modifier canMine() {
    require(isMiningOpen);
    _;
  }

  modifier onlyAdmin() {
    require(msg.sender == creator || msg.sender == dev || msg.sender == origDev);
    _;
  }

  // first call Token(address).approve(mny address, amount) for MNY to transfer on your behalf.
  function mine(address token, uint amount) canMine public {
    require(token != 0 && amount > 0);
    require(exchangeRatios[token] > 0 && cycleMintSupply < SWAP_CAP);
    require(ERC20(token).transferFrom(msg.sender, this, amount));
    _mine(token, amount);
  }

  function _mine(address _token, uint256 _inAmount) private {
    if (!miningActive) {
      miningActive = true;
    }
    uint _tokens = 0;
    uint miningPower = exchangeRatios[_token].div(baseRate).mul(_inAmount);
    unPaidFees[_token] += _inAmount.div(2);

    while (miningPower > 0) {
      if (miningPower >= miningTokenLeftInCurrent) {
        miningPower -= miningTokenLeftInCurrent;
        _tokens += mnyLeftInCurrent;
        miningTokenLeftInCurrent = 0;
        mnyLeftInCurrent = 0;
      } else {
        uint calculatedMny = currentRate.mul(miningPower).div(offset);
        _tokens += calculatedMny;
        mnyLeftInCurrent -= calculatedMny;
        miningTokenLeftInCurrent -= miningPower;
        miningPower = 0;
      }

      if (miningTokenLeftInCurrent == 0) {
        if (currentTier == lastTier) {
          _tokens = SWAP_CAP - cycleMintSupply;
          if (miningPower > 0) {
            uint refund = miningPower.div(exchangeRatios[_token].div(baseRate));
            unPaidFees[_token] -= refund.div(2);
            ERC20(_token).transfer(msg.sender, refund);
          }
          // Open swap
          _startSwap();
          break;
        }
        currentTier++;
        (mnyLeftInCurrent, miningTokenLeftInCurrent, currentRate) = tierContract.getTier(currentTier);
      }
    }
    cycleMintSupply += _tokens;
    MintableToken(this).mint(msg.sender, _tokens);
  }

  // swap data
  bool public swapOpen = false;
  uint public swapEndTime;
  uint[] public holdings;
  mapping(address => uint) public swapRates;

  function _startSwap() private {
    swapEndTime = now + 30 days;
    swapOpen = true;
    isMiningOpen = false;
    miningActive = false;
    delete holdings;

    //set swap rates
    for (uint16 i = 0; i < miningTokens.length; i++) {
      address _token = miningTokens[i];
      uint swapAmt = ERC20(_token).balanceOf(this) - unPaidFees[_token];
      holdings.push(swapAmt);
    }
    for (uint16 j = 0; j < miningTokens.length; j++) {
      address token = miningTokens[j];
      swapRates[token] = holdings[j].div(SWAP_CAP.div(decimalOffset));
    }
    emit SwapStarted(swapEndTime);
  }

  function swap(uint amt) public {
    require(swapOpen && cycleMintSupply > 0);
    if (amt > cycleMintSupply) {
      amt = cycleMintSupply;
    }
    cycleMintSupply -= amt;
    // burn verifies msg.sender has balance
    burn(amt);
    for (uint16 i = 0; i < miningTokens.length; i++) {
      address _token = miningTokens[i];
      ERC20(_token).transfer(msg.sender, amt.mul(swapRates[_token]).div(decimalOffset));
    }
  }

  function restart() public {
    require(swapOpen);
    require(now > swapEndTime || cycleMintSupply == 0);
    cycleMintSupply = 0;
    swapOpen = false;
    swapEndTime = 0;
    isMiningOpen = true;

    // 20% penalty for unswapped tokens
    for (uint16 i = 0; i < miningTokens.length; i++) {
      address _token = miningTokens[i];
      uint amtLeft = ERC20(_token).balanceOf(this) - unPaidFees[_token];
      unPaidFees[_token] += amtLeft.div(5);
    }

    currentTier = 1;
    mnyLeftInCurrent = 6.597 ether;
    miningTokenLeftInCurrent = 0.0369 ether;
    currentRate = mnyLeftInCurrent.div(miningTokenLeftInCurrent.div(offset));
    emit MiningRestart(currentTier);
  }

  function setIsMiningOpen(bool isOpen) onlyAdmin public {
    isMiningOpen = isOpen;
  }

  // base rate is 1 ether, so for 1 to 1 send in 1 ether (toWei)
  function addMiningToken(address tokenAddr, uint ratio) onlyAdmin public {
    exchangeRatios[tokenAddr] = ratio;
    miningTokens.push(tokenAddr);
    unPaidFees[tokenAddr] = 0;
  }

  // can only add/change tier contract in between mining cycles
  function setMnyTiers(address _tiersAddr) onlyAdmin public {
    require(!miningActive);
    tierContract = MNYTiers(_tiersAddr);
  }

  // this allows us to use a different set of tiers
  // can only be changed in between mining cycles by admin
  function setLastTier(uint16 _lastTier) onlyAdmin public {
    require(swapOpen);
    lastTier = _lastTier;
  }

  // Addresses for fees.
  address public foundation = 0xab78275600E01Da6Ab7b5a4db7917d987FdB1b6d;
  address public creator = 0xab78275600E01Da6Ab7b5a4db7917d987FdB1b6d;
  address public dev = 0xab78275600E01Da6Ab7b5a4db7917d987FdB1b6d;
  address public origDev = 0xab78275600E01Da6Ab7b5a4db7917d987FdB1b6d;

  function payFees() public {
    for (uint16 i = 0; i < miningTokens.length; i++) {
      address _token = miningTokens[i];
      uint fees = unPaidFees[_token];
      ERC20(_token).transfer(foundation, fees.div(5).mul(2));
      ERC20(_token).transfer(dev, fees.div(5));
      ERC20(_token).transfer(origDev, fees.div(5));
      ERC20(_token).transfer(creator, fees.div(5));
      unPaidFees[_token] = 0;
    }
  }

  function changeFoundation (address _receiver) public {
    require(msg.sender == foundation);
    foundation = _receiver;
  }

  function changeCreator (address _receiver) public {
    require(msg.sender == creator);
    creator = _receiver;
  }

  function changeDev (address _receiver) public {
    require(msg.sender == dev);
    dev = _receiver;
  }

  function changeOrigDev (address _receiver) public {
    require(msg.sender == origDev);
    origDev = _receiver;
  }
}
function mine(address token, uint amount) canMine public {
  require(token != 0 && amount > 0);
  require(exchangeRatios[token] > 0 && cycleMintSupply < SWAP_CAP);
  require(ERC20(token).transferFrom(msg.sender, this, amount));
  _mine(token, amount);
}

// first call Token(address).approve(mny address, amount) for MNY to transfer on your behalf.