// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

library SafeMath {
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}

library EnumerableSet {
    struct Set {
        bytes32[] _values;
        mapping(bytes32 => uint256) _indexes;
    }
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        uint256 valueIndex = set._indexes[value];
        if (valueIndex != 0) {
            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;
            if (lastIndex != toDeleteIndex) {
                bytes32 lastvalue = set._values[lastIndex];
                set._values[toDeleteIndex] = lastvalue;
                set._indexes[lastvalue] = valueIndex;
            }
            set._values.pop();
            delete set._indexes[value];
            return true;
        } else {
            return false;
        }
    }
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }
    struct Bytes32Set {
        Set _inner;
    }
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        return _values(set._inner);
    }
    struct AddressSet {
        Set _inner;
    }
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;
        assembly {
            result := store
        }
        return result;
    }
    struct UintSet {
        Set _inner;
    }
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;
        assembly {
            result := store
        }
        return result;
    }
}

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

library Address {
    function isContract(address account) internal view returns (bool) {
        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            if (returndata.length > 0) {
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

library SafeERC20 {
    using Address for address;
    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }
    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }
    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }
    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }
    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

abstract contract Ownable is Context {
    address private _owner;
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    constructor() {
        _setOwner(_msgSender());
    }
    function owner() public view virtual returns (address) {
        return _owner;
    }
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }
    function renounceOwnership() public virtual onlyOwner {
        _setOwner(address(0));
    }
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _setOwner(newOwner);
    }
    function _setOwner(address newOwner) private {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

abstract contract ReentrancyGuard {
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;
    uint256 private _status;
    constructor() {
        _status = _NOT_ENTERED;
    }
    modifier nonReentrant() {
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
        _status = _ENTERED;
        _;
        _status = _NOT_ENTERED;
    }
}

contract TokenLocker is Ownable {
  using SafeMath for uint256;
  using SafeERC20 for IERC20;
  using EnumerableSet for EnumerableSet.UintSet;

  struct TokenLock {
    address token;
    address owner;
    uint256 tokenAmount;
    uint256 lockedTime;
    uint256 unlockTime;
  }

  uint256 public lockNonce = 0;
  uint256 public plusPercent = 100;
  address public csdToken;
  bool private pause = false;
  uint256 private ownerDepositAmount = 0;

  mapping(uint256 => TokenLock) public tokenLocks;

  mapping(address => EnumerableSet.UintSet) private userLocks;

  event OnTokenLock(
    uint256 indexed lockId,
    address indexed tokenAddress,
    address indexed owner,
    uint256 amount,
    uint256 lockedTime,
    uint256 unlockTime
  );

  event OnTokenUnlock(uint256 indexed lockId);
  event OnLockWithdrawal(
    uint256 indexed lockId,
    address indexed tokenAddress,
    address indexed owner,
    uint256 amount,
    uint256 refundAmount,
    uint256 lockedTime,
    uint256 unlockTime,
    uint256 realUnlockTime
  );
  event OnEmegencyTokenUnlock(
    uint256 indexed lockId,
    address indexed tokenAddress,
    address indexed owner,
    uint256 amount,
    uint256 lockedTime,
    uint256 unlockTime,
    uint256 realUnlockTime
  );
  event OnWithdrawFee(uint256 amount);
  event OnDepositForFee(uint256 amount);

  modifier onlyLockOwner(uint lockId) {
    TokenLock storage lock = tokenLocks[lockId];
    require(lock.owner == address(msg.sender), "NO ACTIVE LOCK OR NOT OWNER");
    _;
  }

  constructor(address _token, uint256 _percent, bool _pause) {
      csdToken = _token;
      plusPercent = _percent;
      pause = _pause;
  }

  function lockTokens(uint256 amount, uint256 lockDays) public returns (uint256 lockId) {
    require(pause != true, "Pause Locker");
    require(amount > 0, "ZERO AMOUNT");
    require(lockDays >= 90, "INVALID UNLOCK DAYS, MUST BE OVER 90 DAYS");
    uint256 unlockTime = block.timestamp + lockDays.mul(86400);
    TokenLock memory lock = TokenLock({
        token: csdToken,
        owner: msg.sender,
        tokenAmount: amount,
        lockedTime: block.timestamp,
        unlockTime: unlockTime
    });

    lockId = lockNonce++;
    tokenLocks[lockId] = lock;
    
    userLocks[msg.sender].add(lockId);

    IERC20(csdToken).safeTransferFrom(msg.sender, address(this), amount);
    emit OnTokenLock(lockId, csdToken, msg.sender, amount, block.timestamp, unlockTime);
    return lockId;
  }

  function withdraw(uint256 lockId) public onlyLockOwner(lockId) {
    TokenLock storage lock = tokenLocks[lockId];
    require(block.timestamp >= lock.unlockTime, "NOT YET UNLOCKED");
    uint256 duration = (block.timestamp - lock.lockedTime).div(86400);
    uint256 fee = lock.tokenAmount.div(plusPercent).mul(duration);
    uint256 refundAmount = lock.tokenAmount + fee;
    require(ownerDepositAmount >= fee, "NOT ENOUGH TOKENS FOR FEE");
    IERC20(lock.token).safeTransfer(lock.owner, refundAmount);
    ownerDepositAmount = ownerDepositAmount.sub(fee);
    userLocks[lock.owner].remove(lockId);
    delete tokenLocks[lockId];
    emit OnTokenUnlock(lockId);
    emit OnLockWithdrawal(lockId, lock.token, lock.owner, lock.tokenAmount, refundAmount, lock.lockedTime, lock.unlockTime, block.timestamp);
  }

  function emegencyWithdraw(uint256 lockId) public onlyOwner {
    TokenLock storage lock = tokenLocks[lockId];
    IERC20(lock.token).safeTransfer(lock.owner, lock.tokenAmount);
    userLocks[lock.owner].remove(lockId);
    delete tokenLocks[lockId];
    emit OnTokenUnlock(lockId);
    emit OnEmegencyTokenUnlock(lockId, lock.token, lock.owner, lock.tokenAmount, lock.lockedTime, lock.unlockTime, block.timestamp);
  }

  function withdrawFee(uint256 _amount) public onlyOwner {
    require(ownerDepositAmount >= _amount, "Not enough token funds in the wallet for withdraw");
    IERC20(csdToken).safeTransfer(msg.sender, _amount);
    ownerDepositAmount = ownerDepositAmount.sub(_amount);
    emit OnWithdrawFee(_amount);
  }

  function depositForFee(uint256 _amount) public onlyOwner {
    require(IERC20(csdToken).allowance(msg.sender, address(this)) >= _amount, "BEP20: transfer amount exceeds allowance");
    require(IERC20(csdToken).balanceOf(msg.sender) >= _amount, "Not enough token funds in the wallet");
    IERC20(csdToken).transferFrom(msg.sender, address(this), _amount);
    ownerDepositAmount = ownerDepositAmount.add(_amount);
    emit OnDepositForFee(_amount);
  }

  function userLocksLength(address user) public view returns (uint256) {
    return userLocks[user].length();
  }

  function userLockAt(address user, uint256 index) public view returns (uint256) {
    return userLocks[user].at(index);
  }

  function setPlusPercent(uint256 _percent) public onlyOwner {
    plusPercent = _percent;
  }

  function setTokenAddress(address _token) public onlyOwner {
    csdToken = _token;
  }
  
  function getPause() external view returns (bool) {
     return pause;
  }
  
  function setPause(bool _pause) public onlyOwner {
     pause = _pause;
  }

  function getWithdrawbleAmount() external view returns (uint256) {
     return ownerDepositAmount;
  }
}