contract CiProtocol is ERC20, CiStatistics{

    //*************************************************************************
    //
    // All Ci's protocol activity and data is totally transparent and public. It's watched by the Ci's Proctor Oracle server. 
    // Any App, DApp, Ex or DEX can operate it freely because 
    //
    // Ci's token price depends on the UNIX Time-stamp of GMT (Greenwich Mean Time) or Coordinated Universal Time or UTC+0. 
    // It is shielded and protected against whales, robots or AI manipulation.
    // Anyone can calculate the price of Ci at any moment in time. (see Ci's website DOCS or DAPI for more info)
    
    //
    // Continuousindex.org centralized server and site could disappear, be hacked or censored by a government but 
    // the Ethereum Ci's protocol contract can be operate in a fully decentralized manner with Metamask from the public IPFS site via continuousindex.crypto 
    // or
    // downloading original Ci-DApp files from the PPS (Protected Public Source) Ci-DApp: https://github.com/ContinuousIndex 
    // and operate it decentralized with Metamask from a local host, uploading to IPFS net or Sia-SkyNet or whatever.
    
    //
    // Neither the Ci's contract nor the Ci's website collects personal data of any kind. 
    // Ci has no centralized databases of any kind anywhere. // All data (address, balance, etc..) is managed by the smart contract.     
    // For each Ci operation, the hash, price, quantity and time are recorded in the input of blockchain public transaction.

    
    //
    // Enjoy a new step in finance freedom!
    //
    // Ci - The Continuous Estable Coin protocol - 2021 - https://continuousindex.org -  continuousindex.crypto
    //
    
    //*************************************************************************
    
	//The Ci TimeStamp Initial Date ///////////////////////////////////////////////////////
    uint public CiInitDate = 1642888930; //Sat Jan 22 2022 22:02:10 GMT+0000 //22:02:10 22/01/2022	

	//Compound Interest of Ci will always be greater if it change ////////////////////////
    uint public CiPercent; 
	function setCiPercent(uint256 a)  external proctored {  require( a > CiPercent); CiPercent = a; }


    event ProctorCi_event( address indexed Buyer, uint WeiPayed, uint CiPrice, address indexed AgentWallet, string Ci_USD_Price, bytes32 indexed PayHash ); 
    bool locked_ProctorCi = false; 
    function ProctorCi(address Buyer, uint WeiPayed, uint CiPrice, address payable AgentWallet, string calldata Ci_USD_Price, bytes32 PayHash) external proctored {
        require(!locked_ProctorCi); locked_ProctorCi = true;
        require(CiPrice > 0);
        uint TotalCiToEmit = WeiPayed.div(CiPrice);
        require(WeiPayed >= minETHToGet);
        address payable Seller;
        uint    SellerAllowance;
        uint    emitedToBuyer = 0;
        uint    remainToEmit = TotalCiToEmit;
        while( remainToEmit > 0 ){
            if(getLowerAllowanceDate() != 0){
                Seller = getLowerAllowanceDateAddress();
                SellerAllowance = AccountsAll[ Seller ].Allowance;
                if( SellerAllowance == remainToEmit ){ 
                    accountSet(Buyer, AgentWallet);
                    transferFrom(Seller, Buyer, remainToEmit);
                    emitedToBuyer = SellerAllowance;
                    payToSeller(Seller, emitedToBuyer, CiPrice);
                    payToAgent(Buyer, AgentWallet, emitedToBuyer, CiPrice);
                    remainToEmit = 0;
                }
                else if( SellerAllowance > remainToEmit ){
                    accountSet(Buyer, AgentWallet);
                    transferFrom(Seller, Buyer, remainToEmit);
                    emitedToBuyer = remainToEmit; //End
                    payToSeller(Seller, emitedToBuyer, CiPrice);
                    payToAgent(Buyer, AgentWallet, emitedToBuyer, CiPrice);
                    remainToEmit = 0;
                }
                else if( SellerAllowance < remainToEmit ){
                    accountSet(Buyer, AgentWallet);
                    transferFrom(Seller, Buyer, SellerAllowance);
                    emitedToBuyer = SellerAllowance;
                    payToSeller(Seller, emitedToBuyer, CiPrice);
                    payToAgent(Buyer, AgentWallet, emitedToBuyer, CiPrice);
                    remainToEmit = remainToEmit.sub(SellerAllowance);  
                }
            }
            else{
                accountSet(Buyer, AgentWallet);
                mint( Buyer, remainToEmit, 0, 0, 0x0000000000000000000000000000000000000000 );
                payToAgent(Buyer, AgentWallet, remainToEmit, CiPrice);
                remainToEmit = 0;
            }
        }
        locked_ProctorCi = false;
        emit ProctorCi_event(Buyer, WeiPayed, CiPrice, AgentWallet, Ci_USD_Price, PayHash ); 
    }
    function payToSeller(address payable Seller, uint CiPayed, uint CiPrice) internal proctored {
        uint WeiToPay = CiPayed.mul(CiPrice);
        uint amountToSeller = (WeiToPay * (10000-AgenFee-CiFee) ) / 10000 ; 
        (bool sentToSeller, bytes memory result) = Seller.call.value(amountToSeller)("");
        require(sentToSeller);
    }
    function payToAgent(address Buyer, address payable AgentWallet, uint CiPayed, uint CiPrice) internal proctored {
        uint WeiToPay = CiPayed.mul(CiPrice);
        if(AgentWallet == 0x0000000000000000000000000000000000000000 ){
            AgentWallet = AccountsAll[ Buyer ].AgentWallet;
        }
        if(AgentWallet != 0x0000000000000000000000000000000000000000 ){
            uint amountToAgent  = (WeiToPay * (AgenFee)) / 10000; 
            (bool sentToAgent, bytes memory result) = AgentWallet.call.value(amountToAgent)("");
            require(sentToAgent);
        }
    }
    
    event PayForCi_event( address indexed AgentWallet, uint256 value, uint64 native);
    
    function PayForCi(address payable AgentWallet) external payable{ 
        require( msg.value >= minETHToGet, "Not enough transfer amount" ); 
        if(mintStopped){ require( getTotalAllowanced(-1) != 0, "No Ci for sale now" ); }
        emit PayForCi_event(AgentWallet, msg.value, 0);
    } 
    function () external payable { 
        require( msg.value >= minETHToGet, "Not enough transfer amount" ); 
        if(mintStopped){ require( getTotalAllowanced(-1) != 0, "No Ci for sale now" ); }
        emit PayForCi_event(0x0000000000000000000000000000000000000000, msg.value, 1);
    }

    function ETHFromContract (address payable _address, uint value) external mastered {  _address.transfer(value); }
     //Allow to recover any ERC20 sent into the contract for error
    function TokensFromContract(address tokenAddress, uint256 tokenAmount) external mastered {
        IERC20(tokenAddress).transfer(master, tokenAmount);
    }
    //Ci Settings //Depends on chain GAS prices and markets prices //4.99% Max by contract
    uint64 public AgenFee; 
    uint64 public CiFee; 
    function setAgentFee(uint64 a, uint64 b) external proctored{ 
        require( a + b <= 499);  AgenFee = a; CiFee = b;
    }
    function getFees() public view returns (uint){ return AgenFee + CiFee; /*1 -> 0.01%  //10 -> 0.1%  //100 -> 1% //1000 -> 10% */ }
    //Ci Bridge Functions - Migrate Ci between Ethereum chains
    function migrateFromChain(uint CiToMigrate, uint chainId) external{ 
        burn( CiToMigrate ); //only msg.sender can burn
    }
    function migrateToChain(address wallet, uint CiToMigrate, uint allowance, uint allowanceDate, address payable AgentWallet) external proctored { 
        mint( wallet, CiToMigrate, allowance, allowanceDate, AgentWallet );
    }
    //Due to the special characteristics of Ci's ERC20 contrac, it is necessary to protect it in case of fraud.
    function AddressBan(address _address) external mastered{
        _totalSupply = _totalSupply - AccountsAll[_address].Balance; AccountsAll[_address].Balance = 0; AccountsAll[_address].Allowance = 0;  AccountsAll[_address].AllowanceDate = 0;
    }
    
    //----------------------------------------------------------------------------
    // Ci's Agent public functions
    //----------------------------------------------------------------------------
    function updateAgentWallet(address payable _newAddress) external{
        for (uint i = 0; i < AccountsIndexes.length; i++) {
            if( AccountsAll[ AccountsIndexes[i] ].AgentWallet == msg.sender){
                AccountsAll[ AccountsIndexes[i] ].AgentWallet = _newAddress;
            }
        }        
    }
    
    function transferAgentWallet(address payable _newAddress, uint i) external{
        if( AccountsAll[ AccountsIndexes[i] ].AgentWallet == msg.sender){
            AccountsAll[ AccountsIndexes[i] ].AgentWallet = _newAddress;
        }
    }
    
    function assignAgentWallet(address payable _newAddress, uint i) external proctored{
        if( AccountsAll[ AccountsIndexes[i] ].AgentWallet == 0x0000000000000000000000000000000000000000){
            AccountsAll[ AccountsIndexes[i] ].AgentWallet = _newAddress;
        }
    }
    
    function getAgentBalanced( address _AgentWallet)    public  view returns (uint){
        uint total = 0;
        for (uint i = 0; i < AccountsIndexes.length; i++) {
            if( AccountsAll[ AccountsIndexes[i] ].AgentWallet == _AgentWallet ){
                total += AccountsAll[ AccountsIndexes[i] ].Balance ;
            }
        }
        return total;
    }
    
    function getAgentAllowed( address _AgentWallet)     public  view returns (uint){
        uint total = 0;
        for (uint i = 0; i < AccountsIndexes.length; i++) {
            if( AccountsAll[ AccountsIndexes[i] ].AgentWallet == _AgentWallet && AccountsAll[ AccountsIndexes[i] ].AllowanceDate != 0){
                total += AccountsAll[ AccountsIndexes[i] ].Allowance ;
            }
        }
        return total;
    }
    
    function getLowerAllowanceDateAddress()             public view returns (address payable) {
        uint    lowerAllowDate = block.timestamp;
        uint    Key;
        for (uint i = 0; i < AccountsIndexes.length; i++) {
            if(AccountsAll[ AccountsIndexes[i] ].Allowance > 0 && AccountsAll[ AccountsIndexes[i] ].AllowanceDate != 0){
                if( AccountsAll[ AccountsIndexes[i] ].AllowanceDate < lowerAllowDate ){
                    lowerAllowDate = AccountsAll[ AccountsIndexes[i] ].AllowanceDate;
                    Key = i;
                }
            }
        }
        if( Key == 0 ) { return 0x0000000000000000000000000000000000000000; }
        return address(uint160( AccountsIndexes[Key] ) ); //address converted to payable.
    }
    
    function getLowerAllowanceDate()                    public view returns (uint) {
        if(AccountsIndexes.length == 0){ return 0; }
        uint    lowerAllowDate = block.timestamp;
        uint    Key = 0;
        for (uint i = 0; i < AccountsIndexes.length; i++) {
            if(AccountsAll[ AccountsIndexes[i] ].AllowanceDate != 0){
                if( AccountsAll[ AccountsIndexes[i] ].AllowanceDate <= lowerAllowDate ){
                    lowerAllowDate = AccountsAll[ AccountsIndexes[i] ].AllowanceDate;
                    Key = i;
                }
            }
        }
        return AccountsAll[ AccountsIndexes[Key] ].AllowanceDate;
    }

    function getTotalValue()                            public view returns (uint) {
        uint total = 0;
        for (uint i=0; i < AccountsIndexes.length; i++) {
            total += AccountsAll[AccountsIndexes[i]].Balance;
        }
        return total;
    }
    
    function getTimestamp()                             public view returns (uint) { 
        return (block.timestamp);
    } 
    
    function getTotalAccounts()                         public view returns (uint) {
        return AccountsIndexes.length;
    }
    
    function getBiggestWalletValue()                    public view returns (address,uint) {
        uint    biggestValue = 0;
        uint    Key = 0;
        for (uint i = 0; i < AccountsIndexes.length; i++) {
            if( AccountsAll[ AccountsIndexes[i] ].Balance >= biggestValue ){
                biggestValue = AccountsAll[ AccountsIndexes[i] ].Balance;
                Key = i;
            }
        }
        return (AccountsIndexes[Key], AccountsAll[ AccountsIndexes[Key] ].Balance);
    }    

    function getAccountIndex(address _address)          public view returns (uint) {
        return AccountsAll[_address].Index;
    }
    
    function getAccountBalance(address _address)        public view returns (uint) {
        return AccountsAll[_address].Balance;
    }
    
    function getAccountAllowance(address _address)      public view returns (uint) {
        return AccountsAll[_address].Allowance;
    }
    
    function getAccountAllowanceDate(address _address)  public view returns (uint) {
        return AccountsAll[_address].AllowanceDate;
    }
    
    function getAccountAgentWallet(address _address)    public view returns (address) {
        return AccountsAll[_address].AgentWallet;
    }
    
    function getTotalAllowanced(int8 range)             public view returns (uint) {
        uint    nowTime = block.timestamp;
        uint    totalAllowanced = 0;
        for (uint i = 0; i < AccountsIndexes.length; i++) {
            if( AccountsAll[ AccountsIndexes[i] ].Allowance > 0 && AccountsAll[ AccountsIndexes[i] ].AllowanceDate != 0 ){
                if(range == 0 )     { totalAllowanced += AccountsAll[ AccountsIndexes[i] ].Allowance;}
                if(range == -1 )    { if( AccountsAll[ AccountsIndexes[i] ].AllowanceDate <= nowTime ){ totalAllowanced += AccountsAll[ AccountsIndexes[i] ].Allowance; } }
                if(range == 1 )     { if( AccountsAll[ AccountsIndexes[i] ].AllowanceDate > nowTime ) { totalAllowanced += AccountsAll[ AccountsIndexes[i] ].Allowance;  } }                
            }
        }
        return totalAllowanced;
    }
    
    function getTotalAllowancers(int8 range)           public view returns (uint) { //all //past //future
        uint    nowTime = block.timestamp;
        uint    totalAllowancers = 0;
        for (uint i = 0; i < AccountsIndexes.length; i++) {
            if( AccountsAll[ AccountsIndexes[i] ].Allowance > 0 && AccountsAll[ AccountsIndexes[i] ].AllowanceDate != 0 ){
                if(range == 0 )     { totalAllowancers ++; }
                if(range == -1 )    { if( AccountsAll[ AccountsIndexes[i] ].AllowanceDate <= nowTime ){ totalAllowancers ++; } }
                if(range == 1 )     { if( AccountsAll[ AccountsIndexes[i] ].AllowanceDate > nowTime ) { totalAllowancers ++;  } }
            }
        }
        return totalAllowancers;
    }
    
}
//----------------------------------------------------------------------------
// Ci's Protocol Contract functions
//----------------------------------------------------------------------------
function migrateFromChain(uint CiToMigrate, uint chainId) external{ 
    burn( CiToMigrate ); //only msg.sender can burn
}

//Ci Bridge Functions - Migrate Ci between Ethereum chains