# AUTOGENERATED! DO NOT EDIT! File to edit: ../src/pool.ipynb.

# %% auto 0
__all__ = ['Price', 'Amount', 'symbol', 'LiquidityPoolForSwap', 'LiquidityPool', 'LiquidityPoolEpoch']

# %% ../src/pool.ipynb 3
from dataclasses import dataclass
from datetime import datetime
from typing import Tuple, Dict, Optional, List
from .token import Token
from .helpers import normalize_address

# %% ../src/pool.ipynb 4
@dataclass(frozen=True)
class Price:
    """Data class for Token Price

    based on:
    https://github.com/velodrome-finance/oracle/blob/main/contracts/VeloOracle.sol
    """

    token: Token
    price: float

    @property
    def pretty_price(self) -> float: return round(self.price, 5)

# %% ../src/pool.ipynb 5
@dataclass(frozen=True)
class Amount:
    token: Token
    amount: float
    price: "Price"

    @classmethod
    def build(cls, address: str, amount: float, tokens: Dict[str, Token], prices: Dict[str, "Price"]) -> "Amount":
        address = normalize_address(address)
        if address not in tokens or address not in prices: return None
        token = tokens[address]
        return Amount(token=token, amount=token.value_from_bigint(amount), price=prices[address])

    @property
    def amount_in_stable(self) -> float: return self.amount * self.price.price


# %% ../src/pool.ipynb 6
def symbol(token0: Token, token1: Token, pool_type: int) -> str:
    return f"CL{pool_type}-{token0.symbol}/{token1.symbol}" if pool_type > 0  else f"{'s' if pool_type == 0 else 'v'}AMM-{token0.symbol}/{token1.symbol}"

@dataclass(frozen=True)
class LiquidityPoolForSwap:
    """A more compact pool representation used for swaps"""

    lp: str
    type: int
    token0_address: str
    token1_address: str
    is_stable: bool
    # concentrated liquidity pools
    is_cl: bool

    @classmethod
    def from_tuple(cls, t: Tuple) -> "LiquidityPoolForSwap":
        token0, token1, pool_type = normalize_address(t[2]), normalize_address(t[3]), t[1]
        return LiquidityPoolForSwap(
            lp=normalize_address(t[0]),
            type=pool_type,
            token0_address=token0,
            token1_address=token1,
            is_stable=pool_type == 0,
            is_cl=pool_type > 0
        )

        


