# Copyright (c) 2024 Paulo Cachim
# SPDX-License-Identifier: MIT
from enum import Enum

from eurocodepy import dbase

RiskClass = Enum("RiskClasses", dbase.Timber["RiskClasses"])
ServiceClass = Enum("ServiceClasses", dbase.Timber["ServiceClasses"])
LoadDuration = Enum("LoadDuration", dbase.Timber["LoadDuration"])
TimberClass = Enum("TimberClass", list(dbase.TimberGrades.keys()))


class Timber:
    """Initialize Timber object.

    Properties are derived from the database for the given type_label.

    Args:
        type_label (str, optional): Timber grade label (e.g., 'C24').
        Defaults to 'C24'.

    Raises:
        ValueError: If the type_label is not found in the database.

    """

    def __init__(self, type_label: str = "C24") -> None:
        if type_label not in dbase.TimberGrades:
            msg = (
                f"Timber type '{type_label}' not found in database. "
                f"Timber type must be one of {list(dbase.TimberGrades)}"
            )
            raise ValueError(msg)

        timber = dbase.TimberGrades[type_label]
        self.fmk = timber["fmk"]  # Characteristic strength in MPa
        self.ft0k = timber["ft0k"]  # Charact. strength in tension // to grain (MPa)
        self.ft90k = timber["ft90k"]  # Charact. strength in tensio perp. to grain (MPa)
        self.fc0k = timber["fc0k"]  # Charact. strength in compression // to grain (MPa)
        self.fc90k = timber["fc90k"]  # Charact. strength in compr. perp. to grain (MPa)
        self.fvk = timber["fvk"]  # Characteristic shear strength (MPa)
        self.E0mean = timber["E0mean"]  # Mean modulus elasticity in MPa
        self.E0k = timber["E0k"]  # Characteristic modulus elasticity in MPa
        self.E90mean = timber["E90mean"]  # Charact. modulus elasticity perp. to grain (MPa)
        self.Gmean = timber["Gmean"]  # Mean shear modulus in MPa
        self.rhok = timber["rhok"]  # Characteristic density in kg/m³
        self.rhom = timber["rhom"]  # Mean density in kg/m³
        self.type_label = type_label
        self.type = timber["Type"]

        self.safety = dbase.TimberParams["safety"][self.type]  # Partial safety factor
        self.kmod = dbase.TimberParams["kmod"][self.type]
        self.kdef = dbase.TimberParams["kmod"][self.type]
        self.kh = dbase.TimberParams["kh"][self.type]
        self.material = "Solid"

    def k_mod(
        self,
        service_class: ServiceClass = ServiceClass.SC1,
        load_duratiom: LoadDuration = LoadDuration.Medium,
    ) -> float:
        """Return the kmod value for serviceability limit state.

        Args:
            service_class (ServiceClass, optional): Service class (SC1, SC2, or SC3).
                Defaults to ServiceClass.SC1.
            load_duratiom (LoadDuration, optional): Load duration (Perm, Long,
            Medium, Short, Inst).
                Defaults to LoadDuration.Medium.

        Returns:
            float: kmod value for the specified service class and load duration.

        """
        return self.kmod[service_class.name][load_duratiom.value]

    def k_def(self, service_class: ServiceClass = ServiceClass.SC1) -> float:
        """Return the kdef value for serviceability limit state.

        Args:
            service_class (ServiceClass, optional): Service class (SC1, SC2, or SC3).
                Defaults to ServiceClass.SC1.

        Returns:
            float: kmod value for the specified service class.

        """
        return self.kdef[service_class.name]

    def __str__(self) -> str:  # noqa: D105
        return (
            f"Timber class: {self.type_label}\n"
            f"  type: {self.type}\n"
            f"  fmk: {self.fmk} MPa\n"
            f"  ft0k: {self.ft0k} MPa\n"
            f"  fc0k: {self.fc0k} MPa\n"
            f"  fvk: {self.fvk} MPa\n"
            f"  fc90k: {self.fc90k} MPa\n"
            f"  ft90k: {self.ft90k} MPa\n"
            f"  E0k: {self.E0k} MPa\n"
            f"  E90mean: {self.E90mean} MPa\n"
            f"  E0mean: {self.E0mean} MPa\n"
            f"  Gmean: {self.Gmean} MPa\n"
            f"  rhok: {self.rhok} kg/m³\n"
            f"  rhom: {self.rhom} kg/m³\n"
        )


