import time

from calcTools  import *
from osTools import *
from web3Tools import *




class MaxProfitUtils:
    # 判断两个数组是否有交集
    @staticmethod
    def has_intersection(arr1, arr2):
    # 1.这段感觉会慢
        return len(set(arr1) & set(arr2)) > 0

    # 若二维数组中所有数组与指定数组没有交集，返回True

    # 2.已经判断没有交集的路径，就不需要再去互相判断了。
    @staticmethod
    def all_has_no_intersection(swap_details_list, check_swap_details):
        for swap_details in swap_details_list:
            if MaxProfitUtils.has_intersection(swap_details['swapPath'], check_swap_details['swapPath']):
                return False

        return True

    # 求和swap详情list的利润并返回
    @staticmethod
    def get_profit_sum(swap_details_list):
        profit_sum = 0

        for swap_details in swap_details_list:
            profit_sum += swap_details['profit']

        return profit_sum

    @staticmethod
    def get_max_profit_swap_details_list(x, y):
        return x if MaxProfitUtils.get_profit_sum(x) > MaxProfitUtils.get_profit_sum(y) else y

    # 得出指定数据结构中的最大利润path
    @staticmethod
    def max_profit_path_list(origin_swap_details_list, level=0, swap_details_list=None):
        # first level
        if swap_details_list is None:
            swap_details_list = []

        # last level
        # >=
        if level == len(origin_swap_details_list):
            return swap_details_list

        # 此层的swap_path与当前的swap_path_list内的所有swap_path都没有交集的话，将此层的加入为x路径
        x = []
        if MaxProfitUtils.all_has_no_intersection(swap_details_list, origin_swap_details_list[level]):
            # 3.这段会很卡
            new_swap_details_list = json.loads(json.dumps(swap_details_list))
            new_swap_details_list.append(origin_swap_details_list[level])
            x = MaxProfitUtils.max_profit_path_list(origin_swap_details_list, level+1, new_swap_details_list)

        # 不将此层加入的y路径
        y = MaxProfitUtils.max_profit_path_list(origin_swap_details_list, level+1, swap_details_list)

        return MaxProfitUtils.get_max_profit_swap_details_list(x, y)


def calOnlyLpInRouterArray(routerArray):
    onlyLpArray = []
    for router in routerArray:
        for lpInfo in router:
            lp = lpInfo['lp']
            if lp in onlyLpArray:
                continue
            if lp in nowBlockLpRes:
                continue
            onlyLpArray.append(lp)
    return onlyLpArray



