from typing import Dict, Set, Any, Optional

from common_functions import finger


class Major:
    majors: Set["Major"] = set()

    def __init__(self, name: str, alternate_names: Set[str] = None,
                 abbreviations: Optional[Set[str]] = None, is_computing_major: bool = True):
        self._name: str = name
        self.alternate_names: Set[str] = alternate_names if alternate_names is not None else set()
        self.abbreviations: Set[str] = abbreviations if abbreviations is not None else set()
        self._is_computing_major: bool = is_computing_major
        Major.majors.add(self)

    @classmethod
    def get_major(cls, name: str) -> "Major":
        candidate = [major for major in cls.majors if name.lower() == major.name.lower()
                     or name.lower() in [alternate_name.lower() for alternate_name in major.alternate_names]]
        if candidate:
            return candidate[0]
        else:
            return Major(name, is_computing_major=False)

    @staticmethod
    def get_student_majors(login: str) -> Set["Major"]:
        student_data: Dict[str, Any] = finger(login)
        if student_data['unlSISClassLevel'] == 'GR':
            return {Major.get_major('Graduate Student')}
        else:
            majors: Optional[Set[str]] = student_data['unlSISMajor']
            return {Major.get_major(major) for major in majors} if majors is not None else {Major.get_major('None')}

    @property
    def name(self) -> str:
        return self._name

    @property
    def is_computing_major(self) -> bool:
        return self._is_computing_major

    def __str__(self) -> str:
        return self.name

    def __repr__(self) -> str:
        return f'Name: "{self.name}";\tAlternate Names: {self.alternate_names if self.alternate_names else "{}"};\t' \
               f'CSE Major: {self._is_computing_major};\t' \
               f'Abbreviations: {self.abbreviations if self.abbreviations else "{}"}'

    def __eq__(self, other: "Major") -> bool:
        return self.name == other.name

    def __ne__(self, other: "Major") -> bool:
        return not self.__eq__(other)

    def __hash__(self) -> int:
        return hash(self.name)


# Computing Majors
Major('Computer Science', alternate_names={'Computer Science (Raikes)'},
      abbreviations={'COMP-BS', 'COMP-BA', 'COMP-MAJ', 'CMPS-BSCS',
                     'JECS-BS', 'JECS-BA', 'JECS-MAJ', 'JECS-BSCS'})  # double-check Raikes BSCS
Major('Computer Engineering', alternate_names={'Computer Engineering (Raikes)'},
      abbreviations={'CENG-BSCP', 'JECE-BSCP'})
Major('Software Engineering', alternate_names={'Software Engineering (Raikes)'},
      abbreviations={'SOFT-BSSE', 'JESE-BSSE'})

# Raikes Non-Computing Majors (to capture their alternate names)
Major('Accounting', alternate_names={'Accounting (Raikes)'},
      is_computing_major=False,
      abbreviations={'ACCG-BSBA', 'ACCG-MAJ', 'JEAC-BSBA', 'JEAC-MAJ'})
Major('Actuarial Science', alternate_names={'Actuarial Science (Raikes)'}, is_computing_major=False,
      abbreviations={'AACTS-BA', 'AACTS-BS', 'AACTS-MAJ', 'ACTS-BSBA', 'ACTS-MAJ', 'JEAS-BSBA', 'JEAS-MAJ'})
Major('Business Administration', is_computing_major=False,
      alternate_names={'Business Administration with Accounting Emphasis', 'Business Administration (Raikes)'},
      abbreviations={'BAAC-BSBA', 'BAAC-MAJ', 'BLNK-BSBA', 'BSAD-BSBA', 'BSAD-MAJ', 'JEBA-BSBA', 'JEBA-MAJ'})
Major('Economics', alternate_names={'Economics (Raikes)'}, is_computing_major=False,
      abbreviations={'ECON-BSBA', 'ECON-MAJ', 'JEEC-BSBA', 'JEEC-MAJ'})
Major('Finance', is_computing_major=False,
      alternate_names={'Finance (Raikes)',
                       'Finance (Banking & Financial Institutions)',
                       'Finance (Banking & Financial Institutions) (Raikes)',
                       'Finance (CFA-Investments)', 'Finance (CFA-Investments) (Raikes)',
                       'Finance (Risk Management & Insurance)', 'Finance (Risk Management & Insurance) (Raikes)'},
      abbreviations={'FINA-BSBA', 'FINA-MAJ', 'JEFN-BSBA', 'JEFN-MAJ',
                     'FINB-BSBA', 'FINB-MAJ', 'FINI-BSBA', 'FINI-MAJ', 'FINR-BSBA', 'FINR-MAJ',
                     'JEFB-BSBA', 'JEFB-MAJ', 'JEFI-BSBA', 'JEFI-MAJ', 'JEFR-BSBA', 'JEFR-MAJ'})
Major('International Business', alternate_names={'International Business (Raikes)'}, is_computing_major=False,
      abbreviations={'IBUS-BSBA', 'IBUS-MAJ', 'JEIB-BSBA', 'JEIB-MAJ'})
Major('Marketing', alternate_names={'Marketing (Raikes)'}, is_computing_major=False,
      abbreviations={'MRKT-BSBA', 'MRKT-MAJ', 'JEMK-BSBA', 'JEMK-MAJ'})
Major('Management', alternate_names={'Management (Raikes)'}, is_computing_major=False,
      abbreviations={'MNGT-BSBA', 'MNGT-MAJ', 'JEMN-BSBA', 'JEMN-MAJ'})

# Thanks to being able to access directory.unl.edu, I think the abbreviations are now unnecessary
# But if that ever changes, see these "Data Dictionary" pages for the (almost) comprehensive list of abbreviations:
# https://registrar.unl.edu/epm/dd/ep_wf_cpp_v1.shtml/
# https://registrar.unl.edu/data-dictionary-academic-plan/