from typing import Optional

from chemrecon.schema.enums import SourceDatabase
from chemrecon.schema.db_object import Relation, col_src, InverseRelation
from chemrecon.schema.entry_types.compound import Compound


# Is A / Has Instance
# ----------------------------------------------------------------------------------------------------------------------
class CompoundIsA(Relation[Compound, Compound]):
    """ Hierarchical relation which indicates that a compound is a member of a class of compounds.
    """
    # Attributes
    src: SourceDatabase  #: The source of the relation

    # Database
    entrytype_name = 'Is a'
    _table_name = 'CompoundIsA'
    symmetric = False
    source_entrytype = Compound
    target_entrytype = Compound
    _attribute_columns = [
        col_src
    ]
    _index = [0]

    def __init__(
            self,
            src: SourceDatabase,
            recon_id_1: Optional[int] = None,
            recon_id_2: Optional[int] = None
    ):
        super().__init__(recon_id_1, recon_id_2)
        self.src = src

    def _vis_str(self) -> str:
        return f'is a'

class CompoundHasInstance(InverseRelation[Compound, Compound]):
    """ Hierarchical relation which indicates that a compound is a member of a class of compounds.
    """
    # Attributes
    src: SourceDatabase  #: The source of the relation

    # Inverse
    inverse_main_relation = CompoundIsA

    # Database
    entrytype_name = 'Has Instance'
    _table_name = 'CompoundHasInstance'
    symmetric = False
    source_entrytype = Compound
    target_entrytype = Compound
    _attribute_columns = [
        col_src
    ]
    _index = [0]

    def __init__(
            self,
            src: SourceDatabase,
            recon_id_1: Optional[int] = None,
            recon_id_2: Optional[int] = None
    ):
        super().__init__(recon_id_1, recon_id_2)
        self.src = src

    def _vis_str(self) -> str:
        return f'has instance'

# Set Inverse
CompoundIsA.has_inverse = CompoundHasInstance

# New / Old id
# ----------------------------------------------------------------------------------------------------------------------
class CompoundHasNewID(Relation[Compound, Compound]):
    """ Indicates correspondence between identifiers in different database versions. Can be used to resolve deprecated
        identifiers.
    """
    # Attributes
    src: SourceDatabase  #: The source of the relation

    # Database
    entrytype_name = 'Has New ID'
    _table_name = 'CompoundHasNewID'
    symmetric = False
    source_entrytype = Compound
    target_entrytype = Compound
    _attribute_columns = [
        col_src
    ]
    _index = [0]

    def __init__(
            self,
            src: SourceDatabase,
            recon_id_1: Optional[int] = None,
            recon_id_2: Optional[int] = None
    ):
        super().__init__(recon_id_1, recon_id_2)
        self.src = src

    def _vis_str(self) -> str:
        return f'new id'

class CompoundHasOldID(InverseRelation[Compound, Compound]):
    """ Indicates correspondence between identifiers in different database versions. Can be used to resolve deprecated
        identifiers.
    """
    # Attributes
    src: SourceDatabase  #: The source of the relation

    # Inverse
    inverse_main_relation = CompoundHasNewID

    # Database
    entrytype_name = 'Has Old ID'
    _table_name = 'CompoundHasOldID'
    symmetric = False
    source_entrytype = Compound
    target_entrytype = Compound
    _attribute_columns = [
        col_src
    ]
    _index = [0]

    def __init__(
            self,
            src: SourceDatabase,
            recon_id_1: Optional[int] = None,
            recon_id_2: Optional[int] = None
    ):
        super().__init__(recon_id_1, recon_id_2)
        self.src = src

    def _vis_str(self) -> str:
        return f'old id'

# Set Inverse
CompoundHasNewID.has_inverse = CompoundHasOldID


# Has Part / Is Part of
# ----------------------------------------------------------------------------------------------------------------------
class CompoundHasPart(Relation[Compound, Compound]):
    """ Represents a relationship where one compound is a part of another.
    """
    # Attributes
    src: SourceDatabase  #: The source of the relation

    # Database
    entrytype_name = 'Has Part'
    _table_name = 'CompoundHasPart'
    symmetric = False
    source_entrytype = Compound
    target_entrytype = Compound
    _attribute_columns = [
        col_src
    ]
    _index = [0]

    def __init__(
            self,
            src: SourceDatabase,
            recon_id_1: Optional[int] = None,
            recon_id_2: Optional[int] = None
    ):
        super().__init__(recon_id_1, recon_id_2)
        self.src = src

    def _vis_str(self) -> str:
        return f'has part'

