contract DAAORacle is usingProvable, Initializable {
    using SafeMath for uint;
    using strings for *;
    address public whitelistmanager;
    address public _owner;
    address[] public tokenList;
    uint[] public weights;
    mapping(address => bool) public whitelistedaddress;

    mapping(uint256 => string) public PoolDatasource;
    
    struct PoolInfo {
        // Token in indices
        address[] tokens;  
        // weight of tokens  
        uint[]  weights;
        // Threshold amount
        uint threshold;
        // Next rebalance time
		uint rebalanceTime;
        // itoken Name
        string name;
        // itoken Symbol
		string symbol;
        // Description of pool
        string description;
    }
     
    PoolInfo[]  poolInfo;
    /**
     * @dev Modifier to check if the caller is whitelisted or not.
     */
    modifier whitelistedonly{
        require(whitelistedaddress[msg.sender] == true, "Only whitelistedaddress");
        _;
    }
    /**
     * @dev Modifier to check if the caller is owner or not.
     */
    modifier onlyOwner{
        require(msg.sender == _owner, "Only Owner");
        _;
    }
    	/**
     * @dev Modifier to check if the caller is manager or not
     */
    modifier whitelistmanagerOnly{
        require(msg.sender == whitelistmanager, "Only whitelistmanage");
        _;
    }
    function initialize() public initializer{
      _owner = msg.sender;
      whitelistmanager = msg.sender; 
	}
    // Save details of latest response from provable.
    string public tokens;
    event LogTokensUpdated(string tokens);
    event LogNewProvableQuery(string description);

    /**
     * @dev Called by the Provable contracts to return the data of the request. Only provble contract can call this function.
     */
   function __callback(bytes32 myid, string memory result) public{
       if (msg.sender != provable_cbAddress()) revert();
       tokens = result;
       updateValue(result);
       emit LogTokensUpdated(result);
   }

    /**
     * @dev Internal function to decrypt the data from response.
     * Here we send all the details in string seperate by ',' in this order.
     * First is token lenght -1 to calculate internally.
     * Tokens addreses seperated by ,
     * Weight of token
     * Threshold amount
     * Rebalance Time
     */

   function updateValue(string memory _callbackData)internal{
       // Using the string library from above
        strings.slice memory s;
        s = _callbackData.toSlice();
        uint[] memory buf1;
        address[] memory buf2;
        tokenList = buf2;
        weights = buf1;
        uint tokenLength;
        uint loopLenght;
        uint TotalTokens;
        uint _poolIndex;
        uint Threshold;
        uint RebalanceTime;
        // Get the length of token by splittin from first ','. parseInt is provide by provable initially.
        tokenLength = parseInt(s.split(",".toSlice()).toString());
        // Get the actuall in previous it tell based on index.
        TotalTokens = tokenLength.add(1);
        //Count how many time we need to call the loop to get the each value.
        loopLenght = tokenLength.mul(2).add(5);

        // Run the loop to get each field and parse them into required format.
        for(uint i=1;i<=loopLenght;i++){
            if(i<=TotalTokens ){
                address tmp = parseAddr(s.split(",".toSlice()).toString());
                tokenList.push(tmp);
            }else if(i<=2*TotalTokens ){
                uint inttmp = parseInt(s.split(",".toSlice()).toString());
                weights.push(inttmp);
                
            }else if(i==(loopLenght.sub(2))){
                _poolIndex = parseInt(s.split(",".toSlice()).toString());
            }
            else if(i==(loopLenght.sub(1))){
                Threshold = parseInt(s.split(",".toSlice()).toString());
            }
            else if(i==loopLenght){
                RebalanceTime = parseInt(s.split(",".toSlice()).toString());
            }

        }

        // check if returned confirguation is correct or not.
        require(tokenList.length >0,"updateValue: Not enough tokens from AI");
        require(tokenList.length == weights.length, "Invalid configurations");

        // Update the details in mappping after all details are fetched successfully.
        poolInfo[_poolIndex].tokens = tokenList;
        poolInfo[_poolIndex].weights = weights;
        poolInfo[_poolIndex].threshold = Threshold;
        poolInfo[_poolIndex].rebalanceTime = RebalanceTime;
    }

    /**
     * @dev Function to get the latest result from AI. This can be called by anyone. Caller must send enough gas fees so that provable can send response.
     */
   function updateTokens(uint256 _poolIndex)public payable {
       uint256 customFees =8000000;
       // Check if the contract has enough balance to make the response call
       if (provable_getPrice("URL") > address(this).balance) {
           emit LogNewProvableQuery("Error in sending query for Pool");
       } else {
           emit LogNewProvableQuery("Query sent successfully");
           // Query sent to provable. Here we need to send custom gas fees as initially very low gas is used by provable.
           provable_query("URL", PoolDatasource[_poolIndex],customFees);
        //    provable_query("URL", "json(https://run.mocky.io/v3/ef245352-c6ff-4fc6-a972-027906238417).tokens",customFees);
       }
   }

    /**
     * @dev Update the manager address who can control configuration. Manager has permission to whitelist addresses and only owner can update the addresses.
     */
	function updateManager(address _addr) public onlyOwner{
        require(_addr != address(0), "Zero Address"); 
	    require(_addr != whitelistmanager, "Already whitelist manager");
	    whitelistmanager = _addr;
	}

    /**
     * @dev Whitelist address who can control the configuration of pool. Whitelisted address has access to manage the pool details.
     */
	
	function whitelistaddress(address _addr) public whitelistmanagerOnly{
        require(_addr != address(0), "Zero Address");
          require(whitelistedaddress[_addr] == false, "ALready Whitelisted");
          whitelistedaddress[_addr] = true;
	}

   /**
     * @dev Remove user from whitelist.
     */
	function removewhitelist(address _addr) public whitelistmanagerOnly{
        require(_addr != address(0), "Zero Address");
	    require(whitelistedaddress[_addr] == true, "Not Whitelisted");
	    whitelistedaddress[_addr] = false;
	}

    /**
     * @dev Add new pool details. Initially first pool have to be entered manually with datasource.
     */

    function AddNewPoolTokens(address[] memory _tokens, uint[] memory _weights,uint _threshold,uint _rebalanceTime,string memory _name, string memory _symbol,string memory _datasource, string memory _description) public whitelistedonly returns(bool){
        require(_tokens.length == _weights.length, "Invalid configurations");	   
        poolInfo.push(PoolInfo({
            tokens: _tokens,
            weights: _weights,
            threshold:_threshold,
            rebalanceTime:_rebalanceTime,
            name: _name,
		    symbol: _symbol,
            description: _description
        }));

        // Get the added pool index to store datasource details
        uint256 _poolIndex = poolInfo.length.sub(1);
        PoolDatasource[_poolIndex] = _datasource;
	}

     /**
     * @dev Update the AI source of the pool. In case the  wrong datasource is updated then the manager has options to update index datasource.
     */

    function UpdatePoolDatasource(uint256 _poolIndex,string calldata _datasource)external whitelistedonly{
        require(_poolIndex<poolInfo.length, "UpdatePoolDatasource: Invalid Pool Index");
        PoolDatasource[_poolIndex] = _datasource;
    }

    /**
     * @dev Update the configuration mannually. In case details not came from the AI manager can manually update the pool.
     */
    function UpdatePoolConfiguration(address[] memory _tokens, uint[] memory _weights, uint _poolIndex,uint _threshold,uint _rebalanceTime) public whitelistedonly returns(bool){
	    require(_tokens.length == _weights.length, "Invalid configurations");
        poolInfo[_poolIndex].tokens = _tokens;
        poolInfo[_poolIndex].weights = _weights;
        poolInfo[_poolIndex].threshold = _threshold;
        poolInfo[_poolIndex].rebalanceTime = _rebalanceTime;
	}
    /**
     * @dev Get the Token details it used by DAA poolv1 at the time of creating/updating pool.
     */
    function getTokenDetails(uint _poolIndex) public view returns(address[] memory ,uint[] memory,uint,uint){
        return (poolInfo[_poolIndex].tokens,poolInfo[_poolIndex].weights,poolInfo[_poolIndex].threshold,poolInfo[_poolIndex].rebalanceTime);
    }
    /**
     * @dev Get the iToken details it used by DAA poolv1 at the time of creatingpool.
     */
    function getiTokenDetails(uint _poolIndex) public view returns(string memory, string memory,string memory){
        return (poolInfo[_poolIndex].name,poolInfo[_poolIndex].symbol,poolInfo[_poolIndex].description);
    }

}
function getTokenDetails(uint _poolIndex) public view returns(address[] memory ,uint[] memory,uint,uint){
    return (poolInfo[_poolIndex].tokens,poolInfo[_poolIndex].weights,poolInfo[_poolIndex].threshold,poolInfo[_poolIndex].rebalanceTime);
}

/**
 * @dev Get the Token details it used by DAA poolv1 at the time of creating/updating pool.
 */