contract LoanProcess is Permissions{
    using SafeMath for uint256;

    
    uint256 public version = 11;
    uint256 public decimal = 18;
    string public CURRENCY = 'USD';
    uint256 public SECPYEAR = 31536000;
    
    uint256 constant RAT_TYPE_DOCUMENT = 1;
    uint256 constant RAT_TYPE_CONTRACT = 2;
    uint256 constant RAT_TYPE_GUARANTOR = 3;
    
    RatToken public ratToken; 
    CATToken public catToken;
    LoanKYC  public loanKYC;
    LoanContractDB public contractDB;
    LoanDocDB public docDB;
    GuarantorDB public guaDB;
    S1Global public s1Global;


    event CreateNewDocument(
        address indexed owner,
        uint256 indexed docType,
        uint256 indexed docID
    );

    event CreateNewContract(
        address indexed owner,
        uint256 indexed docID,
        uint256 indexed contractID,
        uint256 amount
    );

    event CreateNewGuarantor(
        address indexed owner,
        uint256 amount,
        uint256 commision,
        uint256 guaID
    );

    function setS1Global(address _addr) public onlyAdmin returns (bool){
        s1Global = S1Global(_addr);
        catToken = CATToken(s1Global.getAddressLabel("cattoken"));
        ratToken = RatToken(s1Global.getAddressLabel("rattoken"));//RatToken(s1Global.ratTokenAddr());
        loanKYC  = LoanKYC(s1Global.getAddressLabel("loankyc"));//LoanKYC(s1Global.kycAddr());
        contractDB = LoanContractDB(s1Global.getAddressLabel("contractdb"));//LoanContractDB(s1Global.contractDB());
        docDB = LoanDocDB(s1Global.getAddressLabel("docdb"));//LoanDocDB(s1Global.docDB());
        if(s1Global.getAddressLabel("guarantorDB") != address(0))
            guaDB = GuarantorDB(s1Global.getAddressLabel("guarantorDB"));//GuarantorDB(s1Global.guarantorDB());
    }

    function isValidToken(uint256 _tokenID) public view returns (bool){
        return ratToken.isValidToken(_tokenID);
    }

    function isValidDoc(uint256 _docID) public view returns (bool){
       return docDB.isValidDoc(_docID);
    }

    function isValidContract(uint256 _contractID) public view returns (bool){
       return contractDB.isValidContract(_contractID);
    }


    function stringToBytes8(string memory source) public pure returns (bytes8 result) {
        bytes memory tempEmptyStringTest = bytes(source);
        if (tempEmptyStringTest.length == 0) {
            return 0x0;
        }

        assembly {
            result := mload(add(source, 32))
        }
    }
    
    constructor() public{
        // s1Global = S1Global(0x47A9F0145F5A26a3e4f728CC617Cd1E63030C89b);
        // catToken = CATToken(0x89E5e3bA2576e0E2904601b1c628B79f61F80951);
        // ratToken = RatToken(0xbEEcb77563Cc389A90dbceeD7069f468cD2cb409);//RatToken(s1Global.ratTokenAddr());
        // loanKYC  = LoanKYC(0x8708E12a8c4ddF1bA3130Fe5D5dBD935a587ED16);//LoanKYC(s1Global.kycAddr());
        // contractDB = LoanContractDB(0x4B96140787F2Cdb3D195Be7B04c4e603E30719db);//LoanContractDB(s1Global.contractDB());
        // docDB = LoanDocDB(0xC739eff3f417Da286bEa921e634eb1e9f3De47F7);//LoanDocDB(s1Global.docDB());
        // guaDB = GuarantorDB(0xA00dd623408f569642E3cc1296272231CFA8ECD2);
    }

    function mintGuarantorToken(uint256 _tokenID,uint256 _amount,address _guaAddr,address _lockAddr,uint256 _commission) public onlyPermits returns(bool){
        require(address(ratToken) != address(0),"Not set Rat token");
        require(ratToken.isValidToken(_tokenID) == false,"ERROR:mintGuarantorToken invalid tokenID");
     

        require(catToken.balanceOf(_guaAddr) >= _amount,"Insufficial CAT Token for Guarantor");
        if(catToken.intTransfer(_guaAddr,_lockAddr,_amount) == true){
          guaDB.createGuarantorData(_tokenID,_lockAddr,_amount,_commission);
          ratToken.mintToken(_guaAddr,_tokenID,_tokenID,RAT_TYPE_GUARANTOR,address(guaDB));

          emit CreateNewGuarantor(_guaAddr,_amount,_commission,_tokenID);

          return true;
        }

        return false;
    }
    // amount in USD
    function mintDocToken(uint256 _tokenID,uint256 _docID,uint256 _docType,address _owner,string memory _ipfs) public onlyPermits returns(bool){
        require(address(ratToken) != address(0),"Not set Rat token");
        require(ratToken.isValidToken(_tokenID) == false,"ERROR:mintDoc invalid tokenID");
        require(docDB.isValidDoc(_docID) == false,"ERROR:mintDoc invalid docID");
        
        //_createNewDocument(_docID,_docType,_owner);
        docDB.createNewDocument(_docID,_docType,_ipfs,_owner,CURRENCY);
        ratToken.mintToken(_owner,_tokenID,_docID,RAT_TYPE_DOCUMENT,address(docDB));
        docDB.setDocID2Token(_tokenID,_docID);
        emit CreateNewDocument(_owner,_docType,_docID);
     
        return true;
    }

    function mintLoanToken(uint256 _tokenID,uint256 _docID,uint256 _contractID,address _borrow,uint256 _amount,uint256 _intCom,uint256 _intLen,uint256 _intGua,string memory _currency) public onlyPermits returns(bool){
        require(address(ratToken) != address(0),"Not set RatTokenAddr");
        require(ratToken.isValidToken(_tokenID) == false,"ERROR:mintcontract invalid tokenID");
        require(contractDB.isValidContract(_contractID) == false,"ERROR:mintContract invalud contractID");
        
        contractDB.createLoanContract(_docID,_contractID,_amount,_borrow,_intCom,_intLen,_intGua,_currency);
        ratToken.mintToken(_borrow,_tokenID,_contractID,RAT_TYPE_CONTRACT,address(contractDB));
        contractDB.setConID2Token(_tokenID,_contractID);

        emit CreateNewContract(_borrow,_docID,_contractID,_amount);
        mintCATToken(_tokenID);
        return true;
    }

    
    function setLegalApprove(address _legal,uint256 _tokenID) public onlyPermits returns(bool){
        require(ratToken.isValidToken(_tokenID) == true,"ERROR: setLegalApprove Invalid Token ID");
        uint256 tokenType;
        uint256 docID;
        address conAddr;
        LoanDocDB  _docDB;    
        (tokenType,docID,conAddr) = ratToken.getRatDetail(_tokenID);
        require(tokenType == RAT_TYPE_DOCUMENT,"This token not document Token");
        
        _docDB = LoanDocDB(conAddr);
        
         
        require(loanKYC.legalKYC(_legal) == true,"This Legal Address not KYC");
        require(loanKYC.legalBlackList(_legal) == false,"This Legal has Blacklist");
        //_SetLegalApprove(_legal,_docID);
        _docDB.legalSignDoc(docID,_legal);
        return true;
    }
    
    function setAuditApproveAndCredit(address _audit,uint256 _tokenID,uint256 _credit,uint256 _score) public onlyPermits returns(bool){
        require(ratToken.isValidToken(_tokenID) == true,"ERROR: setAuditApproveAndCredit Invalid Token ID");
        uint256 tokenType;
        uint256 docID;
        address conAddr;
        LoanDocDB  _docDB;    
        (tokenType,docID,conAddr) = ratToken.getRatDetail(_tokenID);
        require(tokenType == RAT_TYPE_DOCUMENT,"This token not document Token");
        
        _docDB = LoanDocDB(conAddr);
        
        
        require(loanKYC.auditKYC(_audit) == true,"Audit not kyc");
        require(loanKYC.auditBlackList(_audit) == false,"Audit Black list");
        _docDB.auditSignDocAndCredit(docID,_audit,_credit,_score);
        return true;
        
    }
    
    
    function cLevelApprove(uint256 _tokenID,uint256 _credit,uint256 _score) external onlyAdmin returns(bool){
        require(ratToken.isValidToken(_tokenID) == true,"ERROR: cLevelApprove Invalid Token ID");
        uint256 tokenType;
        uint256 docID;
        address conAddr;
        LoanDocDB  _docDB;    
        (tokenType,docID,conAddr) = ratToken.getRatDetail(_tokenID);
        require(tokenType == RAT_TYPE_DOCUMENT,"This token not document Token");
        
        _docDB = LoanDocDB(conAddr);
        
        _docDB.legalSignDoc(docID,msg.sender);
        _docDB.auditSignDocAndCredit(docID,msg.sender,_credit,_score);
       // _setDocumentActive(_docID);
        
        return true;
    }
    
    
    function defaultContract(uint256 _tokenID) public onlyPermits returns(bool){
        
        require(ratToken.isValidToken(_tokenID) == true,"ERROR: cLevelApprove Invalid Token ID");
        uint256 tokenType;
        uint256 conID;

        address conAddr;
        LoanContractDB  _conDB;    
        (tokenType,conID,conAddr) = ratToken.getRatDetail(_tokenID);
        require(tokenType == RAT_TYPE_CONTRACT,"This token not contract Token");
        
        _conDB = LoanContractDB(conAddr);
        uint256[] memory data;
        address borrowAddr;
        address leanAddr;
        data = new uint256[](4);


        // (loanAmount,paidAmount,commision,GuaTokenID,borrowAddr,leanAddr) = contractDB.getContractInfo(_conID);
        (data[0],data[1],data[2],data[3],borrowAddr,leanAddr,) = _conDB.getContractInfo(conID);

        //check everything first
        uint256  defaultAmount = data[0] - data[1];  // loan - paid;
        if(data[3] > 0){ // GuaToken ID > 0
            
            if(guaDB.payDefault(data[3],defaultAmount,leanAddr) == true){
                _conDB.defaultContract(conID,0);
            }
        }
        else
            _conDB.defaultContract(conID,defaultAmount);

        return true;
    }


    function _paidContract(uint256 _conID,uint256 _amount,address _tokenAddr,address _from) internal returns(bool){
        require(catToken.allowDeposit(_tokenAddr) == true,"This token not allow to paid");
        // pay back to pools first
        uint256[] memory data;
        POOLS pools;
        address  poolsAddr;
        uint256 sumInt;
        uint256 principlePaid;
        bool endContract;
        data = new uint256[](5);

        (data,poolsAddr) = contractDB.getPaidInfo(_conID);
        pools = POOLS(poolsAddr);
        sumInt = data[1] + data[2] + data[3];
        if(_amount <  sumInt){
            return false;
        }
        else
        {
           
                 principlePaid = _amount - sumInt;
                 if(principlePaid > data[0]){
                     principlePaid = data[0];    
                     endContract = true;
                }

                pools.rePaymentWithWrap(principlePaid + data[2],data[4],_from); // pay to pool
                //mint interest for cat token
                catToken.mintFromWarpToken(_tokenAddr,data[1],_from);
                catToken.burnToken(_from,principlePaid + data[1]);
                contractDB.updatePaidContract(_conID,principlePaid,sumInt);
                
        }

    } 

    //Borrow can do it by them self
    function paidContract(uint256 _conID,uint256 _amount,address _tokenAddr,address _from) public onlyPermits returns(bool){
      return _paidContract(_conID,_amount,_tokenAddr,_from);

    }

    function mintCATToken(uint256 _tokenID) public onlyPermits returns(string memory){
        return catToken.mintFromRATToken(_tokenID);
    }
    
    function getLoanInterest(uint256 _contractID) public view returns(uint256){
        uint256 _lend;
        (,_lend,) = contractDB.loanInterest(_contractID);
        return _lend;
    }
    
    function activeContract(uint256 _contractID,uint256 _termpay,uint256 expirationTime,address lender) public onlyPermits returns(string memory result){

        require(lender != address(0),"No Pools");
        address borrow = contractDB.getBorrowAddr(_contractID);
        uint256 tokenID = contractDB.conIDToToken(_contractID);
        uint256 loanAmount;
       // uint256 poolInt;
        POOLS  pools;

        loanAmount = contractDB.getLoanAmount(_contractID);

        pools = POOLS(lender);
        // check interest pools
        require(getLoanInterest(_contractID) >= pools.borrowInterest(),"Lender interest not more then pools");

        if(pools.loanBalance() < loanAmount)
            return "lender not enoungh Stable Coin";
       
            
        
        ratToken.intTransfer(borrow,lender,tokenID);
        uint256 lenderID = pools.borrowWithAddr(loanAmount,borrow);
        if(contractDB.activeContract(_contractID,_termpay,expirationTime,lender,0,1000000000000000000,lenderID) == false)
            return "Active contract ERROR";

        
        return "OK";
        
    }
    


    function loanDocInfo(uint256 _tokenID) public view  returns( uint256[] memory _data,bytes8 _cur,address[] memory _addr,string memory _st,string memory _ipfs){
            require(ratToken.isValidToken(_tokenID) == true,"ERROR: loanDocInfo Invalid Token ID");
            uint256 tokenType;
            uint256 docID;
            address conAddr;
            
            (tokenType,docID,conAddr) = ratToken.getRatDetail(_tokenID);
        
            require(tokenType == RAT_TYPE_DOCUMENT,"ERROR:loanDocInfo This token not Document");
            require(docDB.isValidDoc(docID) == true,"ERROR:loanDocInfo no document in array");
        
            
            return docDB.loanDocDataFromID(docID);
    }
    
    function loanContractInfo(uint256 _tokenID) public view returns(uint256[] memory _data,bytes8 _cur,address[] memory _addr){
        require(ratToken.isValidToken(_tokenID) == true,"ERROR:loanConatractInfo invalid token");
        uint256 tokenType;
        uint256 conID;
        address conAddr;
            
        (tokenType,conID,conAddr) = ratToken.getRatDetail(_tokenID);
            
        require(tokenType == RAT_TYPE_CONTRACT,"ERROR:This token not contract");
        require(contractDB.isValidContract(conID) == true,"ERROR invalid Contract");
    
        return contractDB.loanContractDataFromID(conID);
    }

}
function mintDocToken(uint256 _tokenID,uint256 _docID,uint256 _docType,address _owner,string memory _ipfs) public onlyPermits returns(bool){
    require(address(ratToken) != address(0),"Not set Rat token");
    require(ratToken.isValidToken(_tokenID) == false,"ERROR:mintDoc invalid tokenID");
    require(docDB.isValidDoc(_docID) == false,"ERROR:mintDoc invalid docID");
        
    //_createNewDocument(_docID,_docType,_owner);
    docDB.createNewDocument(_docID,_docType,_ipfs,_owner,CURRENCY);
    ratToken.mintToken(_owner,_tokenID,_docID,RAT_TYPE_DOCUMENT,address(docDB));
    docDB.setDocID2Token(_tokenID,_docID);
    emit CreateNewDocument(_owner,_docType,_docID);
     
    return true;
}

// amount in USD