class CompoundIsPartOf(InverseRelation[Compound, Compound]):
    """ Represents a relationship where one compound is a part of another.
    """
    # Attributes
    src: SourceDatabase  #: The source of the relation

    # Inverse
    inverse_main_relation = CompoundHasNewID

    # Database
    entrytype_name = 'Is Part of'
    _table_name = 'CompoundIsPartOf'
    symmetric = False
    source_entrytype = Compound
    target_entrytype = Compound
    _attribute_columns = [
        col_src
    ]
    _index = [0]

    def __init__(
            self,
            src: SourceDatabase,
            recon_id_1: Optional[int] = None,
            recon_id_2: Optional[int] = None
    ):
        super().__init__(recon_id_1, recon_id_2)
        self.src = src

    def _vis_str(self) -> str:
        return f'is part of'

# Set Inverse
CompoundHasPart.has_inverse = CompoundIsPartOf


# Conjugate Acid Base
# ----------------------------------------------------------------------------------------------------------------------
class CompoundHasConjugateAcid(Relation[Compound, Compound]):
    """ Represents a relationship indicating that a compound has a conjugate acid.
    """
    # Attributes
    src: SourceDatabase  #: The source of the relation

    # Database
    entrytype_name = 'Has Conjugate Acid'
    _table_name = 'CompoundHasConjugateAcid'
    symmetric = False
    source_entrytype = Compound
    target_entrytype = Compound
    _attribute_columns = [
        col_src
    ]
    _index = [0]

    def __init__(
            self,
            src: SourceDatabase,
            recon_id_1: Optional[int] = None,
            recon_id_2: Optional[int] = None
    ):
        super().__init__(recon_id_1, recon_id_2)
        self.src = src

    def _vis_str(self) -> str:
        return f'conj. acid'

class CompoundHasConjugateBase(InverseRelation[Compound, Compound]):
    """ Represents a relationship indicating that a compound has a conjugate base.
    """
    # Attributes
    src: SourceDatabase  #: The source of the relation

    # Inverse
    inverse_main_relation = CompoundHasNewID

    # Database
    entrytype_name = 'Has Conjugate Base'
    _table_name = 'CompoundHasConjugateBase'
    symmetric = False
    source_entrytype = Compound
    target_entrytype = Compound
    _attribute_columns = [
        col_src
    ]
    _index = [0]

    def __init__(
            self,
            src: SourceDatabase,
            recon_id_1: Optional[int] = None,
            recon_id_2: Optional[int] = None
    ):
        super().__init__(recon_id_1, recon_id_2)
        self.src = src

    def _vis_str(self) -> str:
        return f'conj. base'

# Set Inverse
CompoundHasConjugateAcid.has_inverse = CompoundHasConjugateBase

# Tautomer
# ----------------------------------------------------------------------------------------------------------------------
class CompoundHasTautomer(Relation[Compound, Compound]):
    """ Represents that two compounds are considered tautomers of each other.
    """
    # Attributes
    src: SourceDatabase  #: The source of the relation

    # Database
    entrytype_name = 'Has Tautomer'
    _table_name = 'CompoundHasTautomer'
    symmetric = True
    source_entrytype = Compound
    target_entrytype = Compound
    _attribute_columns = [
        col_src
    ]
    _index = [0]

    def __init__(
            self,
            src: SourceDatabase,
            recon_id_1: Optional[int] = None,
            recon_id_2: Optional[int] = None
    ):
        super().__init__(recon_id_1, recon_id_2)
        self.src = src

    def _vis_str(self) -> str:
        return f'tautomer'

# Stereoisomer
# ----------------------------------------------------------------------------------------------------------------------
class CompoundHasStereoIsomer(Relation[Compound, Compound]):
    """ Represents that two compounds are considered stereoisomers of each other.
    """
    # Attributes
    src: SourceDatabase  #: The source of the relation

    # Database
    entrytype_name = 'Has Stereoisomer'
    _table_name = 'CompoundHasStereoisomer'
    symmetric = True
    source_entrytype = Compound
    target_entrytype = Compound
    _attribute_columns = [
        col_src
    ]
    _index = [0]

    def __init__(
            self,
            src: SourceDatabase,
            recon_id_1: Optional[int] = None,
            recon_id_2: Optional[int] = None
    ):
        super().__init__(recon_id_1, recon_id_2)
        self.src = src

    def _vis_str(self) -> str:
        return f'stereoisomer'

# Isotopologue
# ----------------------------------------------------------------------------------------------------------------------
class CompoundHasIsotopologue(Relation[Compound, Compound]):
    """ Represents that two compounds are considered isotopologues of each other.
        (identical in elements, but different isotopic composition).
    """
    # Attributes
    src: SourceDatabase  #: The source of the relation

    # Database
    entrytype_name = 'Has Isotopologue'
    _table_name = 'CompoundHasIsotopologue'
    symmetric = True
    source_entrytype = Compound
    target_entrytype = Compound
    _attribute_columns = [
        col_src
    ]
    _index = [0]

    def __init__(
            self,
            src: SourceDatabase,
            recon_id_1: Optional[int] = None,
            recon_id_2: Optional[int] = None
    ):
        super().__init__(recon_id_1, recon_id_2)
        self.src = src

    def _vis_str(self) -> str:
        return f'isotopologue'

