# # ⚠ Warning
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# [🥭 Mango Markets](https://mango.markets/) support is available at:
#   [Docs](https://docs.mango.markets/)
#   [Discord](https://discord.gg/67jySBhxrg)
#   [Twitter](https://twitter.com/mangomarkets)
#   [Github](https://github.com/blockworks-foundation)
#   [Email](mailto:<EMAIL>)


import datetime
import logging

from decimal import Decimal
from solana.publickey import PublicKey

from .accountinfo import AccountInfo
from .addressableaccount import AddressableAccount
from .context import Context
from .layouts import layouts
from .version import Version


# # 🥭 AggregatorConfig class
#

class AggregatorConfig:
    def __init__(self, version: Version, description: str, decimals: Decimal, restart_delay: Decimal,
                 max_submissions: Decimal, min_submissions: Decimal, reward_amount: Decimal,
                 reward_token_account: PublicKey):
        self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
        self.version: Version = version
        self.description: str = description
        self.decimals: Decimal = decimals
        self.restart_delay: Decimal = restart_delay
        self.max_submissions: Decimal = max_submissions
        self.min_submissions: Decimal = min_submissions
        self.reward_amount: Decimal = reward_amount
        self.reward_token_account: PublicKey = reward_token_account

    @staticmethod
    def from_layout(layout: layouts.AGGREGATOR_CONFIG) -> "AggregatorConfig":
        return AggregatorConfig(Version.UNSPECIFIED, layout.description, layout.decimals,
                                layout.restart_delay, layout.max_submissions, layout.min_submissions,
                                layout.reward_amount, layout.reward_token_account)

    def __str__(self) -> str:
        return f"« AggregatorConfig: '{self.description}', Decimals: {self.decimals} [restart delay: {self.restart_delay}], Max: {self.max_submissions}, Min: {self.min_submissions}, Reward: {self.reward_amount}, Reward Account: {self.reward_token_account} »"

    def __repr__(self) -> str:
        return f"{self}"


# # 🥭 Round class
#


class Round:
    def __init__(self, version: Version, id: Decimal, created_at: datetime.datetime, updated_at: datetime.datetime):
        self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
        self.version: Version = version
        self.id: Decimal = id
        self.created_at: datetime.datetime = created_at
        self.updated_at: datetime.datetime = updated_at

    @staticmethod
    def from_layout(layout: layouts.ROUND) -> "Round":
        return Round(Version.UNSPECIFIED, layout.id, layout.created_at, layout.updated_at)

    def __str__(self) -> str:
        return f"« Round[{self.id}], Created: {self.updated_at}, Updated: {self.updated_at} »"

    def __repr__(self) -> str:
        return f"{self}"


# # 🥭 Answer class
#

class Answer:
    def __init__(self, version: Version, round_id: Decimal, median: Decimal, created_at: datetime.datetime, updated_at: datetime.datetime):
        self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
        self.version: Version = version
        self.round_id: Decimal = round_id
        self.median: Decimal = median
        self.created_at: datetime.datetime = created_at
        self.updated_at: datetime.datetime = updated_at

    @staticmethod
    def from_layout(layout: layouts.ANSWER) -> "Answer":
        return Answer(Version.UNSPECIFIED, layout.round_id, layout.median, layout.created_at, layout.updated_at)

    def __str__(self) -> str:
        return f"« Answer: Round[{self.round_id}], Median: {self.median:,.8f}, Created: {self.updated_at}, Updated: {self.updated_at} »"

    def __repr__(self) -> str:
        return f"{self}"

# # 🥭 Aggregator class
#


