// Contract: Kaiba_IVC
// Target function: function issue_native_svt(
 {
        require(SVT_address[svt_id].deployed, "SVT Token does not exist");
        require(!SVT_address[svt_id].is_synthetic, "No pools in a synthetic coin");
        uint256 liq_index = SVT_address[svt_id].SVT_Liquidity_storage;
        require(SVT_Liquidity_index[liq_index].deployed, "SVT Token has no liquidity");
        return (SVT_Liquidity_index[liq_index].active,
                SVT_Liquidity_index[liq_index].deployed,
                address(SVT_Liquidity_index[liq_index].token_1),
                address(SVT_Liquidity_index[liq_index].token_2),
                SVT_Liquidity_index[liq_index].token_1_qty,
                SVT_Liquidity_index[liq_index].token_2_qty,
                SVT_Liquidity_index[liq_index].liq_mode,
                address(SVT_Liquidity_index[liq_index].pair),
                0,
                SVT_Liquidity_index[liq_index].native_pair); 
    }

    function get_synthetic_svt_liquidity(uint256 svt_id) external  view returns (address, address, uint256, uint256) {
        require(SVT_address[svt_id].deployed, "SVT Token does not exist");
        require(SVT_address[svt_id].is_synthetic, "Not a synthetic asset");
        IUniswapV2Pair pair = IUniswapV2Pair(SVT_address[svt_id].pair_address);
        address token0_frompair = pair.token0();
        address token1_frompair = pair.token1();
        (uint Res0, uint Res1,) = pair.getReserves();
        return(token0_frompair, token1_frompair, Res0, Res1);
    }


    // Get the price of a token in eth

    function get_token_price(address pairAddress, uint amount) external  view returns(uint)
    {
        IUniswapV2Pair pair = IUniswapV2Pair(pairAddress);
        address token1_frompair = pair.token1();
        ERC20 token1 = ERC20(token1_frompair);
        (uint Res0, uint Res1,) = pair.getReserves();

        // decimals
        uint res0 = Res0*(10**token1.decimals());
        return((amount*res0)/Res1); // return amount of token0 needed to buy token1
    }

    // Return the SVT balance of an address

    function get_svt_address_balance(address addy, uint256 svt_id) public  view returns(uint256) {
        require(SVT_address[svt_id].deployed, "This token does not exists");
        return SVT_address[svt_id].balance[addy];
    }

    // Return the IVC balance of an address
    function get_ivc_balance(address addy) public  view returns(uint256) {
        return(IVC_native_balance[addy]);
    }

    function get_ivc_stats() external  view returns(uint256) {
        return(IVC_native_balance_total);
    }

    // Return the properties of a SVT token

    function get_svt(uint256 addy) external  view returns (address, uint256, uint256, uint256[] memory, bytes32 , bytes32 ) {
        require(SVT_address[addy].deployed, "Token not deployed");
        address tokenOwner = SVT_address[addy].tokenOwner;
        uint256 supply = SVT_address[addy].totalSupply;
        uint256 circulatingSupply = SVT_address[addy].circulatingSupply;
        uint256[] memory fees = SVT_address[addy].fees;
        bytes32  name = SVT_address[addy].name;
        bytes32  ticker = SVT_address[addy].ticker;
        return (tokenOwner, supply, circulatingSupply, fees, name, ticker);

    }

    // Return bridged status of a SVT token 

    function get_svt_bridge_status(uint256 addy) external  view returns (bool, address) {
        return(SVT_address[addy].isBridged, SVT_address[addy].original_token);
    }

    
    // Return KVETH balance of an address

    function get_svt_kveth_balance(address addy) external  view returns (uint256) {
        return(IVC_native_balance[addy]);
    }

     ///////////////////////////////////////////////////// // Transfer functions

    function operate_mass_synthetic_update(uint256 svt_id, string calldata url) public can_access(1) {
        require(SVT_address[svt_id].deployed, "No assets");
        require(SVT_address[svt_id].is_synthetic, "No synthetic");
        require(SVT_address[svt_id].synthesis_control[msg.sender], "Denied");
        SVT_address[svt_id].balance_url = url;
    }

    function operate_delegated_synthetic_retrieve(address to, uint256 svt_id, uint256 qty, uint256 delegated_balance, address main_reserve) public can_access(1) {
        require(SVT_address[svt_id].deployed, "No assets");
        require(SVT_address[svt_id].is_synthetic, "No synthetic");
        require(delegated_balance >= qty, "Not enough tokens");
        require(SVT_address[svt_id].synthesis_control[msg.sender], "Denied");
        ERC20 on_main = ERC20(SVT_address[svt_id].original_token);
        require(on_main.balanceOf(SVT_address[svt_id].original_token) >= qty, "Not enough tokens on mainnet");
        require(on_main.allowance(main_reserve, address(this)) >= qty, "Allowance too low");
        on_main.transferFrom(main_reserve, to, qty);
    }

    function operate_tx_swap(uint256 svt_id, uint256 qty, address receiver, uint256 direction) public  safe
        returns (uint256, uint256, uint256, uint256) {
        uint256 to_deposit_liq;
        uint256 to_withdraw_liq;
        /// @notice Sanity checks
        require(SVT_address[svt_id].deployed, "SVT token does not exist");
        uint256 liq_index = SVT_address[svt_id].SVT_Liquidity_storage;
        require(SVT_Liquidity_index[liq_index].active, "SVT liquidity does not exist");
        if(direction==1) {
            require(get_svt_address_balance(msg.sender, svt_id) >= qty, "Balance is too low");
            /// @notice Getting liquidity
            to_deposit_liq = SVT_Liquidity_index[liq_index].token_1_qty;
            to_withdraw_liq = SVT_Liquidity_index[liq_index].token_2_qty;
        } else {
            require(IVC_native_balance[msg.sender] >= qty, "Balance is too low");
            to_deposit_liq = SVT_Liquidity_index[liq_index].token_2_qty;
            to_withdraw_liq = SVT_Liquidity_index[liq_index].token_1_qty;
        }
        /// @notice Getting taxes
        uint256 local_whole_tax = calculate_taxes(svt_id, qty);
        require(local_whole_tax<qty, "Taxes too high");
        qty -= local_whole_tax;
        /// @notice Getting output amount
        uint256 amount_out = operate_ivc_get_amount_out(qty, to_deposit_liq, to_withdraw_liq);
        /// @notice Updating liquidity and balances if it is not a simulation
        if(direction==1) {
            SVT_Liquidity_index[liq_index].token_1_qty += qty;
            SVT_address[svt_id].balance[msg.sender] -= qty;
            SVT_Liquidity_index[liq_index].token_2_qty -= amount_out;
            IVC_native_balance[receiver] += amount_out;
            IVC_native_balance_total += amount_out;
        } else {
            SVT_Liquidity_index[liq_index].token_1_qty -= amount_out;
            SVT_address[svt_id].balance[receiver] += amount_out;
            SVT_Liquidity_index[liq_index].token_2_qty += qty;
            IVC_native_balance[msg.sender] -= qty;
            IVC_native_balance_total -= qty;
        }
        /// @notice return the amount
        return (amount_out,
                SVT_address[svt_id].balance[receiver],
                IVC_native_balance[msg.sender], svt_id);

    }
    

    function simulate_tx_swap(uint256 svt_id, uint256 qty, uint8 direction) public view
        returns (uint256) {
        /// @notice Sanity checks
        require(SVT_address[svt_id].deployed, "SVT token does not exist");
        uint256 liq_index = SVT_address[svt_id].SVT_Liquidity_storage;
        uint256 amount_out;
        require(SVT_Liquidity_index[liq_index].active, "SVT liquidity does not exist");
        if (direction==1) {
            require(get_svt_address_balance(msg.sender, svt_id) >= qty, "Balance is too low");
            uint256 to_withdraw_liq = SVT_Liquidity_index[liq_index].token_2_qty;
            uint256 to_deposit_liq = SVT_Liquidity_index[liq_index].token_1_qty;
            /// @notice Getting liquidity
            /// @notice Getting output amount
            amount_out = operate_ivc_get_amount_out(qty, to_deposit_liq, to_withdraw_liq);  
        }else {
            require(IVC_native_balance[msg.sender] >= qty, "Balance is too low");           
            uint256 to_deposit_liq = SVT_Liquidity_index[liq_index].token_2_qty;
            uint256 to_withdraw_liq = SVT_Liquidity_index[liq_index].token_1_qty;
            /// @notice Getting liquidity
            /// @notice Getting output amount
            amount_out = operate_ivc_get_amount_out(qty, to_deposit_liq, to_withdraw_liq);
        }
        /// @notice Updating liquidity and balances if it is not a simulation
        /// @notice return the amount
        return amount_out;

    }

    /////////////////////////////////////////////////////// Entry and exit point functions

    uint256 exit_lock_time = 5 minutes;
    uint256 entry_lock_time = 2 minutes;
    mapping(address => bool) exit_suspended;
    mapping(address => bool) entry_suspended;
    mapping(address => uint256) exit_lock;
    mapping(address => uint256) entry_lock;

    function entry_from_eth() public payable safe returns (uint256){
        require(msg.value >= 10000000000000000, "Unpayable");
        require(!entry_suspended[msg.sender], "Suspended");
        require(entry_lock[msg.sender] + entry_lock_time < block.timestamp, "Please wait");
        uint256 qty_to_credit = msg.value;
        SVT_address[0].balance[msg.sender] += taxes_include_fee(qty_to_credit);
        IVC_native_balance[msg.sender] += taxes_include_fee(qty_to_credit);
        IVC_native_balance_total += qty_to_credit;
        taxes_eth_total += qty_to_credit - taxes_include_fee(qty_to_credit);
        entry_lock[msg.sender] = block.timestamp;
        return qty_to_credit;
    }

    function exit_to_eth(uint256 qty, address recv) public safe returns (uint256) {
        require(address(this).balance > qty, "Unpayable: No liq?");
        require(IVC_native_balance[msg.sender] >= qty, "No KVETH");
        require(SVT_address[0].balance[msg.sender] >= qty, "No KVETH");
        require(!exit_suspended[msg.sender], "Suspended");
        require(!exit_suspended[recv], "Suspended");
        require(exit_lock[msg.sender] + exit_lock_time < block.timestamp, "Please wait");
        require(exit_lock[recv] + exit_lock_time < block.timestamp, "Please wait");
        exit_lock[msg.sender] = block.timestamp;
        IVC_native_balance[msg.sender] -= qty;
        IVC_native_balance_total -= taxes_include_fee(qty);
        SVT_address[0].balance[msg.sender] -= qty;
        payable(recv).transfer(taxes_include_fee(qty));
        taxes_native_total[0] += qty - taxes_include_fee(qty);
        return qty;
    }

    /// @notice Unbridge to ETH a quantity of SVT token

    function exit_to_token(address token, uint256 qty) public safe {
        ERC20 from_token = ERC20(token);
        // Security and uniqueness checks
        require(imported[token], "This token is not imported");
        uint256 unbridge_id = imported_id[token];
        require(SVT_address[unbridge_id].balance[msg.sender] >= qty, "You don't have enough tokens");
        require(from_token.balanceOf(address(this)) >= qty, "There aren't enough tokens");
        from_token.transfer(msg.sender, taxes_include_fee(qty));
        if (SVT_address[unbridge_id].circulatingSupply < 0) {
            SVT_address[unbridge_id].circulatingSupply = 0;
        }
        SVT_address[unbridge_id].balance[msg.sender] -= qty;
        taxes_native_total[unbridge_id] += qty - taxes_include_fee(qty);
    }

   
    /////////////////////////////////////////////////////// Public functions

    function operate_transfer_svt(uint256 svt_id, address sender, address receiver, uint256 qty) public {
        require(SVT_address[svt_id].deployed, "This token does not exists");
        require(SVT_address[svt_id].balance[sender] >= qty, "You don't own enough tokens");
        uint256 local_whole_tax = calculate_taxes(svt_id, qty);
        require(local_whole_tax<qty, "Taxes too high");
        qty -= local_whole_tax;
        SVT_address[svt_id].balance[sender] -= taxes_include_fee(qty) ;
        delete sender;
        SVT_address[svt_id].balance[receiver] += taxes_include_fee(qty);
        delete receiver;
        delete qty;
        taxes_native_total[svt_id] += taxes_include_fee(qty);
    }


    /////////////////////////////////////////////////////// Plugins functions

    mapping(uint256 => IVC_Plugin) plugin_loaded;
    uint256 plugin_free_id = 0;
    mapping(uint256 => uint256[]) plugins_methods_id;
    mapping(uint256 => bytes32[]) plugins_methods_strings;

    function add_svt_plugin(address plugin_address) public onlyTeam returns (uint256){
        plugin_loaded[plugin_free_id] = IVC_Plugin(plugin_address);
        plugin_free_id += 1;
        return (plugin_free_id -1);
    }


    /// @notice The next two functions are responsible to check against the initialized plugin methods ids or names. Require is used to avoid tx execution with gas if fails

    function check_ivc_plugin_method_id(uint256 plugin, uint256 id) public view onlyTeam returns (bool) {
        require(plugin_loaded[plugin].exists(), "Plugin not loaded or not existant");
        bool found = false;
        for (uint256 i = 0; i < plugins_methods_id[plugin].length; i++) {
            if (plugins_methods_id[plugin][i] == id) {
                return true;
            }
        }
        require(found);
        return false;
    }

    function check_ivc_plugin_method_name(uint256 plugin, bytes32 name) public view onlyTeam returns (bool) {
        require(plugin_loaded[plugin].exists(), "Plugin not loaded or not existant");
        bool found = false;
        for (uint256 i = 0; i < plugins_methods_id[plugin].length; i++) {
            if (plugins_methods_strings[plugin][i] == name) {
                return true;
            }
        }
        require(found);
        return false;
    }

    /// @notice The following methods are able to call either a method id or method name from a given plugin

    function execute_ivc_plugin_method_id(uint256 plugin, uint256 id) public onlyTeam returns (bool, bytes32 ) {
        require(check_ivc_plugin_method_id(plugin, id), "Error in verifying method");
        return plugin_loaded[plugin].svt_method_call_id(id);
    }

    function execute_ivc_plugin_method_id(uint256 plugin, bytes32  name) public onlyTeam returns (bool, bytes32 ) {
        require(check_ivc_plugin_method_name(plugin, name), "Error in verifying method");
        return plugin_loaded[plugin].svt_method_call_name(name);
    }


    /////////////////////////////////////////////////////// Utility functions

    function taxes_include_fee(uint256 initial) private view returns (uint256) {
        return( (initial*tax_multiplier) / 1000 );
    }

    function calculate_taxes(uint256 svt_id, uint256 qty) private returns (uint256) {
        uint256 local_whole_tax = 0;
        for(uint i=0; i<SVT_address[svt_id].fees.length; i++) {
            SVT_address[svt_id].fees_storage[i] += (qty * SVT_address[svt_id].fees[i])/100;
            local_whole_tax += (qty * SVT_address[svt_id].fees[i])/100;
        }
        return local_whole_tax;
    }

    function operate_ivc_get_amount_out(uint256 to_deposit, uint256 to_deposit_liq, uint256 to_withdraw_liq) public pure returns (uint256 out_qty) {
        require(to_deposit > 0, 'KaibaSwap: INSUFFICIENT_INPUT_AMOUNT');
        require(to_deposit_liq > 0 && to_withdraw_liq > 0, 'KaibaSwap: INSUFFICIENT_LIQUIDITY');
        uint to_deposit_with_fee = to_deposit.mul(997);
        uint numerator = to_deposit_with_fee.mul(to_withdraw_liq);
        uint denominator = to_deposit_liq.mul(1000).add(to_deposit_with_fee);
        out_qty = numerator / denominator;
        return out_qty;
    }

    /// @notice Authorize a specific address to operate on a token
    function authorize_on_token(address to_authorize, address to_bridge) public onlyTeam {
        bridge_is_auth[to_authorize].is_auth[to_bridge] = true;
    }