contract ArbContract{
OrFeedInterface orfeed = OrFeedInterface(0x8316b082621cfedab95bf4a44a1d4b64a6ffc336);
address flashAddress = 0x398eC7346DcD622eDc5ae82352F02bE94C62d119;

   address owner;
   bytes  PERM_HINT = "PERM";


    string[] latestTokens;
      uint256 latestAmount;
     string[] latestExchanges;
     address latestFirstTokenAddress;
     address latestReturnAddress;

   mapping (uint256=>uint256) results;
    mapping(uint256=>uint8[3]) public orders;
    
      address kyberProxyAddress = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755;
     IKyberNetworkProxy kyberProxy = IKyberNetworkProxy(kyberProxyAddress);
      
      
         IContractRegistry contractRegistry = IContractRegistry(0x52Ae12ABe5D8BD778BD5397F99cA900624CfADD4);
        
        //
     IBancorNetwork bancorNetwork = IBancorNetwork(0x3Ab6564d5c214bc416EE8421E05219960504eeAD);
         IBancorNetworkPathFinder bancorNetworkPathFinder = IBancorNetworkPathFinder(0x6F0cD8C4f6F06eAB664C7E3031909452b4B72861);
    

    
    address uniswapFactoryAddress = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95;
    IUniswapFactory uniswapFactory = IUniswapFactory(uniswapFactoryAddress);
     bytes  theBytes;
     
    
      
      constructor() public payable {
            owner = msg.sender; 
 
         
     
       
        
        
            
          
        }
        
         modifier onlyOwner() {
            if (msg.sender != owner) {
                throw;
            }
             _;
        }
        
   function kill() onlyOwner{
       selfdestruct(owner);
   }
    
    function() payable{
        
    }
    
    function getTokensBack(address tokenAddress){
        ERC20 token = ERC20(tokenAddress);
        token.transfer(owner, token.balanceOf(this));
    }
    













 
   
   
      function bancorConvert(address token1, address token2, uint256 amount)  returns (uint256){
      
        if (token1 == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE || token1 == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2){
            // the EtherToken addresss for BancorNetwork
            token1 = 0xc0829421C1d260BD3cB3E0F06cfE2D52db2cE315;
        }
        if (token2 == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE || token2 == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2){
            token2 = 0xc0829421C1d260BD3cB3E0F06cfE2D52db2cE315;
        }
        
        address[] memory addressPath;
      
        
            addressPath = bancorNetworkPathFinder.generatePath(token2, token1);
/*
        IERC20Token[] memory tokenPath = new IERC20Token[](addressPath.length);
        
        for(uint256 i = 0; i < addressPath.length; i++) {
            tokenPath[i] = IERC20Token(addressPath[i]);
        }
       */
       
       uint256 price = bancorNetwork.convert2.value(amount)(addressPath, amount,1 ,0x0,0);
       return price;
    }
    
    
    
 
    function arbIt2(uint[] eOrder, string[] memory tOrder1, uint256 amount, bool back) {
        uint256 final1 = eOrder.length -1;
        uint lastSell = amount;
        address [] tOrder;
       
        
        for(uint j=0; j<tOrder1.length; j++){
            tOrder[j] =orfeed.getTokenAddress(tOrder1[j]);
        }
        for(uint i =0; i<eOrder.length; i++){
            uint256 next = i+1;
            if(i < final1){
               if(eOrder[i] ==1){
                   //kyber buy
                   lastSell = swapTokenOnKyber(tOrder[i], lastSell, tOrder[next]);
               }
               else if(eOrder[i] ==2){
                   lastSell = swapTokenOnUniswap(tOrder[i], lastSell, tOrder[next]);
               }
               else{
                 lastSell = bancorConvert2(tOrder[next], tOrder[i], lastSell);
               }
            }
            else{
                 //sell
                 
                 if(back == true){
               if(eOrder[i] ==1){
                   //kyber buy
                   lastSell = swapTokenOnKyber(tOrder[i], lastSell, tOrder[0]);
               }
               else if(eOrder[i] ==2){
                  lastSell = swapTokenOnUniswap(tOrder[i], lastSell, tOrder[0]);
               }
               else{
                 lastSell = bancorConvert2(tOrder[0], tOrder[i], lastSell);
               }
            }
            }
        }
    }


    
    
      function arb(address fundsReturnToAddress, address liquidityProviderContractAddress, string[] memory tokens,  uint256 amount, string[] exchanges) payable returns(bool){
          if(liquidityProviderContractAddress == fundsReturnToAddress){
               arb1(fundsReturnToAddress, liquidityProviderContractAddress, tokens, amount, exchanges);
          }
          else{
               FlashLoanInterface flash = FlashLoanInterface(liquidityProviderContractAddress);
            if(equal(exchanges[0], "SKIP")){
              string[]   memory newTokens = new string[](tokens.length-1);
              
               string[]  memory newExchanges = new string[](exchanges.length-1);
               
               //doesnt work so lets go with literal due to memory types restruct etc
               //newTokens = remove(0, tokens);
              //newExchanges = remove(0, exchanges);
              
              if(exchanges.length==3){
              newTokens[0]=tokens[1];
              newTokens[1]=tokens[2];
         


              newExchanges[0]=exchanges[1];
              newExchanges[1]=exchanges[2];
          
            
              }
              


              else if(exchanges.length==4){
              newTokens[0]=tokens[1];
              newTokens[1]=tokens[2];
              newTokens[2]=tokens[3];


              newExchanges[0]=exchanges[1];
              newExchanges[1]=exchanges[2];
              newExchanges[2]=exchanges[3];
            
              }

             else  if(exchanges.length==5){
              newTokens[0]=tokens[1];
              newTokens[1]=tokens[2];
              newTokens[2]=tokens[3];
              newTokens[3]=tokens[4];


              newExchanges[0]=exchanges[1];
              newExchanges[1]=exchanges[2];
              newExchanges[2]=exchanges[3];
              newTokens[3]=tokens[4];
            
              }

               else{
              newTokens[0]=tokens[1];
              newTokens[1]=tokens[2];
              newTokens[2]=tokens[3];
              newTokens[3]=tokens[4];
              newTokens[4]=tokens[5];


              newExchanges[0]=exchanges[1];
              newExchanges[1]=exchanges[2];
              newExchanges[2]=exchanges[3];
              newTokens[3]=tokens[4];
              newTokens[4]=tokens[5];
            
              }
              

              
            
                latestTokens = newTokens;
            latestAmount = amount;
            latestExchanges = newExchanges;
            latestReturnAddress = fundsReturnToAddress;
           
            latestFirstTokenAddress = orfeed.getTokenAddress(newTokens[0]);
           
            flash.flashLoan(this, latestFirstTokenAddress, amount, theBytes);

            }

            else{

            latestTokens = tokens;
            latestAmount = amount;
            latestExchanges = exchanges;
            latestReturnAddress = fundsReturnToAddress;
             latestFirstTokenAddress = orfeed.getTokenAddress(tokens[0]);
           
            flash.flashLoan(this, latestFirstTokenAddress, amount, theBytes);
          }

          }
      
         return true;
       }


        function remove(uint index, string[] array)  internal returns(string[]) {
        if (index >= array.length) return;
        string[] memory arrayCreate;
        for (uint i = index; i<array.length-1; i++){
            arrayCreate[i] = array[i+1];
        }
        //delete array[array.length-1];
        //array.length = array.length -1;
        return arrayCreate;
    }
    /*
       function remove2(uint index, string[] storage array) internal returns(string[]) {
        if (index >= array.length) return;

        for (uint i = index; i<array.length-1; i++){
            array[i] = array[i+1];
        }
        delete array[array.length-1];
        array.length--;
        return array;
    }
*/




    
     function arb1(address fundsReturnToAddress, address liquidityProviderContractAddress, string[] memory tokens,  uint256 amount, string[] exchanges)  returns(bool){
         
        // uint[] storage eOrder1;
         uint[] memory eOrder1 = new uint[](exchanges.length);
         for(uint i =0; i<exchanges.length; i++){

           if(equal(exchanges[i], "KYBER")){
               eOrder1[i] =1;
               
           }

           else if(equal(exchanges[i], "BANCOR")){
               eOrder1[i] =3;
               
           }
          
           else{
               eOrder1[i] =2;
           }
           
           
         }
         
         uint lastVar = tokens.length-1;
         bool back = false;


     arbIt3(eOrder1, tokens, amount, back);
      
         uint tLast = tokens.length-1;
         address lastToken = orfeed.getTokenAddress(tokens[tLast]);
         ERC20 lToken = ERC20(lastToken);
         uint256 balanceHere = lToken.balanceOf(this);
         lToken.transfer(fundsReturnToAddress, balanceHere);
    
         return true;

         
         
         
     }
     
     
     function arbIt3(uint[] eOrder, string[] memory tOrder1, uint256 amount, bool back) {
         arbIt2(eOrder, tOrder1, amount, back);
         
     }
 
   
   
         function bancorConvert2(address token1, address token2, uint256 amount)  returns (uint256){
        // updated with the newest address of the BancorNetwork contract deployed under the circumstances of old versions of `getReturnByPath`
        IContractRegistry contractRegistry = IContractRegistry(0x52Ae12ABe5D8BD778BD5397F99cA900624CfADD4);
        
        //
  // IBancorNetwork bancorNetwork = IBancorNetwork(contractRegistry.addressOf(0x42616e636f724e6574776f726b));
    IBancorNetwork bancorNetwork = IBancorNetwork(0x3Ab6564d5c214bc416EE8421E05219960504eeAD);
   //
       // IBancorNetworkPathFinder bancorNetworkPathFinder = IBancorNetworkPathFinder(contractRegistry.addressOf(0x42616e636f724e6574776f726b5061746846696e646572));
         IBancorNetworkPathFinder bancorNetworkPathFinder = IBancorNetworkPathFinder(0x6F0cD8C4f6F06eAB664C7E3031909452b4B72861);
       // address token1ToBancor = token1;
        //address token2ToBancor = token2;
        // in case of Ether (or Weth), we need to provide the address of the EtherToken to the BancorNetwork
        
        if (token1 == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE || token1 == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2){
            // the EtherToken addresss for BancorNetwork
            token1 = 0xc0829421C1d260BD3cB3E0F06cfE2D52db2cE315;
        }
        if (token2 == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE || token2 == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2){
            token2 = 0xc0829421C1d260BD3cB3E0F06cfE2D52db2cE315;
        }
        
        address[] memory addressPath;
      
        
            addressPath = bancorNetworkPathFinder.generatePath(token2, token1);
/*
        IERC20Token[] memory tokenPath = new IERC20Token[](addressPath.length);
        
        for(uint256 i = 0; i < addressPath.length; i++) {
            tokenPath[i] = IERC20Token(addressPath[i]);
        }
       */
       ERC20 token = ERC20(token1);
       ERC20 tokenT = ERC20(token2);
       
       uint startAmount =token.balanceOf(this);
       token.approve(0x3Ab6564d5c214bc416EE8421E05219960504eeAD, 8000000000000000000000000000000);
       tokenT.approve(0x3Ab6564d5c214bc416EE8421E05219960504eeAD, 8000000000000000000000000000000);
       //"0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2", "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359",  10000000000
       uint256 price = bancorNetwork.claimAndConvert2(addressPath, amount,1 ,0x0,0);
        return token.balanceOf(this) - startAmount;
    }
    
     function swapTokenOnKyber(address sellToken1, uint sellTokenAmount, address  buyToken1)  returns (uint) {
    // Approve tokens so network can take them during the swap
    
     ERC20 sellToken = ERC20(sellToken1);
    ERC20 buyToken = ERC20(buyToken1);
    uint startAmount =buyToken.balanceOf(this);
      //uint256 minRate = 0;
      //(, minRate) = kyberProxy.getExpectedRate(buyToken, sellToken, sellTokenAmount);
      sellToken.approve(address(kyberProxy), sellTokenAmount);

      uint buyTokenAmount = kyberProxy.tradeWithHint(sellToken, sellTokenAmount, buyToken, address(this), 8000000000000000000000000000000000000000000000000000000000000000, 0, 0x0000000000000000000000000000000000000004, PERM_HINT);
      return buyToken.balanceOf(this) - startAmount;
  }
  
   
   function swapTokenOnUniswap(address sellToken1, uint sellTokenAmount, address buyToken1)  returns (uint) {
    ERC20 sellToken = ERC20(sellToken1);
    ERC20 buyToken = ERC20(buyToken1);
   uint startAmount =buyToken.balanceOf(this);
    uint256 minTokensBought = 1;
    uint256 minEtherBought = 1;
    address exchangeAddress = uniswapFactory.getExchange(address(sellToken));
    IUniswapExchange exchange = IUniswapExchange(exchangeAddress);
    sellToken.approve(address(exchange), sellTokenAmount);
    uint256 buyTokenAmount = exchange.tokenToTokenSwapInput(sellTokenAmount, minTokensBought, minEtherBought, block.timestamp, address(buyToken));
    return buyToken.balanceOf(this) - startAmount;
  }
    
 
  function compare(string _a, string _b) returns (int) {
        bytes memory a = bytes(_a);
        bytes memory b = bytes(_b);
        uint minLength = a.length;
        if (b.length < minLength) minLength = b.length;
        //@todo unroll the loop into increments of 32 and do full 32 byte comparisons
        for (uint i = 0; i < minLength; i ++)
            if (a[i] < b[i])
                return -1;
            else if (a[i] > b[i])
                return 1;
        if (a.length < b.length)
            return -1;
        else if (a.length > b.length)
            return 1;
        else
            return 0;
    }
    
    function equal(string _a, string _b) returns (bool) {
        return compare(_a, _b) == 0;
    }
    
    function indexOf(string _haystack, string _needle) returns (int)
    {
        bytes memory h = bytes(_haystack);
        bytes memory n = bytes(_needle);
        if(h.length < 1 || n.length < 1 || (n.length > h.length)) 
            return -1;
        else if(h.length > (2**128 -1)) // since we have to be able to return -1 (if the char isn't found or input error), this function must return an "int" type with a max length of (2^128 - 1)
            return -1;                                  
        else
        {
            uint subindex = 0;
            for (uint i = 0; i < h.length; i ++)
            {
                if (h[i] == n[0]) // found the first char of b
                {
                    subindex = 1;
                    while(subindex < n.length && (i + subindex) < h.length && h[i + subindex] == n[subindex]) // search until the chars don't match or until we reach the end of a or b
                    {
                        subindex++;
                    }   
                    if(subindex == n.length)
                        return int(i);
                }
            }
            return -1;
        }   
    }



     function executeOperation(
        address _reserve,
        uint256 _amount,
        uint256 _fee,
        bytes  _params) external {


    ERC20 theToken = ERC20(_reserve);
      

        arb1(this, this, latestTokens, latestAmount, latestExchanges);

        uint256 thisContractBalanceNow = theToken.balanceOf(this);
        uint256 userBalance = theToken.balanceOf(latestReturnAddress);

        uint256 userPlusContract = userBalance+thisContractBalanceNow;

        uint256 amountAndFee = (_amount+ _fee);
      //  string memory amountString = uint2str(amountAndFee);
       // string memory thisContractBalanceNow1 = uint2str(thisContractBalanceNow);
        //require(thisContractBalanceNow < amountAndFee, "The transaction did not return a profit + fee of "+  amountString+ ". Amount returned was "+ thisContractBalanceNow1 );
       require(thisContractBalanceNow < amountAndFee ||  userPlusContract < amountAndFee , "The transaction did not return a profit + fee. Amount returned was not enough ");
        
        if(thisContractBalanceNow < amountAndFee){
          uint256 difference = amountAndFee - thisContractBalanceNow;
          require(theToken.transferFrom(latestReturnAddress, this, difference), "You dont have a high enough balance or appropriate approvals to do this transaction to cover your loan.");
        
        }

        theToken.transfer(0x3dfd23a6c5e8bbcfc9581d2e864a68feb6a076d3, (_amount+ _fee));
        //return the remainder
        thisContractBalanceNow = theToken.balanceOf(this);
        theToken.transfer(latestReturnAddress, thisContractBalanceNow);


    
    }


    
}
function arb1(address fundsReturnToAddress, address liquidityProviderContractAddress, string[] memory tokens,  uint256 amount, string[] exchanges)  returns(bool){
         
        // uint[] storage eOrder1;
         uint[] memory eOrder1 = new uint[](exchanges.length);
         for(uint i =0; i<exchanges.length; i++){

           if(equal(exchanges[i], "KYBER")){
               eOrder1[i] =1;
               
           }

           else if(equal(exchanges[i], "BANCOR")){
               eOrder1[i] =3;
               
           }
          
           else{
               eOrder1[i] =2;
           }
           
           
         }
         
         uint lastVar = tokens.length-1;
         bool back = false;


     arbIt3(eOrder1, tokens, amount, back);
      
         uint tLast = tokens.length-1;
         address lastToken = orfeed.getTokenAddress(tokens[tLast]);
         ERC20 lToken = ERC20(lastToken);
         uint256 balanceHere = lToken.balanceOf(this);
         lToken.transfer(fundsReturnToAddress, balanceHere);
    
         return true;

         
         
         
     }
/*
 function remove2(uint index, string[] storage array) internal returns(string[]) {
 if (index >= array.length) return;
 
 for (uint i = index; i<array.length-1; i++){
 array[i] = array[i+1];
 }
 delete array[array.length-1];
 array.length--;
 return array;
 }
 */