class SolidTimber(Timber):
    """Class represents Eurocode 5 solid timber properties.

    It includes both softwood and hardwood types.
    Properties read from the database for the given type_label.

    Args:
        type_label (str, optional): Timber grade label (e.g., 'C24', 'D30').
        Defaults to 'C24'.

    Raises:
        ValueError: If the type_label is not found in the database.

    """

    def __init__(self, type_label: str = "C24") -> None:
        grades_list = [item for item in dbase.TimberGrades
                    if (item.startswith(("C", "D")))]
        if type_label not in grades_list:
            msg = (
                f"Softwood type '{type_label}' not found in database. "
                f"Softwood type must be one of {grades_list}"
            )
            raise ValueError(msg)

        super().__init__(type_label)

        self.fvrefk = dbase.TimberParams["fvrefk"][self.type]
        self.theta_twist = dbase.TimberParams["theta_twist"][self.type]
        self.kred = dbase.TimberParams["kred"][self.type]
        self.material = "Solid"


class Glulam(Timber):
    """Eurocode 5 glulam properties.

    Properties read from the database for the given type_label.

    Args:
        type_label (str, optional): Glulam type label (e.g., 'GL24h', 'GL28c').
        Defaults to 'GL24h'.

    Raises:
        ValueError: If the type_label does not start with 'GL' or
        is not found in the database.
    """

    def __init__(self, type_label: str = "GL24h") -> None:
        grades_list = [item for item in dbase.TimberGrades if item.startswith("GL")]

        if not type_label.startswith("GL"):
            msg = (
                f"Glulam type '{type_label}' must start with 'GL'. "
                f"Glulam type must be one of {grades_list}"
            )
            raise ValueError(msg)
        if type_label not in grades_list:
            msg = (
                f"Glulam type '{type_label}' not found in database. "
                f"Glulam type must be one of {grades_list}"
            )
            raise ValueError(msg)

        super().__init__(type_label)

        timber = dbase.TimberGrades[type_label]
        self.frk = timber["frk"]  # Characteristic rolling shear strength (MPa)
        self.Gk = timber["Gk"]  # Characteristic shear modulus in MPa
        self.Grk = timber["Grk"]  # Charact. rolling shear modulus in
        self.Grmean = timber["Grmean"]  # Mean rolling shear modulus in MPa
        self.G90k = timber["Gk"]  # Charact. shear modulus perp. to grain (MPa)
        self.fvrefk = dbase.TimberParams["fvrefk"][self.type]
        self.theta_twist = dbase.TimberParams["theta_twist"][self.type]
        self.kred = dbase.TimberParams["kred"][self.type]
        self.material = "Glulam"

    def __str__(self) -> str:  # noqa: D105
        return (
            f"Timber class: {self.type_label}\n"
            f"  type: {self.type}\n"
            f"  fmk: {self.fmk} MPa\n"
            f"  ft0k: {self.ft0k} MPa\n"
            f"  fc0k: {self.fc0k} MPa\n"
            f"  fvk: {self.fvk} MPa\n"
            f"  fc90k: {self.fc90k} MPa\n"
            f"  ft90k: {self.ft90k} MPa\n"
            f"  frk: {self.frk} MPa\n"
            f"  E0mean: {self.E0mean} MPa\n"
            f"  E0k: {self.E0k} MPa\n"
            f"  E90mean: {self.E90mean} MPa\n"
            f"  E90k: {self.E90k} MPa\n"
            f"  Gmean: {self.Gmean} MPa\n"
            f"  Gk: {self.Gk} MPa\n"
            f"  Grmean: {self.Grmean} MPa\n"
            f"  Grk: {self.Grk} MPa\n"
            f"  rhok: {self.rhok} kg/m³\n"
            f"  rhom: {self.rhom} kg/m³\n"
        )


class CLT(Timber):
    """Eurocode 5 Cross-Laminated Timber (CLT) properties. Not implemented."""


class LVL(Timber):
    """Eurocode 5 Laminated Veneer Lumber (LVL) properties. Not implemented."""


class WoodBasedPanels(Timber):
    """Eurocode 5 Wood-Based Panels properties. Not implemented."""


# Alias for SolidTimber, as it is commonly referred to as Softwood in Eurocode 5
Softwood = SolidTimber
# Alias for SolidTimber, as it is commonly referred to as HArdwood in Eurocode 5
Hardwood = SolidTimber
# Alias for SolidTimber, as it is commonly referred to as ST in Eurocode 5
ST = SolidTimber
# Alias for Glulam, as it is commonly referred to as Glulam in Eurocode 5
GL = Glulam


TimberGrades = {item: Timber(item)
            for item in dbase.TimberGrades}
