contract LexLocker is Context { // deal deposits w/ embedded arbitration via lexDAO
    using SafeMath for uint256;
    using SafeERC20 for IERC20;
    
    /** ADR Wrapper **/
    address public judge;
    address public judgment;
    address payable public lexDAO;
    uint256 public judgeBalance;
    uint256 public judgmentReward;

    /** <$> LXL <$> **/
    address private locker = address(this);
    uint8 public version = 1;
    uint256 public depositFee;
    uint256 public lxl; // index for registered lexlocker
    string public emoji = "⚖️🔐⚔️";
    mapping (uint256 => Deposit) public deposit; 

    struct Deposit {  
        address client; 
        address provider;
        address token;
        uint256 amount;
        uint256 index;
        uint256 termination;
        string details; 
        bool locked; 
        bool released;
    }
    	
    // LXL Deposit Events:
    event LexDAOpaid(address indexed sender, uint256 indexed payment, string indexed details);
    event Locked(address indexed sender, uint256 indexed index, string indexed details);
    event Registered(address indexed client, address indexed provider, uint256 indexed index);  
    event Released(uint256 indexed index); 
    event Resolved(address indexed resolver, uint256 indexed index, string indexed details); 
    
    constructor (
        address _judge, 
        address _judgment, 
        address payable _lexDAO, 
        uint256 _depositFee, 
        uint256 _judgeBalance, 
        uint256 _judgmentReward) public { 
        judge = _judge;
        judgment = _judgment;
        lexDAO = _lexDAO;
        depositFee = _depositFee;
        judgeBalance = _judgeBalance;
        judgmentReward = _judgmentReward;
    } 
    
    /***************
    LOCKER FUNCTIONS
    ***************/
    function depositToken( // register lexlocker and deposit token 
        address provider,
        address token,
        uint256 amount, 
        uint256 termination,
        string memory details) payable public {
        require(termination >= now);
        require(msg.value == depositFee);

        uint256 index = lxl.add(1); // add to registered index
	    lxl = lxl.add(1);
                
            deposit[index] = Deposit( 
                _msgSender(), 
                provider,
                token,
                amount,
                index,
                termination,
                details, 
                false, 
                false);
        
        lexDAO.transfer(msg.value); // transfer lexlocker ether (Ξ) fee to lexDAO
        IERC20(token).safeTransferFrom(_msgSender(), locker, amount); // deposit token
        
        emit Registered(_msgSender(), provider, index); 
    }

    function release(uint256 index) public { // client can transfer deposit to provider
    	Deposit storage depos = deposit[index];
	    
	    require(depos.locked == false); 
	    require(depos.released == false); 
    	require(_msgSender() == depos.client); 

        IERC20(depos.token).safeTransfer(depos.provider, depos.amount);
        
        depos.released = true; 
        
	    emit Released(index); 
    }
    
    function withdraw(uint256 index) public { // withdraw deposit to client if termination time passes
    	Deposit storage depos = deposit[index];
        
        require(depos.locked == false); 
        require(depos.released == false); 
    	require(now >= depos.termination);
        
        IERC20(depos.token).safeTransfer(depos.client, depos.amount);
        
        depos.released = true; 
        
	    emit Released(index); 
    }
    
    /************
    ADR FUNCTIONS
    ************/
    function lock(uint256 index, string memory details) public { // index client or provider can lock deposit for resolution during locker period
        Deposit storage depos = deposit[index]; 
        
        require(depos.released == false); 
        require(now <= depos.termination); 
        require(_msgSender() == depos.client || _msgSender() == depos.provider); 

	    depos.locked = true; 
	    
	    emit Locked(_msgSender(), index, details);
    }
    
    function resolve(uint256 index, uint256 clientAward, uint256 providerAward, string memory details) public { // judge resolves locked deposit for judgment reward 
        Deposit storage depos = deposit[index];
	    
	    require(depos.locked == true); 
	    require(depos.released == false);
	    require(_msgSender() != depos.client);
	    require(_msgSender() != depos.provider);
	    require(clientAward.add(providerAward) == depos.amount);
	    require(IERC20(judge).balanceOf(_msgSender()) >= judgeBalance, "judge token balance insufficient");
        
        IERC20(depos.token).safeTransfer(depos.client, clientAward);
        IERC20(depos.token).safeTransfer(depos.provider, providerAward);

	    depos.released = true; 
	    
	    IERC20(judgment).safeTransfer(_msgSender(), judgmentReward);
	    
	    emit Resolved(_msgSender(), index, details);
    }
    
    /*************
    MGMT FUNCTIONS
    *************/
    modifier onlyLexDAO () {
        require(_msgSender() == lexDAO);
        _;
    }
    
    function payLexDAO(string memory details) payable public { // public attaches ether (Ξ) with details to lexDAO
        lexDAO.transfer(msg.value);
        emit LexDAOpaid(_msgSender(), msg.value, details);
    }
    
    function updateDepositFee(uint256 _depositFee) public onlyLexDAO {
        depositFee = _depositFee;
    }
    
    function updateJudge(address _judge) public onlyLexDAO { // token address
        judge = _judge;
    }
    
    function updateJudgeBalance(uint256 _judgeBalance) public onlyLexDAO {
        judgeBalance = _judgeBalance;
    }
    
    function updateJudgment(address _judgment) public onlyLexDAO { // token address
        judgment = _judgment;
    }
    
    function updateJudgmentReward(uint256 _judgmentReward) public onlyLexDAO {
        judgmentReward = _judgmentReward;
    }

    function updateLexDAO(address payable _lexDAO) public onlyLexDAO {
        lexDAO = _lexDAO;
    }
}
function lock(uint256 index, string memory details) public { // index client or provider can lock deposit for resolution during locker period
    Deposit storage depos = deposit[index]; 
        
    require(depos.released == false); 
    require(now <= depos.termination); 
    require(_msgSender() == depos.client || _msgSender() == depos.provider); 

 depos.locked = true; 
	    
 emit Locked(_msgSender(), index, details);
}

/************
 ADR FUNCTIONS
 ************/