contract Lockable is Context {

  using SafeMath for uint;

  struct TimeLock {
    uint amount;
    uint expiresAt;
  }

  struct InvestorLock {
    uint amount;
    uint months;
    uint startsAt;
  }

  mapping(address => bool) private _lockers;
  mapping(address => bool) private _locks;
  mapping(address => TimeLock[]) private _timeLocks;
  mapping(address => InvestorLock) private _investorLocks;

  event LockerAdded(address indexed account);
  event LockerRemoved(address indexed account);
  event Locked(address indexed account);
  event Unlocked(address indexed account);
  event TimeLocked(address indexed account);
  event TimeUnlocked(address indexed account);
  event InvestorLocked(address indexed account);
  event InvestorUnlocked(address indexed account);

  /**
    * @dev Throws if called by any account other than the locker.
    */
  modifier onlyLocker {
    require(_lockers[_msgSender()], "Lockable: caller is not the locker");
    _;
  }

  /**
    * @dev Returns whether the address is locker.
    */
  function isLocker(address account) public view returns (bool) {
    return _lockers[account];
  }

  /**
    * @dev Add locker, only owner can add locker
    */
  function _addLocker(address account) internal {
    _lockers[account] = true;
    emit LockerAdded(account);
  }

  /**
    * @dev Remove locker, only owner can remove locker
    */
  function _removeLocker(address account) internal {
    _lockers[account] = false;
    emit LockerRemoved(account);
  }

  /**
    * @dev Returns whether the address is locked.
    */
  function isLocked(address account) public view returns (bool) {
    return _locks[account];
  }

  /**
    * @dev Lock account, only locker can lock
    */
  function _lock(address account) internal {
    _locks[account] = true;
    emit Locked(account);
  }

  /**
    * @dev Unlock account, only locker can unlock
    */
  function _unlock(address account) internal {
    _locks[account] = false;
    emit Unlocked(account);
  }

  /**
    * @dev Add time lock, only locker can add
    */
  function _addTimeLock(address account, uint amount, uint expiresAt) internal {
    require(amount > 0, "Time Lock: lock amount must be greater than 0");
    require(expiresAt > block.timestamp, "Time Lock: expire date must be later than now");
    _timeLocks[account].push(TimeLock(amount, expiresAt));
    emit TimeLocked(account);
  }

  /**
    * @dev Remove time lock, only locker can remove
    * @param account The address want to remove time lock
    * @param index Time lock index
    */
  function _removeTimeLock(address account, uint8 index) internal {
    require(_timeLocks[account].length > index && index >= 0, "Time Lock: index must be valid");

    uint len = _timeLocks[account].length;
    if (len - 1 != index) { // if it is not last item, swap it
      _timeLocks[account][index] = _timeLocks[account][len - 1];
    }
    _timeLocks[account].pop();
    emit TimeUnlocked(account);
  }

  /**
    * @dev Get time lock array length
    * @param account The address want to know the time lock length.
    * @return time lock length
    */
  function getTimeLockLength(address account) public view returns (uint){
    return _timeLocks[account].length;
  }

  /**
    * @dev Get time lock info
    * @param account The address want to know the time lock state.
    * @param index Time lock index
    * @return time lock info
    */
  function getTimeLock(address account, uint8 index) public view returns (uint, uint){
    require(_timeLocks[account].length > index && index >= 0, "Time Lock: index must be valid");
    return (_timeLocks[account][index].amount, _timeLocks[account][index].expiresAt);
  }

  /**
    * @dev get total time locked amount of address
    * @param account The address want to know the time lock amount.
    * @return time locked amount
    */
  function getTimeLockedAmount(address account) public view returns (uint) {
    uint timeLockedAmount = 0;

    uint len = _timeLocks[account].length;
    for (uint i = 0; i < len; i++) {
      if (block.timestamp < _timeLocks[account][i].expiresAt) {
        timeLockedAmount = timeLockedAmount.add(_timeLocks[account][i].amount);
      }
    }
    return timeLockedAmount;
  }

  /**
    * @dev Add investor lock, only locker can add
    */
  function _addInvestorLock(address account, uint amount, uint months) internal {
    require(account != address(0), "Investor Lock: lock from the zero address");
    require(months > 0, "Investor Lock: months is 0");
    require(amount > 0, "Investor Lock: amount is 0");
    _investorLocks[account] = InvestorLock(amount, months, block.timestamp);
    emit InvestorLocked(account);
  }

  /**
    * @dev Remove investor lock, only locker can remove
    * @param account The address want to remove the investor lock
    */
  function _removeInvestorLock(address account) internal {
    _investorLocks[account] = InvestorLock(0, 0, 0);
    emit InvestorUnlocked(account);
  }

   /**
    * @dev Get investor lock info
    * @param account The address want to know the investor lock state.
    * @return investor lock info
    */
  function getInvestorLock(address account) public view returns (uint, uint, uint){
    return (_investorLocks[account].amount, _investorLocks[account].months, _investorLocks[account].startsAt);
  }

  /**
    * @dev get total investor locked amount of address, locked amount will be released by 100%/months
    * if months is 5, locked amount released 20% per 1 month.
    * @param account The address want to know the investor lock amount.
    * @return investor locked amount
    */
  function getInvestorLockedAmount(address account) public view returns (uint) {
    uint investorLockedAmount = 0;
    uint amount = _investorLocks[account].amount;
    if (amount > 0) {
      uint months = _investorLocks[account].months;
      uint startsAt = _investorLocks[account].startsAt;
      uint expiresAt = startsAt.add(months*(31 days));
      uint timestamp = block.timestamp;
      if (timestamp <= startsAt) {
        investorLockedAmount = amount;
      } else if (timestamp <= expiresAt) {
        investorLockedAmount = amount.mul(expiresAt.sub(timestamp).div(31 days).add(1)).div(months);
      }
    }
    return investorLockedAmount;
  }
}
/**
 * @dev Contract for locking mechanism.
 * Locker can add and remove locked account.
 * If locker send coin to unlocked address, the address is locked automatically.
 */
function getInvestorLockedAmount(address account) public view returns (uint) {
  uint investorLockedAmount = 0;
  uint amount = _investorLocks[account].amount;
  if (amount > 0) {
    uint months = _investorLocks[account].months;
    uint startsAt = _investorLocks[account].startsAt;
    uint expiresAt = startsAt.add(months*(31 days));
    uint timestamp = block.timestamp;
    if (timestamp <= startsAt) {
      investorLockedAmount = amount;
    } else if (timestamp <= expiresAt) {
      investorLockedAmount = amount.mul(expiresAt.sub(timestamp).div(31 days).add(1)).div(months);
    }
  }
  return investorLockedAmount;
}

/**
 * @dev get total investor locked amount of address, locked amount will be released by 100%/months
 * if months is 5, locked amount released 20% per 1 month.
 * @param account The address want to know the investor lock amount.
 * @return investor locked amount
 */