import BN from 'bn.js';

import { TickLayout } from '../layout';
import { mulDivFloor, wrappingSubU128 } from './bigNum';
import { Q64 } from './constants';

export class PositionUtils {

  static getfeeGrowthInside(
    poolState: {
      tickCurrent: number,
      feeGrowthGlobalX64A: BN,
      feeGrowthGlobalX64B: BN,
    },
    tickLowerState: ReturnType<typeof TickLayout.decode>,
    tickUpperState: ReturnType<typeof TickLayout.decode>
  ): { feeGrowthInsideX64A: BN, feeGrowthInsideBX64: BN } {
    let feeGrowthBelowX64A = new BN(0)
    let feeGrowthBelowX64B = new BN(0)
    if (poolState.tickCurrent >= tickLowerState.tick) {
      feeGrowthBelowX64A = tickLowerState.feeGrowthOutsideX64A
      feeGrowthBelowX64B = tickLowerState.feeGrowthOutsideX64B
    } else {
      feeGrowthBelowX64A = wrappingSubU128(poolState.feeGrowthGlobalX64A, tickLowerState.feeGrowthOutsideX64A)
      feeGrowthBelowX64B = wrappingSubU128(poolState.feeGrowthGlobalX64B, tickLowerState.feeGrowthOutsideX64B)
    }

    let feeGrowthAboveX64A = new BN(0)
    let feeGrowthAboveX64B = new BN(0)
    if (poolState.tickCurrent < tickUpperState.tick) {
      feeGrowthAboveX64A = tickUpperState.feeGrowthOutsideX64A
      feeGrowthAboveX64B = tickUpperState.feeGrowthOutsideX64B
    } else {
      feeGrowthAboveX64A = wrappingSubU128(poolState.feeGrowthGlobalX64A, tickUpperState.feeGrowthOutsideX64A)
      feeGrowthAboveX64B = wrappingSubU128(poolState.feeGrowthGlobalX64B, tickUpperState.feeGrowthOutsideX64B)
    }

    const feeGrowthInsideX64A = wrappingSubU128(
      wrappingSubU128(poolState.feeGrowthGlobalX64A, feeGrowthBelowX64A),
      feeGrowthAboveX64A,
    )
    const feeGrowthInsideBX64 = wrappingSubU128(
      wrappingSubU128(poolState.feeGrowthGlobalX64B, feeGrowthBelowX64B),
      feeGrowthAboveX64B,
    )
    return { feeGrowthInsideX64A, feeGrowthInsideBX64 }
  }

  static GetPositionFees(
    ammPool: {
      tickCurrent: number,
      feeGrowthGlobalX64A: BN,
      feeGrowthGlobalX64B: BN,
    },
    positionState: {
      liquidity: BN
      feeGrowthInsideLastX64A: BN
      feeGrowthInsideLastX64B: BN
      tokenFeesOwedA: BN
      tokenFeesOwedB: BN
    },
    tickLowerState: ReturnType<typeof TickLayout.decode>,
    tickUpperState: ReturnType<typeof TickLayout.decode>,
  ): { tokenFeeAmountA: BN, tokenFeeAmountB: BN } {
    const { feeGrowthInsideX64A, feeGrowthInsideBX64 } = this.getfeeGrowthInside(
      ammPool,
      tickLowerState,
      tickUpperState,
    )

    const feeGrowthdeltaA = mulDivFloor(
      wrappingSubU128(feeGrowthInsideX64A, positionState.feeGrowthInsideLastX64A),
      positionState.liquidity,
      Q64,
    )
    const tokenFeeAmountA = positionState.tokenFeesOwedA.add(feeGrowthdeltaA)

    const feeGrowthdelta1 = mulDivFloor(
      wrappingSubU128(feeGrowthInsideBX64, positionState.feeGrowthInsideLastX64B),
      positionState.liquidity,
      Q64,
    )
    const tokenFeeAmountB = positionState.tokenFeesOwedB.add(feeGrowthdelta1)

    return { tokenFeeAmountA, tokenFeeAmountB }
  }

  static GetPositionRewards(
    ammPool: { tickCurrent: number, rewardInfos: { growthGlobalX64: BN }[] },
    positionState: { liquidity: BN, rewardInfos: { growthInsideLastX64: BN, rewardAmountOwed: BN }[] },
    tickLowerState: ReturnType<typeof TickLayout.decode>,
    tickUpperState: ReturnType<typeof TickLayout.decode>,
  ): BN[] {
    const rewards: BN[] = []

    const rewardGrowthsInside = this.getRewardGrowthInside(
      ammPool.tickCurrent,
      tickLowerState,
      tickUpperState,
      ammPool.rewardInfos,
    )
    for (let i = 0; i < rewardGrowthsInside.length; i++) {
      const rewardGrowthInside = rewardGrowthsInside[i]
      const currRewardInfo = positionState.rewardInfos[i]

      const rewardGrowthDelta = wrappingSubU128(rewardGrowthInside, currRewardInfo.growthInsideLastX64)
      const amountOwedDelta = mulDivFloor(rewardGrowthDelta, positionState.liquidity, Q64)
      const rewardAmountOwed = currRewardInfo.rewardAmountOwed.add(amountOwedDelta)
      rewards.push(rewardAmountOwed)
    }
    return rewards
  }

  static getRewardGrowthInside(
    tickCurrentIndex: number,
    tickLowerState: ReturnType<typeof TickLayout.decode>,
    tickUpperState: ReturnType<typeof TickLayout.decode>,
    rewardInfos: { growthGlobalX64: BN }[]
  ): BN[] {
    const rewardGrowthsInside: BN[] = []
    for (let i = 0; i < rewardInfos.length; i++) {
      let rewardGrowthsBelow = new BN(0)
      if (tickLowerState.liquidityGross.eqn(0)) {
        rewardGrowthsBelow = rewardInfos[i].growthGlobalX64
      } else if (tickCurrentIndex < tickLowerState.tick) {
        rewardGrowthsBelow = wrappingSubU128(rewardInfos[i].growthGlobalX64, tickLowerState.rewardGrowthsOutsideX64[i])
      } else {
        rewardGrowthsBelow = tickLowerState.rewardGrowthsOutsideX64[i]
      }

      let rewardGrowthsAbove = new BN(0)
      if (tickUpperState.liquidityGross.eqn(0)) {
        //
      } else if (tickCurrentIndex < tickUpperState.tick) {
        rewardGrowthsAbove = tickUpperState.rewardGrowthsOutsideX64[i]
      } else {
        rewardGrowthsAbove = wrappingSubU128(rewardInfos[i].growthGlobalX64, tickUpperState.rewardGrowthsOutsideX64[i])
      }

      rewardGrowthsInside.push(
        wrappingSubU128(
          wrappingSubU128(rewardInfos[i].growthGlobalX64, rewardGrowthsBelow),
          rewardGrowthsAbove,
        ),
      )
    }

    return rewardGrowthsInside
  }
}