@dataclass(frozen=True)
class LiquidityPool:
    """Data class for Liquidity Pool

    based on:
    https://github.com/velodrome-finance/sugar/blob/v2/contracts/LpSugar.vy#L31
    """

    lp: str
    factory: str
    symbol: str
    type: int
    is_stable: bool
    # concentrated liquidity pools
    is_cl: bool
    total_supply: float
    decimals: int
    token0: Token
    reserve0: Amount
    token1: Token
    reserve1: Amount
    token0_fees: Amount
    token1_fees: Amount
    pool_fee: float
    gauge_total_supply: float
    emissions: Amount
    emissions_token: Token
    weekly_emissions: Amount
    nfpm: str
    alm: str

    @property
    def tvl(self) -> float: return self.reserve0.amount_in_stable + self.reserve1.amount_in_stable

    @property
    def total_fees(self) -> float:
        result = 0

        if self.token0_fees: result += self.token0_fees.amount_in_stable
        if self.token1_fees: result += self.token1_fees.amount_in_stable

        return result

    @property
    def pool_fee_percentage(self) -> float: return self.pool_fee / 100

    @property
    def volume_pct(self) -> float: return 100 / self.pool_fee_percentage

    @property
    def volume(self) -> Amount:
        t0 = self.token0_fees.amount_in_stable if self.token0_fees else 0
        t1 = self.token1_fees.amount_in_stable if self.token1_fees else 0
        return self.volume_pct * (t0 + t1)

    @property
    def token0_volume(self) -> float: return self.token0_fees.amount * self.volume_pct if self.token0_fees else 0

    @property
    def token1_volume(self) -> float: return self.token1_fees.amount * self.volume_pct if self.token1_fees else 0

    @property
    def apr(self) -> float:
        day_seconds, tvl = 24 * 60 * 60, self.tvl
        reward_value = self.emissions.amount_in_stable if self.emissions else 0
        reward = reward_value * day_seconds
        staked_pct = (
            100 * self.gauge_total_supply / self.total_supply if self.total_supply != 0 else 0
        )
        staked_tvl = tvl * staked_pct / 100
        return (reward / staked_tvl) * (100 * 365) if staked_tvl != 0 else 0

    @classmethod
    def from_tuple(
        cls, t: Tuple, tokens: Dict[str, Token], prices: Dict[str, Price]
    ) -> Optional["LiquidityPool"]:
        token0, token1, pool_type = normalize_address(t[7]), normalize_address(t[10]), t[4]
        token0_fees, token1_fees = t[23], t[24]
        emissions_token = normalize_address(t[20])
        emissions = t[19]

        seconds_in_a_week = 7 * 24 * 60 * 60

        # Sugar.all returns a tuple with the following structure:
        # { "name": "lp", "type": "address" },          <== 0
        # { "name": "symbol", "type": "string" },       <== 1
        # { "name": "decimals", "type": "uint8" },      <== 2
        # { "name": "liquidity", "type": "uint256" },   <== 3
        # { "name": "type", "type": "int24" },          <== 4
        # { "name": "tick", "type": "int24" },          <== 5
        # { "name": "sqrt_ratio", "type": "uint160" },  <== 6
        # { "name": "token0", "type": "address" },      <== 7
        # { "name": "reserve0", "type": "uint256" },    <== 8
        # { "name": "staked0", "type": "uint256" },     <== 9
        # { "name": "token1", "type": "address" },      <== 10
        # { "name": "reserve1", "type": "uint256" },    <== 11
        # { "name": "staked1", "type": "uint256" },     <== 12
        # { "name": "gauge", "type": "address" },        <== 13
        # { "name": "gauge_liquidity", "type": "uint256" },  <== 14
        # { "name": "gauge_alive", "type": "bool" },        <== 15
        # { "name": "fee", "type": "address" },             <== 16
        # { "name": "bribe", "type": "address" },           <== 17
        # { "name": "factory", "type": "address" },         <== 18
        # { "name": "emissions", "type": "uint256" },       <== 19
        # { "name": "emissions_token", "type": "address" },  <== 20
        # { "name": "pool_fee", "type": "uint256" },        <== 21
        # { "name": "unstaked_fee", "type": "uint256" },    <== 22
        # { "name": "token0_fees", "type": "uint256" },     <== 23
        # { "name": "token1_fees", "type": "uint256" },    <== 24
        # { "name": "nfpm", "type": "address" },           <== 25
        # { "name": "alm", "type": "address" }             <== 26

        token0, token1 = tokens.get(token0), tokens.get(token1)
        if not token0 or not token1: return None

        return LiquidityPool(
            lp=normalize_address(t[0]),
            factory=normalize_address(t[18]),
            symbol=symbol(token0, token1, pool_type),
            type=pool_type,
            # stable pools have type set to 0
            is_stable=pool_type == 0,
            is_cl=pool_type > 0,
            total_supply=t[3],
            decimals=t[2],
            token0=token0,
            reserve0=Amount.build(token0.token_address, t[8], tokens, prices),
            token1=token1,
            reserve1=Amount.build(token1.token_address, t[11], tokens, prices),
            token0_fees=Amount.build(token0.token_address, token0_fees, tokens, prices),
            token1_fees=Amount.build(token1.token_address, token1_fees, tokens, prices),
            pool_fee=t[21],
            gauge_total_supply=t[14],
            emissions_token=tokens.get(emissions_token),
            emissions=Amount.build(emissions_token, emissions, tokens, prices),
            weekly_emissions=Amount.build(emissions_token, emissions * seconds_in_a_week, tokens, prices),
            nfpm=normalize_address(t[25]),
            alm=normalize_address(t[26]),
        )

# %% ../src/pool.ipynb 7
@dataclass(frozen=True)
class LiquidityPoolEpoch:
    ts: int
    lp: str
    pool: LiquidityPool
    votes: int
    emissions: int
    incentives: List[Amount]
    fees: List[Amount]

    @classmethod
    def from_tuple(cls, t: Tuple, pools: Dict[str, LiquidityPool], tokens: Dict[str, Token], prices: Dict[str, Price]) -> "LiquidityPoolEpoch":
        ts, lp, votes, emissions, incentives, fees = t[0], normalize_address(t[1]), t[2], t[3], t[4], t[5]
        def build_amount(token_address: str, amount: int) -> Amount:
            return Amount.build(normalize_address(token_address), amount, tokens, prices)

        return LiquidityPoolEpoch(ts=ts, lp=lp, pool=pools.get(lp), votes=votes, emissions=emissions,
            incentives=[build_amount(i[0], i[1]) for i in incentives],
            fees=[build_amount(f[0], f[1]) for f in fees]
        )

    @property
    def total_fees(self) -> float:
        return sum([fee.amount_in_stable for fee in self.fees]) if self.fees else 0
    @property
    def total_incentives(self) -> float:
        return sum([incentive.amount_in_stable for incentive in self.incentives]) if self.incentives else 0

    @property
    def epoch_date(self) -> datetime:
        return datetime.fromtimestamp(self.ts)
