// Contract: Link
// Target function: function pledge(address node) override external onlyLuca onlyLinkUser
// 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 {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        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;
    }
    }
}

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);
}

interface IWETH is IERC20{
    function deposit() payable external;
    function withdraw(uint) external;
}

interface Ipledge{
    function  stakeWLuca(address _nodeAddr, uint256 _amount, address _sender) external returns(bool);
    function  cancleStakeWLuca(address _sender) external returns(bool);
}

interface Itrader {
    function payment(address _token, address _from, address _to, uint256 _amount) external returns(bool); 
    function withdrawFor(address _to, uint256 _amount) external;
    function suck(address _to, uint256 _amount, uint256 _lockDay) external;
}

interface Ifactory {
    function linkActive(address _user, uint256 _methodId) external;
}

interface Ifile {
    function luca() external view returns(address);
    function pledger() external view returns(address);
    function collector() external view returns(address);
    function linkLoad() external view returns (address, address, address, address, address);//luca, wluca, weth, trader, pledger
}

//Ilink.sol
interface Ilink {
    function getPledgedInfo() external view returns(bool pledgedA_, bool pledgedB_);
    function getStatus() external view returns(string memory);
    function getLinkInfo() external view returns(string memory symbol_,address token_, address userA_,address userB_, uint256 amountA_,uint256 amountB_,uint256 percentA_,uint256 totalPlan_,uint256 lockDays_, uint256 startTime_, uint256 status_, bool isAward_);
    function getCloseInfo() external view returns(address closer_, uint256 startTime_,uint256 expiredTime_,uint256 closeTime_, bool closeReqA_, bool closeReqB_);
    function getRecevabesInfo() external view returns(uint256 receivableA_, bool isExitA_, uint256 receivableB_, bool isExitB_);
    function setUserB(address _userB) external;
    function agree() external payable;
    function reject() external;
    function close() external;
    function rejectClose() external;
    function repealCloseReq() external;
    function isExpire() external view returns(bool);
    function pledge(address node) external;
    function depledge() external;
    function wtihdrawSelf() external;
}

//link.sol
contract Initialized {
    bool internal initialized;
    
    modifier noInit(){
        require(!initialized, "initialized");
        _;
        initialized = true;
    }
}

contract Enum {
    Status internal status;
    enum Status {INITED, AGREED, CLOSED, PLEDGED}
    enum MethodId {setUserB, agree, reject, pledge, depledge, close, repealCloseReq, rejectClose, wtihdrawSelf}
    
    function _init() internal {status = Status.INITED;}

    function _agree() internal {status = Status.AGREED;}

    function _close() internal {status = Status.CLOSED;}
    
    function _pledge() internal {status = Status.PLEDGED;}
}

contract LinkInfo is Enum {
    address internal file;
    address internal factory;
    bool    internal closeReqA;
    bool    internal closeReqB;
    bool    internal pledgedA;
    bool    internal pledgedB;
    
    string   symbol;
    address  userA;
    address  userB;
    uint256  amountA;
    uint256  amountB;
    uint256  percentA;
    uint256  totalPlan;
    address  token;
    address  closer;
    uint256  lockDays;
    uint256  startTime;
    uint256  expiredTime;
    uint256  closeTime;

    uint256  receivableA;
    uint256  receivableB;
    bool     isExitA;
    bool     isExitB;
    
    
    modifier onlyLuca(){
        require(token == Ifile(file).luca(), "Link: only luca");
        _;
    }
    
    modifier onlyEditLink(){
        require(msg.sender == userA, "Link: only userA");
        require(userB == address(0) && percentA == 100, "Link: only Editable Link");
        _;
    }

    modifier onlyLinkUser(){
        require(msg.sender == userA || msg.sender == userB, "Link: access denied");
        _;
    }

    modifier onlyUserB(){
        require(msg.sender == userB, "Link: noly userB");
        _;
    }

    modifier onlyINITED(){
        require(status == Status.INITED, "Link: only initialized");
        _;
    }

    modifier onlyAGREED(){
        require(status == Status.AGREED, "Link: only agreed");
        _;
    }

    modifier unCLOSED(){
        require(status != Status.CLOSED, "Link: only unclosed");
        _;
    }
    
    modifier onlyPLEDGED(){
        require(status == Status.PLEDGED, "Link: only pledged");
        _;
    }
    
    modifier unPLEDGED(){
        require(status != Status.PLEDGED, "Link: only unpledged");
        _;
    }
}

contract Link is LinkInfo, Initialized, Ilink {
    using SafeMath for uint256;
    address constant ETH = address(0);
    fallback() payable external{}
    receive() payable external{}
    
    function _linkActive(MethodId _methodId) internal{
        Ifactory(factory).linkActive(msg.sender, uint256(_methodId));
    }
    
    function initialize( address _file, address _userA, address _userB, address _token, string memory _symbol, uint256 _amount, uint256 _percentA, uint256 _lockDays) external noInit{
        (factory, file, userA, userB, token, symbol) = (msg.sender, _file, _userA, _userB, _token, _symbol);
        (totalPlan, percentA, amountA, amountB, lockDays) = (_amount, _percentA, _amount.mul(_percentA).div(100), _amount.mul(100 - _percentA).div(100), _lockDays);
        
        if(_percentA == 100 && userB != address(0)){
            startTime = block.timestamp;
            expiredTime = startTime.add(lockDays.mul(1 days));
            _agree();
        }else{
            _init();
        }
    }

    function setUserB(address _userB) override external onlyEditLink {
        require(_userB != address(0) && _userB != msg.sender, "Link: unlawful address");
        _linkActive(MethodId.setUserB);
        userB = _userB;
        startTime = block.timestamp;
        expiredTime = startTime.add(lockDays.mul(1 days));
        _agree();
        
        (address luca,,,address trader,) = Ifile(file).linkLoad();
        //airdrop gta 
        if(token == luca){
            Itrader(trader).suck(userA, amountA, lockDays);
            Itrader(trader).suck(userB, amountB, lockDays);
        }
    }

    function reject() override external onlyUserB onlyINITED{
        _linkActive(MethodId.reject);
        _exit();
    }

    function agree() override payable external onlyUserB onlyINITED{
        _linkActive(MethodId.agree);
        // (luca, wluca, weth, trader, pledger)
        (address luca,,address weth, address trader,) = Ifile(file).linkLoad();
        if (token == ETH){
            
            require(msg.value == amountB, "Link: wrong amount of ETH");
            IWETH(weth).deposit{value: msg.value}();
            IWETH(weth).transfer(address(this), msg.value);
        }else{
            Itrader(trader).payment(token, userB, address(this), amountB);
        }
        
        //require(_verifyDeposit(token, userB), "Link: deposit not enough" );
        startTime = block.timestamp;
        expiredTime = startTime.add(lockDays.mul(1 days));
        _agree();
        
        //airdrop gta 
        if(token == luca){
            Itrader(trader).suck(userA, amountA, lockDays);
            Itrader(trader).suck(userB, amountB, lockDays);
        }
    }