class Aggregator(AddressableAccount):
    def __init__(self, account_info: AccountInfo, version: Version, config: AggregatorConfig,
                 initialized: bool, name: str, owner: PublicKey, round_: Round,
                 round_submissions: PublicKey, answer: Answer, answer_submissions: PublicKey):
        super().__init__(account_info)
        self.version: Version = version
        self.config: AggregatorConfig = config
        self.initialized: bool = initialized
        self.name: str = name
        self.owner: PublicKey = owner
        self.round: Round = round_
        self.round_submissions: PublicKey = round_submissions
        self.answer: Answer = answer
        self.answer_submissions: PublicKey = answer_submissions

    @property
    def price(self) -> Decimal:
        return self.answer.median / (10 ** self.config.decimals)

    @staticmethod
    def from_layout(layout: layouts.AGGREGATOR, account_info: AccountInfo, name: str) -> "Aggregator":
        config = AggregatorConfig.from_layout(layout.config)
        initialized = bool(layout.initialized)
        round_ = Round.from_layout(layout.round)
        answer = Answer.from_layout(layout.answer)

        return Aggregator(account_info, Version.UNSPECIFIED, config, initialized, name, layout.owner,
                          round_, layout.round_submissions, answer, layout.answer_submissions)

    @staticmethod
    def parse(context: Context, account_info: AccountInfo) -> "Aggregator":
        data = account_info.data
        if len(data) != layouts.AGGREGATOR.sizeof():
            raise Exception(f"Data length ({len(data)}) does not match expected size ({layouts.AGGREGATOR.sizeof()})")

        name = context.lookup_oracle_name(account_info.address)
        layout = layouts.AGGREGATOR.parse(data)
        return Aggregator.from_layout(layout, account_info, name)

    @staticmethod
    def load(context: Context, account_address: PublicKey):
        account_info = AccountInfo.load(context, account_address)
        if account_info is None:
            raise Exception(f"Aggregator account not found at address '{account_address}'")
        return Aggregator.parse(context, account_info)

    def __str__(self) -> str:
        return f"""
« Aggregator '{self.name}' [{self.version}]:
    Config: {self.config}
    Initialized: {self.initialized}
    Owner: {self.owner}
    Round: {self.round}
    Round Submissions: {self.round_submissions}
    Answer: {self.answer}
    Answer Submissions: {self.answer_submissions}
»
"""

# # ⚠ Warning
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# [🥭 Mango Markets](https://mango.markets/) support is available at:
#   [Docs](https://docs.mango.markets/)
#   [Discord](https://discord.gg/67jySBhxrg)
#   [Twitter](https://twitter.com/mangomarkets)
#   [Github](https://github.com/blockworks-foundation)
#   [Email](mailto:<EMAIL>)


import datetime
import logging

from decimal import Decimal
from solana.publickey import PublicKey

from .accountinfo import AccountInfo
from .addressableaccount import AddressableAccount
from .context import Context
from .layouts import layouts
from .version import Version


# # 🥭 AggregatorConfig class
#

class AggregatorConfig:
    def __init__(self, version: Version, description: str, decimals: Decimal, restart_delay: Decimal,
                 max_submissions: Decimal, min_submissions: Decimal, reward_amount: Decimal,
                 reward_token_account: PublicKey):
        self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
        self.version: Version = version
        self.description: str = description
        self.decimals: Decimal = decimals
        self.restart_delay: Decimal = restart_delay
        self.max_submissions: Decimal = max_submissions
        self.min_submissions: Decimal = min_submissions
        self.reward_amount: Decimal = reward_amount
        self.reward_token_account: PublicKey = reward_token_account

    @staticmethod
    def from_layout(layout: layouts.AGGREGATOR_CONFIG) -> "AggregatorConfig":
        return AggregatorConfig(Version.UNSPECIFIED, layout.description, layout.decimals,
                                layout.restart_delay, layout.max_submissions, layout.min_submissions,
                                layout.reward_amount, layout.reward_token_account)

    def __str__(self) -> str:
        return f"« AggregatorConfig: '{self.description}', Decimals: {self.decimals} [restart delay: {self.restart_delay}], Max: {self.max_submissions}, Min: {self.min_submissions}, Reward: {self.reward_amount}, Reward Account: {self.reward_token_account} »"

    def __repr__(self) -> str:
        return f"{self}"


# # 🥭 Round class
#


class Round:
    def __init__(self, version: Version, id: Decimal, created_at: datetime.datetime, updated_at: datetime.datetime):
        self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
        self.version: Version = version
        self.id: Decimal = id
        self.created_at: datetime.datetime = created_at
        self.updated_at: datetime.datetime = updated_at

    @staticmethod
    def from_layout(layout: layouts.ROUND) -> "Round":
        return Round(Version.UNSPECIFIED, layout.id, layout.created_at, layout.updated_at)

    def __str__(self) -> str:
        return f"« Round[{self.id}], Created: {self.updated_at}, Updated: {self.updated_at} »"

    def __repr__(self) -> str:
        return f"{self}"


