contract Lockable is Context {
    struct TimeLock {
        uint256 amount;
        uint256 expiresAt;
    }

    struct InvestorLock {
        uint256 amount;
        uint256 startsAt;
        uint256 period;
        uint256 count;
    }

    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,
        uint256 amount,
        uint256 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");

        uint256 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 (uint256) {
        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 (uint256, uint256) {
        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 (uint256) {
        uint256 timeLockedAmount = 0;

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

    /**
     * @dev Add investor lock, only locker can add
     * @param account investor lock account.
     * @param amount investor lock amount.
     * @param startsAt investor lock release start date.
     * @param period investor lock period.
     * @param count investor lock count. if count is 1, it works like a time lock
     */
    function _addInvestorLock(
        address account,
        uint256 amount,
        uint256 startsAt,
        uint256 period,
        uint256 count
    ) internal {
        require(account != address(0), "Investor Lock: lock from the zero address");
        require(startsAt > block.timestamp, "Investor Lock: must set after now");
        require(amount > 0, "Investor Lock: amount is 0");
        require(period > 0, "Investor Lock: period is 0");
        require(count > 0, "Investor Lock: count is 0");
        _investorLocks[account] = InvestorLock(amount, startsAt, period, count);
        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, 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 (
            uint256,
            uint256,
            uint256,
            uint256
        )
    {
        return (_investorLocks[account].amount, _investorLocks[account].startsAt, _investorLocks[account].period, _investorLocks[account].count);
    }

    /**
     * @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 (uint256) {
        uint256 investorLockedAmount = 0;
        uint256 amount = _investorLocks[account].amount;
        if (amount > 0) {
            uint256 startsAt = _investorLocks[account].startsAt;
            uint256 period = _investorLocks[account].period;
            uint256 count = _investorLocks[account].count;
            uint256 expiresAt = startsAt + period * (count - 1);
            uint256 timestamp = block.timestamp;
            if (timestamp < startsAt) {
                investorLockedAmount = amount;
            } else if (timestamp < expiresAt) {
                investorLockedAmount = (amount * ((expiresAt - timestamp) / period + 1)) / count;
            }
        }
        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 isLocker(address account) public view returns (bool) {
    return _lockers[account];
}

/**
 * @dev Returns whether the address is locker.
 */