contract TransmutationEngine is VRFConsumerBase, OwnableOperable {
    using SafeMathChainlink for uint256;
    
    
    uint64 public currentSession = 0;
    uint64 public nextSession = 1;
    
    struct Engine_params { 
        address rewardsVault;
        uint8 alchemyCut;
        uint8 minAlchemists;
        uint16 maxAlchemists;
        uint64 minXpb;
        uint16 minTime;
        bool enabled;
    }
    
    
    struct Tokens_blueprint { 
        address tokenAddress;
        address pairAddress;
        uint64 amount;
        bool enabled;
    }
    
    struct Transmute_registry_entry { 
        address alchemist;  // wallet address attemtping the transmutation
        uint8 reward;     // ERC-20 address of reward token
        // uint64 amount;      // (9 decimals), committed amount of XPb
        uint16 chance;      // ( 0 decimals)
        // uint32 timestamp;    // timestamp of operation, as reference for prices verification and general timestamp-stuff
        // bool successful;    // marked as true if randomness <= chance. if true, the transmutation is successful and the alchemist receives the reward.
    }
    
    struct Transmute_registry_blueprint { 
        uint32 timestamp_init;
        uint32 timestamp_end;
        bytes32 randReqId;
        uint16 randomness;  // ( 0 decimals) -> received randomnes, simmered down to a more usable (5 digit max 65535) degree of resolution
        uint16 total_entries;
        uint64 total_xpb;
        bool vrf;      // marked as true when the VRF callback function is triggered and the transmutation attempt is complete
        bool complete;      
    }
    
    
    
    Engine_params public EngineParameters;
    
    mapping (uint8 => Tokens_blueprint) public tokens;
    mapping (uint64 => mapping (uint8 => uint64)) public token_prices; 
    mapping (uint64 => Transmute_registry_blueprint) public transmutation_sessions;  // maximum frequency - hourly
    mapping (uint64 => mapping (uint16 => Transmute_registry_entry)) public transmutation_sessions_entries;
    
    bytes32 internal keyHash;
    uint256 internal fee;
    
    constructor () 
        VRFConsumerBase(
            0xf0d54349aDdcf704F77AE15b96510dEA15cb7952, // VRF Coordinator
            0x514910771AF9Ca656af840dff83E8264EcF986CA  // LINK Token
        ) 
    {
        keyHash = 0xAA77729D3466CA35AE8D28B3BBAC7CC36A5031EFDC430821C02BC31A238AF445;
        fee = 2 * 1e18; // 2 LINK for VRF Fees
        
        owner = msg.sender;
        operator = msg.sender;
        
    
        EngineParameters = Engine_params({ 
                                                   rewardsVault: 0x4417e253582B8F9612Bbb0B906339958691D7dCf,
                                                   alchemyCut: 3, //%
                                                   minAlchemists: 10,
                                                   maxAlchemists: 50,
                                                   minXpb: 777*1e9, 
                                                   minTime: 60, //seconds
                                                   enabled: true
            
                                                                });
        
        tokens[0] = Tokens_blueprint({ 
                                                   tokenAddress: 0xbC81BF5B3173BCCDBE62dba5f5b695522aD63559,  // XPb
                                                   pairAddress: 0x1ab24a692EFf49b9712CEaCdEf853152d78b9050,
                                                   amount: 0,
                                                   enabled: false
            
                                                                });

        tokens[1] = Tokens_blueprint({ 
                                                   tokenAddress: 0x514910771AF9Ca656af840dff83E8264EcF986CA,  // LINK
                                                   pairAddress: 0xa2107FA5B38d9bbd2C461D6EDf11B11A50F6b974,
                                                   amount: 1e9,
                                                   enabled: true
            
                                                                });
        tokens[2] = Tokens_blueprint({ 
                                                   tokenAddress: 0xb9871cB10738eADA636432E86FC0Cb920Dc3De24,  // PRIA
                                                   pairAddress: 0xAc350EefCCdAE050614070E5040e17759Cebb3e9,
                                                   amount: 1e9,
                                                   enabled: false
            
                                                                });
                                                                
        tokens[3] = Tokens_blueprint({ 
                                                   tokenAddress: 0x6810e776880C02933D47DB1b9fc05908e5386b96,  // GNO
                                                   pairAddress: 0x3e8468f66d30Fc99F745481d4B383f89861702C6,
                                                   amount: 1e9,
                                                   enabled: true
            
                                                                });
                                                                
        tokens[4] = Tokens_blueprint({ 
                                                   tokenAddress: 0xc00e94Cb662C3520282E6f5717214004A7f26888,  // COMP
                                                   pairAddress: 0xCFfDdeD873554F362Ac02f8Fb1f02E5ada10516f,
                                                   amount: 1e9,
                                                   enabled: true
            
                                                                });
                                                                
        tokens[5] = Tokens_blueprint({ 
                                                   tokenAddress: 0xD5525D397898e5502075Ea5E830d8914f6F0affe,  // MEME
                                                   pairAddress: 0x5DFbe95925FFeb68f7d17920Be7b313289a1a583,
                                                   amount: 1e9,
                                                   enabled: true
            
                                                                });
                                                                
        tokens[6] = Tokens_blueprint({ 
                                                   tokenAddress: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2,  // WETH
                                                   pairAddress: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2,
                                                   amount: 1e9,
                                                   enabled: true
            
                                                                });
                                                                
        tokens[7] = Tokens_blueprint({ 
                                                   tokenAddress: 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599,  // WBTC
                                                   pairAddress: 0xBb2b8038a1640196FbE3e38816F3e67Cba72D940,
                                                   amount: 1e8,
                                                   enabled: true
            
                                                                });
                                                                
    }
    
    function modify_token(uint8 _token, address _tokenAddress, address _pairAddress, uint64 _amount, bool _enabled  ) onlyOwner public {
        
        tokens[_token] = Tokens_blueprint({ 
                                                   tokenAddress: _tokenAddress,
                                                   pairAddress: _pairAddress,
                                                   amount: _amount, // 9 decs
                                                   enabled: _enabled
            
                                                                });
                                                                
                                                                // add events
    }
    
    function modify_params(address _rewardsVault, uint8 _alchemyCut, uint8 _minAlchemists, uint16 _maxAlchemists, uint16 _minXpb, uint16 _minTime, bool _enabled  ) onlyOwner public {
        
        EngineParameters = Engine_params({ 
                                                   rewardsVault: _rewardsVault,
                                                   alchemyCut: _alchemyCut,
                                                   minAlchemists: _minAlchemists,
                                                   maxAlchemists: _maxAlchemists,
                                                   minXpb: _minXpb,
                                                   minTime: _minTime,
                                                   enabled: _enabled
                                                   
                                                                });
                                                                
    }
    
    function init_session() onlyOperator public {
        
        require(currentSession == nextSession -1, 'SESSION_ALREADY_OPEN');
        // require(transmutation_sessions[currentSession].complete == true, 'WAIT_FOR_PREV_SESSION');
        
        currentSession = nextSession;
        
        token_prices[currentSession][0] = getUniTokenPrice(tokens[0].pairAddress);
        token_prices[currentSession][1] = getUniTokenPrice(tokens[1].pairAddress);
        token_prices[currentSession][2] = getUniTokenPrice(tokens[2].pairAddress);
        token_prices[currentSession][3] = getUniTokenPrice(tokens[3].pairAddress);
        token_prices[currentSession][4] = getUniTokenPrice(tokens[4].pairAddress);
        token_prices[currentSession][5] = getUniTokenPrice(tokens[5].pairAddress);
        token_prices[currentSession][6] = 1e9;
        token_prices[currentSession][7] = getUniTokenPrice(tokens[7].pairAddress);
        
        transmutation_sessions[currentSession] = Transmute_registry_blueprint({ 
                                                                                randReqId: 0,
                                                                                randomness: 0,
                                                                                timestamp_init: uint32(block.timestamp),
                                                                                timestamp_end: 0,
                                                                                complete: false,
                                                                                vrf: false,
                                                                                total_entries: 0,
                                                                                total_xpb: 0
                                                                                                });
    }
    
    
    function submit_transmutation(uint8 formula, uint64 amount) public {
        
        require(EngineParameters.enabled == true, 'TRANSMUTATION_ENGINE_DISABLED');
        require(amount > 0, 'CMON_REALLY');
        require(currentSession == nextSession, 'SESSION_NOT_OPEN');
        require(tokens[formula].enabled == true, 'FORMULA_NOT_ENABLED');
        require(transmutation_sessions[currentSession].total_entries <= EngineParameters.maxAlchemists, 'ROUND_IS_FULL');
        
        
        IERC20(tokens[0].tokenAddress).transferFrom(msg.sender, EngineParameters.rewardsVault, amount*1e9);
        
        uint16 this_entry = transmutation_sessions[currentSession].total_entries + 1;
        uint64 total_xpb = transmutation_sessions[currentSession].total_xpb + amount;
        transmutation_sessions[currentSession].total_entries = this_entry;
        transmutation_sessions[currentSession].total_xpb = total_xpb;
        
        uint256 sentEtherValue = uint256(token_prices[currentSession][0]).mul(amount);  // 18dec 
        
        uint256 rewardEtherValue = uint256(token_prices[currentSession][formula]).mul(tokens[formula].amount).div(1e9);   
        
        uint256 transmuteChance = sentEtherValue.div(rewardEtherValue).mul(100-EngineParameters.alchemyCut).mul(65535).div(100).div(1e9);
        
        if(transmuteChance >= 65535){ 
            transmuteChance = 65535; 
        } 
        
        transmutation_sessions_entries[currentSession][this_entry] = Transmute_registry_entry({
                                                                                                alchemist: msg.sender,
                                                                                                reward: formula,
                                                                                                chance: uint16(transmuteChance)
                                                                                                // amount: amount,
                                                                                                // timestamp: uint32(block.timestamp),
                                                                                                // successful: false
                                                                                                                  });
                                                                                                                  
    }
    
    
    
    
    function close_session() onlyOperator public {
        
        require(currentSession == nextSession, 'SESSION_NOT_OPEN');
        
        require(block.timestamp >= (transmutation_sessions[currentSession].timestamp_init + EngineParameters.minTime), 'MIN_TIME_NOT_ELAPSED' );
        
        bytes32 reqId = requestRandomness(keyHash, fee, block.timestamp);
        
        transmutation_sessions[currentSession].randReqId = reqId;
        
        transmutation_sessions[currentSession].timestamp_end = uint32(block.timestamp);
        
        nextSession = nextSession +1;
        
    }
    
    
        /**
     * Callback function used by VRF Coordinator
     */
    function fulfillRandomness(bytes32 requestId, uint randomness) internal override {
        
        if(transmutation_sessions[currentSession].randReqId == requestId){
            transmutation_sessions[currentSession].randomness = uint16(randomness);     // obtain 5 digit randomness (0 - 65535)
            transmutation_sessions[currentSession].vrf = true;
        }
        
    }
    
    
    function complete_session(bool _distribute) onlyOperator public {
        
        require(currentSession == nextSession-1, 'SESSION_STILL_OPEN');
        require(transmutation_sessions[currentSession].complete == false, 'NO');
        
        if(_distribute == false){
            
            // do manual distribution for this round. 
            
        } else {
            
        if(transmutation_sessions[currentSession].vrf == true){
            uint16 i = 0;
            for (i = 0; i <= transmutation_sessions[currentSession].total_entries ; i++) { 
         if(transmutation_sessions_entries[currentSession][i].chance >= transmutation_sessions[currentSession].randomness){
             
             address _rewardAddress = tokens[transmutation_sessions_entries[currentSession][i].reward].tokenAddress;
             address _alchemistAddress = transmutation_sessions_entries[currentSession][i].alchemist;
             uint256 _rewardAmt = tokens[transmutation_sessions_entries[currentSession][i].reward].amount*1e9;
             
             IERC20(_rewardAddress).transferFrom(EngineParameters.rewardsVault, _alchemistAddress, _rewardAmt);
         }      
      }
           
            
        } else {
            // round forfeited. some problem occured, VRF not arriving, etc....
        }
        }
        transmutation_sessions[currentSession].complete = true;
        
        
    }
    
    function getUniTokenPrice(address  pair) public view returns(uint64 price){
        
        (uint256 reserve0, uint256 reserve1, uint32 blockTimestampLast) = IUniswapV2Pair(pair).getReserves();
        
        price = uint64(reserve0.mul(1e9).div(reserve1));
    }
    
    
    function recoverERC20Tokens(address _token, address _to, uint _value) public onlyOwner() {
        IERC20(_token).transfer(_to, _value);
    }
    
    

}
function fulfillRandomness(bytes32 requestId, uint randomness) internal override {
        
    if(transmutation_sessions[currentSession].randReqId == requestId){
        transmutation_sessions[currentSession].randomness = uint16(randomness);     // obtain 5 digit randomness (0 - 65535)
        transmutation_sessions[currentSession].vrf = true;
    }
        
}

/**
 * Callback function used by VRF Coordinator
 */