# # 🥭 Answer class
#

class Answer:
    def __init__(self, version: Version, round_id: Decimal, median: Decimal, created_at: datetime.datetime, updated_at: datetime.datetime):
        self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
        self.version: Version = version
        self.round_id: Decimal = round_id
        self.median: Decimal = median
        self.created_at: datetime.datetime = created_at
        self.updated_at: datetime.datetime = updated_at

    @staticmethod
    def from_layout(layout: layouts.ANSWER) -> "Answer":
        return Answer(Version.UNSPECIFIED, layout.round_id, layout.median, layout.created_at, layout.updated_at)

    def __str__(self) -> str:
        return f"« Answer: Round[{self.round_id}], Median: {self.median:,.8f}, Created: {self.updated_at}, Updated: {self.updated_at} »"

    def __repr__(self) -> str:
        return f"{self}"

# # 🥭 Aggregator class
#


class Aggregator(AddressableAccount):
    def __init__(self, account_info: AccountInfo, version: Version, config: AggregatorConfig,
                 initialized: bool, name: str, owner: PublicKey, round_: Round,
                 round_submissions: PublicKey, answer: Answer, answer_submissions: PublicKey):
        super().__init__(account_info)
        self.version: Version = version
        self.config: AggregatorConfig = config
        self.initialized: bool = initialized
        self.name: str = name
        self.owner: PublicKey = owner
        self.round: Round = round_
        self.round_submissions: PublicKey = round_submissions
        self.answer: Answer = answer
        self.answer_submissions: PublicKey = answer_submissions

    @property
    def price(self) -> Decimal:
        return self.answer.median / (10 ** self.config.decimals)

    @staticmethod
    def from_layout(layout: layouts.AGGREGATOR, account_info: AccountInfo, name: str) -> "Aggregator":
        config = AggregatorConfig.from_layout(layout.config)
        initialized = bool(layout.initialized)
        round_ = Round.from_layout(layout.round)
        answer = Answer.from_layout(layout.answer)

        return Aggregator(account_info, Version.UNSPECIFIED, config, initialized, name, layout.owner,
                          round_, layout.round_submissions, answer, layout.answer_submissions)

    @staticmethod
    def parse(context: Context, account_info: AccountInfo) -> "Aggregator":
        data = account_info.data
        if len(data) != layouts.AGGREGATOR.sizeof():
            raise Exception(f"Data length ({len(data)}) does not match expected size ({layouts.AGGREGATOR.sizeof()})")

        name = context.lookup_oracle_name(account_info.address)
        layout = layouts.AGGREGATOR.parse(data)
        return Aggregator.from_layout(layout, account_info, name)

    @staticmethod
    def load(context: Context, account_address: PublicKey):
        account_info = AccountInfo.load(context, account_address)
        if account_info is None:
            raise Exception(f"Aggregator account not found at address '{account_address}'")
        return Aggregator.parse(context, account_info)

    def __str__(self) -> str:
        return f"""
« Aggregator '{self.name}' [{self.version}]:
    Config: {self.config}
    Initialized: {self.initialized}
    Owner: {self.owner}
    Round: {self.round}
    Round Submissions: {self.round_submissions}
    Answer: {self.answer}
    Answer Submissions: {self.answer_submissions}
»
"""

# # ⚠ Warning # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT # LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN # NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # [🥭 Mango Markets](https://mango.markets/) support is available at: #   [Docs](https://docs.mango.markets/) #   [Discord](https://discord.gg/67jySBhxrg) #   [Twitter](https://twitter.com/mangomarkets) #   [Github](https://github.com/blockworks-foundation) #   [Email](mailto:<EMAIL>) # # 🥭 AggregatorConfig class # # # 🥭 Round class # # # 🥭 Answer class # # # 🥭 Aggregator class # « Aggregator '{self.name}' [{self.version}]:     Config: {self.config}     Initialized: {self.initialized}     Owner: {self.owner}     Round: {self.round}     Round Submissions: {self.round_submissions}     Answer: {self.answer}     Answer Submissions: {self.answer_submissions} »