def calRouterProfit(router):
    # 整理LP信息
    for lpInfo in router:
        lpAddress = lpInfo['lp']
        r0 = nowBlockLpRes[lpAddress][0]
        r1 = nowBlockLpRes[lpAddress][1]
        if lpAddress not in topLpInfo:
            return False
        lpInfo['fee'] = topLpInfo[lpAddress]['fee']
        if lpInfo['inToken'] < lpInfo['outToken']:
            lpInfo['rIn'] = r0
            lpInfo['rOut'] = r1
        else:
            lpInfo['rIn'] = r1
            lpInfo['rOut'] = r0
    # ABA
    if len(router) == 2:
        tokenLoan = router[1]['outToken']
        tokenIn0  = router[0]['inToken']
        tokenLoanETHPrice = baseTokenInfo[tokenLoan]['price'] * 10 ** baseTokenInfo[WETH]['decimals'] /  10 ** baseTokenInfo[tokenLoan]['decimals']
        # WETH - A - WETH
        tradeInfo = calBestAmountV2V2(router[0]['rIn'], router[0]['rOut'], router[0]['fee'], router[1]['rIn'], router[1]['rOut'], router[1]['fee'] )
        profit = tradeInfo['profit']
        tradeInfo['ethProfit'] = profit * tokenLoanETHPrice
        #printTime(router, amountIn0, amountOut0, amountOut1, profit)
        
        if tradeInfo['ethProfit'] < tradeMinProfit:
            return False
        else:
            tradeInfo['lp0'] = router[0]['lp']
            tradeInfo['lp1'] = router[1]['lp']
            tradeInfo['fee0'] = router[0]['fee']
            tradeInfo['fee1'] = router[1]['fee']
            tradeInfo['indexIn0'] = router[0]['indexIn']
            tradeInfo['indexIn1'] = router[1]['indexIn']
            tradeInfo['tokenLoan'] = tokenLoan
            tradeInfo['tokenIn0'] = tokenIn0
            tradeInfo['tradeToken0'] = router[0]['outToken']
            tradeInfo['type'] = 'type2'
            return tradeInfo

    # ABCA
    elif len(router) == 3:
        tokenLoan = router[2]['outToken']
        tokenIn0  = router[0]['inToken']
        tokenLoanETHPrice = baseTokenInfo[tokenLoan]['price'] * 10 ** baseTokenInfo[WETH]['decimals'] /  10 ** baseTokenInfo[tokenLoan]['decimals']
        tradeInfo = calBestAmountABCA(router[0]['rIn'], router[0]['rOut'], router[0]['fee'],
                                      router[1]['rIn'], router[1]['rOut'], router[1]['fee'],
                                      router[2]['rIn'], router[2]['rOut'], router[2]['fee'])
        profit = tradeInfo['profit']
        tradeInfo['ethProfit'] = profit * tokenLoanETHPrice
        #printTime(router, amountIn0, amountOut0, amountOut1, amountOut2, profit)
        if tradeInfo['ethProfit'] < tradeMinProfit:
            return False
        else:
            tradeInfo['lp0'] = router[0]['lp']
            tradeInfo['lp1'] = router[1]['lp']
            tradeInfo['lp2'] = router[2]['lp']
            tradeInfo['fee0'] = router[0]['fee']
            tradeInfo['fee1'] = router[1]['fee']
            tradeInfo['fee2'] = router[2]['fee']
            tradeInfo['indexIn0'] = router[0]['indexIn']
            tradeInfo['indexIn1'] = router[1]['indexIn']
            tradeInfo['indexIn2'] = router[2]['indexIn']
            tradeInfo['tokenLoan'] = tokenLoan
            tradeInfo['tokenIn0'] = tokenIn0
            tradeInfo['tradeToken0'] = router[0]['outToken']
            tradeInfo['tradeToken1'] = router[1]['outToken']
            tradeInfo['type'] = 'type3'
            return tradeInfo

def callABCA(lp0, lp1, lpLoan, tokenIn0, tokenLoan, fee0, fee1, fee2, lp0InIndex, lp1InIndex, lp2InIndex, minProfit, block = 'latest'):
    function = '0x1d650c35'
    inputData = encodeInput(lp0, lp1, lpLoan, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                fee0, fee1, fee2, lp0InIndex, lp1InIndex, lp2InIndex,
                tokenIn0, tokenLoan, minProfit, 0)
    inputData = function + inputData
    r = gasCal(myAddress, v3CCAddress, inputData, block)
    tradeInfo = {'type':3, 'lp0': lp0, 'lp1': lp1, 'lpLoan':lpLoan, 'tokenIn':tokenIn0, 'tokenLoan':tokenLoan, 'profit':0, 'sumValue':0}
    if len(r['info']) != 23 or r['info'][-1] == 0:
        return tradeInfo
    sumValue = r['info'][3] + r['info'][4] + r['info'][5] + r['info'][6] + r['info'][7] + r['info'][8]
    tradeInfo['sumValue'] = sumValue
    tradeInfo['profit'] = r['info'][-1]
    tradeInfo['gasUsed'] = r['gasUsed']
    tradeInfo['inputData'] = inputData
    return tradeInfo

def callABA(lp0, lp1, tokenIn0, tokenLoan, fee0, fee1, lp0InIndex, lp1InIndex, minProfit, block = 'latest'):
    function = '0xa628df6d'
    inputData = encodeInput(lp0, lp1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                fee0, fee1, 0, lp0InIndex, lp1InIndex, 0,
                tokenIn0, tokenLoan, minProfit, 0)
    inputData = function + inputData
    r = gasCal(myAddress, v3CCAddress, inputData, block)
    tradeInfo = {'type':2, 'lp0': lp0, 'lpLoan':lp1, 'tokenIn':tokenIn0, 'tokenLoan':tokenLoan, 'profit':0, 'sumValue':0}
    if len(r['info']) != 23 or r['info'][-1] == 0:
        return tradeInfo
    sumValue = r['info'][3] + r['info'][4] + r['info'][5] + r['info'][6] + r['info'][7] + r['info'][8]
    tradeInfo['sumValue'] = sumValue
    tradeInfo['profit'] = r['info'][-1]
    tradeInfo['gasUsed'] = r['gasUsed']
    tradeInfo['inputData'] = inputData
    return tradeInfo

def readABA():
    array = []
    for i in baseTokenInfo:
        i = i.lower()
        iiArray = readText('aba' + i + i)
        array = array + iiArray
    array = array + readText('aba' + WETHW + WETH)
    array = array + readText('aba' + WETH + WETHW)
    return array

def readABCA():
    array = []
    for i in baseTokenInfo:
        i = i.lower()
        iiArray = readText('abca' + i + i)
        array = array + iiArray
    array = array + readText('abca' + WETHW + WETH)
    array = array + readText('abca' + WETH + WETHW)
    return array

topLpInfo  = readText('topLP')
lpRouterInfo = readABCA()
lpRouterInfo = lpRouterInfo + readABA()

printTime('v3 start')
v3CCAddress = '0xB4971D0dda22359Ad86867362b7FC3206eA0d86B'
dataUsed = []
nowBlockLpRes = {}
initBlock = 15879488
while True:
    try:

        blockNumber = initBlock + 1
        if blockNumber == initBlock:
            time.sleep(0.1)
            continue
        printTime(blockNumber, 'start')
        initBlock = blockNumber 
        baseGasPrice = getBaseGasPrice(blockNumber) * 1.5
        if baseGasPrice < 1.6 * 1e9:
            baseGasPrice = 1.6 * 1e9

        nowBlockLpRes = {}
        # 找出有关该LP的路由
        lpRouterArray = lpRouterInfo  # findLpRouter(nowBlockLpTradeI, lpRouterInfo)

        # 过滤需要获取余额的路由
        lpOnlyRouterArray = calOnlyLpInRouterArray(lpRouterArray)

        # printTime(len(lpOnlyRouterArray), len(lpRouterArray))
        # 获取余额
        lpResArray = w3.dex.getPairSBalance(lpOnlyRouterArray, blockNumber)
        # 余额进行缓存
        for lpResI in lpResArray:
            nowBlockLpRes[lpResI] = lpResArray[lpResI]
            #计算每一个路由的利润
        tradeInfoArray = []
        initForTime = time.time()
        for lpRouter in lpRouterArray:
            if time.time() - initForTime > 60:
                printTime(blockNumber, 'lpRouterArray long time')
            tradeInfo = calRouterProfit(lpRouter)
            if tradeInfo:
                type = tradeInfo['type']

                if type == 'type2':
                    result = callABA(tradeInfo['lp0'], tradeInfo['lp1'],
                                    tradeInfo['tokenIn0'], tradeInfo['tokenLoan'],
                                    tradeInfo['fee0'], tradeInfo['fee1'],
                                    tradeInfo['indexIn0'], tradeInfo['indexIn1'],
                                    baseTokenInfo[tradeInfo['tokenIn0']]['profitMin'], blockNumber)
                    if result['profit'] < tradeMinProfit:
                        continue
                    sumValue = result['sumValue']
                    if sumValue in dataUsed:
                        continue
                    tokenLoanETHPrice = baseTokenInfo[tradeInfo['tokenLoan']]['price'] * 10 ** baseTokenInfo[WETH]['decimals'] / 10 ** \
                                        baseTokenInfo[tradeInfo['tokenLoan']]['decimals']
                    result['ethProfit'] = result['profit'] * tokenLoanETHPrice
                    result['ethProfit-E18'] = result['profit'] * tokenLoanETHPrice / 1e18
                    result['tureProfit'] = result['ethProfit'] - result['gasUsed'] * baseGasPrice
                    result['maxPrice'] = result['ethProfit'] / result['gasUsed']
                    # if tradeInfo['lp1'] =='0xd75ea151a61d06868e31f8988d28dfe5e9df57b4':
                    #     printTime(result)
                    if result['tureProfit'] < tradeMinProfit:
                        continue
                    tradeInfoArray.append(result)

                if type == 'type3':
                    result = callABCA(tradeInfo['lp0'], tradeInfo['lp1'], tradeInfo['lp2'],
                                    tradeInfo['tokenIn0'], tradeInfo['tokenLoan'],
                                    tradeInfo['fee0'], tradeInfo['fee1'],tradeInfo['fee2'],
                                    tradeInfo['indexIn0'], tradeInfo['indexIn1'], tradeInfo['indexIn2'],
                                    baseTokenInfo[tradeInfo['tokenIn0']]['profitMin'], blockNumber)
                    if result['profit'] < tradeMinProfit:
                        continue
                    sumValue = result['sumValue']
                    if sumValue in dataUsed:
                        continue
                    tokenLoanETHPrice = baseTokenInfo[tradeInfo['tokenLoan']]['price'] * 10 ** baseTokenInfo[WETH][
                        'decimals'] / 10 ** \
                                        baseTokenInfo[tradeInfo['tokenLoan']]['decimals']
                    result['ethProfit'] = result['profit'] * tokenLoanETHPrice
                    result['ethProfit-E18'] = result['profit'] * tokenLoanETHPrice / 1e18
                    result['tureProfit'] = result['ethProfit'] - result['gasUsed'] * baseGasPrice
                    result['maxPrice'] = result['ethProfit'] / result['gasUsed']
                    if result['tureProfit'] < tradeMinProfit:
                        continue
                    tradeInfoArray.append(result)


        if len(tradeInfoArray) != 0 :
            #printTime('trade List' ,blockNumber, tradeInfoArray)
            tradeList = []
            initForTime = time.time()
            for tradeInfoI in tradeInfoArray:
                if time.time() - initForTime > 60:
                    printTime(blockNumber, 'tradeInfoArray long time')
                if tradeInfoI['type'] == 2:
                    tradeList.append({"swapPath": [tradeInfoI['lp0'], tradeInfoI['lpLoan']], "profit": tradeInfoI['tureProfit'], 'data':tradeInfoI})
                if tradeInfoI['type'] == 3:
                    tradeList.append(
                        {"swapPath": [tradeInfoI['lp0'], tradeInfoI['lp1'],tradeInfoI['lpLoan']], "profit": tradeInfoI['tureProfit'], 'data':tradeInfoI})
            
            
            initbestTradeListTime = time.time()
            initTradeList = tradeList
            tradeList = sorted(tradeList, key=lambda k: k['profit'], reverse=True)
            params = []
            for info in tradeList:
                paramsI = {}
                paramsI['from'] = myAddress
                paramsI['to'] = '0xEAf90BcB75e7B92900678c4aBD80883b414bEDD6'
                tradeInputData = info['data']['inputData']
                paramsI['data'] = '0x4327f0db' + encodeInput(v4Address, 0x40, (len(tradeInputData) - 2) / 2, tradeInputData)
                params.append(paramsI)

            batchCallResult = batchCall(params, blockNumber)
            batchCallResult = decodeBatchCall(batchCallResult['result']) 
            totalProfit = 0
            length = len(tradeList)
            bestIndex = []
            for i in range(0, length):
                profit = batchCallResult[i][-1]
                if profit > 0:
                    tokenLoan = batchCallResult[i][-3]
                    gasUsed = batchCallResult[i][1]
                    tokenLoanETHPrice = baseTokenInfo[tokenLoan]['price'] * 10 ** baseTokenInfo[WETH][
                        'decimals'] / 10 ** \
                                        baseTokenInfo[tokenLoan]['decimals']
                    profit = profit * tokenLoanETHPrice
                    profit = profit - gasUsed * baseGasPrice  
                    if profit < tradeMinProfit:
                        continue                 
                    totalProfit = totalProfit + profit      
                    bestIndex.append(i) 

            printTime(blockNumber, 'batch', time.time() - initbestTradeListTime, '  s totalProfit', totalProfit/1e18)
            bestTradeList = []
            for index in bestIndex:
                bestTradeList.append(tradeList[index])
            
            for i in bestTradeList:
                printTime(i['data']['tokenIn'])

            # 最优解法
            initbestTradeListTime = time.time()
            newMinProfit = tradeMinProfit
            while len(tradeList) > 50:
                newMinProfit = newMinProfit * 1.1
                newTradeList = []
                for tradeI in tradeList:
                    if tradeI['profit'] > newMinProfit:
                        newTradeList.append(tradeI)
                tradeList = newTradeList
            bestTradeList = MaxProfitUtils.max_profit_path_list(tradeList)

            totalProfit = 0
            for tradeI in bestTradeList:
                totalProfit = totalProfit + tradeI['profit']    
            printTime(blockNumber, 'best ', time.time() - initbestTradeListTime, '  s totalProfit', totalProfit/1e18)
            for i in bestTradeList:
                printTime(i['data']['tokenIn'])

            printTime('-' * 50)
    except BaseException as err:
        printTime(traceback.format_exc())
        printTime('loop', )
        time.sleep(1)








        
