from pathlib import Path
from .doctsdefinition import DocTsDefinition
from .doctsformula import DocTsFormula
from .doctsobjecttype import DocTsObjectType, DocDimension
from .doctsobjecttyperelation import DocTsObjectTypeRelation
from .doctstype import DocTsType
from .doctsbusinesstype import DocTsBusinessType
from .doctstimeunit import DocTsTimeUnit
from .doctemplates import use_template
import fetchx.ioutils as ioutils


class Documentation:
    def __init__(self, metamodel_zip_path: Path, documentation_path: Path):
        """
        Initialize the Documentation object by loading the metamodel from the specified zip file.
        :param metamodel_zip_path: Path to the metamodel zip file.
        :param documentation_path: Path where the documentation will be generated.
        """
        self.metamodel_path = documentation_path / "metamodel"
        ioutils.delete_folder(str(self.metamodel_path.resolve()))
        # if metamodel_zip_path is a directory, use it directly
        if metamodel_zip_path.is_dir():
            self.metamodel_path = metamodel_zip_path
        else:
            self._unzip_to_directory(metamodel_zip_path, self.metamodel_path)
        self.documentation_path = documentation_path
        self.tsdefinitions = DocTsDefinition.load_from_directory(self.metamodel_path)
        self.tsformulas = DocTsFormula.load_from_directory(self.metamodel_path)
        self.tsobjecttypes = DocTsObjectType.load_from_directory(self.metamodel_path)
        self.tsobjecttypesrelations = DocTsObjectTypeRelation.load_from_directory(self.metamodel_path)
        self.tstypes = DocTsType.load_from_directory(self.metamodel_path)
        self.tsbusinesstypes = DocTsBusinessType.load_from_directory(self.metamodel_path)
        self.tstimeunits = DocTsTimeUnit.load_from_directory(self.metamodel_path)
        self._link_objects()
        self._link_tsdefinition_predecessors_and_successors()

    def _unzip_to_directory(self, zip_path: Path, extract_to: Path):
        """
        Unzip the specified zip file to the given directory.
        :param zip_path: Path to the zip file.
        :param extract_to: Directory where the contents will be extracted.
        """
        import zipfile
        ioutils.create_folder_structure(extract_to)
        with zipfile.ZipFile(zip_path, 'r') as zip_ref:
            zip_ref.extractall(extract_to)

    def _link_objects(self):
        """
        Link the loaded objects based on their references.
        This method should be called after loading all objects.
        """
        # Link to object type relations
        for tsobjecttyperelation_code, tsobjecttyperelation in self.tsobjecttypesrelations.items():
            # Link to source tsobjecttype
            if "sourceTsObjectTypeCode" in tsobjecttyperelation and tsobjecttyperelation["sourceTsObjectTypeCode"] in self.tsobjecttypes:
                source_tsobjecttype = self.tsobjecttypes[tsobjecttyperelation["sourceTsObjectTypeCode"]]
                tsobjecttyperelation.source_tsobjecttype = source_tsobjecttype
                source_tsobjecttype.relationsFromThisTsObjectType.append(tsobjecttyperelation)
            # Link to target tsobjecttype
            if "targetTsObjectTypeCode" in tsobjecttyperelation and tsobjecttyperelation["targetTsObjectTypeCode"] in self.tsobjecttypes:
                target_tsobjecttype = self.tsobjecttypes[tsobjecttyperelation["targetTsObjectTypeCode"]]
                tsobjecttyperelation.target_tsobjecttype = target_tsobjecttype
                target_tsobjecttype.relationsToThisTsObjectType.append(tsobjecttyperelation)

        # Link to dimensions
        for tsobjecttype_code, tsobjecttype in self.tsobjecttypes.items():
            if tsobjecttype["implementationType"] == "tsDimensionList":
                tsobjecttype.dimensions = []
            else:
                tsobjecttype.dimensions = None
                continue
            # link dimensions
            if "tsDimensionList" in tsobjecttype:
                for dimension in tsobjecttype["tsDimensionList"]:
                    if "tsObjectTypeCode" in dimension and dimension["tsObjectTypeCode"] in self.tsobjecttypes:
                        tsobjecttype.dimensions.append(DocDimension(dimension, self.tsobjecttypes[dimension["tsObjectTypeCode"]]))    

        # Link to definitions
        for tsdefinition_code, tsdefinition in self.tsdefinitions.items():
            # Link to parent tsobjecttype
            if "tsObjectTypeCode" in tsdefinition and tsdefinition["tsObjectTypeCode"] in self.tsobjecttypes:
                parent_tsobjecttype = self.tsobjecttypes[tsdefinition["tsObjectTypeCode"]]
                tsdefinition.parent_tsobjecttype = parent_tsobjecttype
                parent_tsobjecttype.tsdefinitions.append(tsdefinition)
            # Link to tstype
            if "tsTypeCode" in tsdefinition and tsdefinition["tsTypeCode"] in self.tstypes:
                tstype = self.tstypes[tsdefinition["tsTypeCode"]]
                tsdefinition.tstype = tstype
            # Link to tstimeunit
            if "tstimeunitCode" in tsdefinition and tsdefinition["tstimeunitCode"] in self.tstimeunits:
                tstimeunit = self.tstimeunits[tsdefinition["tstimeunitCode"]]
                tsdefinition.tstimeunit = tstimeunit

        for tstype_code, tstype in self.tstypes.items():
            # Link to business type
            if "tsBusinessTypeCode" in tstype and tstype["tsBusinessTypeCode"] in self.tsbusinesstypes:
                tsbusinesstype = self.tsbusinesstypes[tstype["tsBusinessTypeCode"]]
                tstype.tsbusinesstype = tsbusinesstype
            # Link to time unit
            if "tsTimeUnitCode" in tstype and tstype["tsTimeUnitCode"] in self.tstimeunits:
                tstimeunit = self.tstimeunits[tstype["tsTimeUnitCode"]]
                tstype.tstimeunit = tstimeunit

        for tsbusinesstype_code, tsbusinesstype in self.tsbusinesstypes.items():
            # Link to time unit
            if "tsTimeUnitCode" in tsbusinesstype and tsbusinesstype["tsTimeUnitCode"] in self.tstimeunits:
                tstimeunit = self.tstimeunits[tsbusinesstype["tsTimeUnitCode"]]
                tsbusinesstype.tstimeunit = tstimeunit
        
        # Link to formulas
        for tsformula_code, tsformula in self.tsformulas.items():
            # Link to tsdefinition
            if "tsDefinitionCode" in tsformula and tsformula["tsDefinitionCode"] in self.tsdefinitions:
                tsdefinition = self.tsdefinitions[tsformula["tsDefinitionCode"]]
                tsformula.tsdefinition = tsdefinition
                tsdefinition.formula = tsformula

    def _link_tsdefinition_predecessors_and_successors(self):
        """
        Link tsdefinitions based on formulas.
        If a formula of a tsdefinition references another tsdefinition code, it is considered a predecessor
        of the current tsdefinition, and the current tsdefinition is a successor of the referenced tsdefinition.
        """
        for tsdefinition_code, tsdefinition in self.tsdefinitions.items():
            if tsdefinition.formula is not None:
                formula_words = tsdefinition.formula.parse_formula_words()
                for word in formula_words:
                    if word in self.tsdefinitions:
                        predecessor = self.tsdefinitions[word]
                        tsdefinition.predecessors.append(predecessor)
                        predecessor.successors.append(tsdefinition)

    def modify_links(self, *, pages: list[str], page_urls: list[str]):
        """
        Modify internal links in the documentation to point to the specified page URLs.
        :param page_urls: List of page URLs to replace internal links with.
        """
        result = []
        # dictionary of links. Example 
        # {"code_anchor": {"old_link", "href='#code_anchor'", "new_link": "href='full_page_url#code_anchor'"}}
        links = {}
        if len(page_urls) < len(pages):
            print("Warning: Not enough page URLs provided to modify links. See --pages parameter")
            return pages
        # find all anchors in the page and map them to full URLs
        for page_index, page in enumerate(pages):
            # find anchors for all tsdefinitions
            for tsdefinition_code in self.tsdefinitions.keys():
                anchor_to_search = f'{tsdefinition_code}'
                old_link = f'href=\"#{anchor_to_search}\"'
                new_link = f'href=\"{page_urls[page_index]}#{anchor_to_search}\"'
                if anchor_to_search in page:
                    links[anchor_to_search] = {
                        'old_link': old_link,
                        'new_link': new_link
                    }
            # find anchors for all tsobjecttypes and tsobjecttyperelations
            for tsobjecttype_code in self.tsobjecttypes.keys():
                anchor_to_search = f'<div id=\"{tsobjecttype_code}\">'
                old_link = f'href=\"#{anchor_to_search}\"'
                new_link = f'href=\"{page_urls[page_index]}#{anchor_to_search}\"'
                if anchor_to_search in page:
                    links[anchor_to_search] = {
                        'old_link': old_link,
                        'new_link': new_link
                    }
            # find anchors for all tsobjecttyperelations
            for tsobjecttyperelation_code in self.tsobjecttypesrelations.keys():
                anchor_to_search = f'<div id=\"{tsobjecttyperelation_code}\">'
                old_link = f'href=\"#{anchor_to_search}\"'
                new_link = f'href=\"{page_urls[page_index]}#{anchor_to_search}\"'
                if anchor_to_search in page:
                    links[anchor_to_search] = {
                        'old_link': old_link,
                        'new_link': new_link
                    }
        # replace anchors in pages with full URLs
        for page in pages:
            modified_page = page
            for code, full_url in links.items():
                modified_page = modified_page.replace(f'href="{code}"', f'href="{full_url}"')
            result.append(modified_page)
        return result

    def generate_documentation(self, *, language: str = "en", page_urls: list[str] = []):
        """
        Generate documentation files for each tsdefinition.
        """
        output_path_tsdefinitions = self.documentation_path / "doc/tsdefinitions"
        output_path_tsobjecttypes = self.documentation_path / "doc/tsobjecttypes"
        output_path_tsobjecttyperelations = self.documentation_path / "doc/tsobjecttyperelations"
        output_path_all = self.documentation_path / "doc"
        ioutils.delete_folder(str(output_path_all.resolve()))
        ioutils.create_folder_structure(output_path_tsdefinitions)
        ioutils.create_folder_structure(output_path_tsobjecttypes)
        ioutils.create_folder_structure(output_path_tsobjecttyperelations)
        ioutils.create_folder_structure(output_path_all)

        mini_sections = []
        # Generate documentation for each tsdefinition
        for tsobjecttype_code, tsobjecttype in self.tsobjecttypes.items():
            mini_sections.append('')
            # generate documentation for each tsobjecttype
            single_tsobjecttype = use_template('Section_Begin',{'language_code':language}, language=language)
            single_tsobjecttype += use_template('TSObjectType_Separator',{'language_code':language}, language=language)
            single_tsobjecttype += tsobjecttype.to_string(language=language)
            single_tsobjecttype += use_template('Section_End',{}, language=language)
            output_file = output_path_tsobjecttypes / f"{tsobjecttype['code']}.txt"
            with open(output_file, 'w', encoding='utf-8') as f:
                f.write(single_tsobjecttype)
            # append to entire page
            mini_sections[-1] += use_template('TSObjectType_Separator',{'language_code':language}, language=language)
            mini_sections[-1] += tsobjecttype.to_string(language=language)
            for tsObjectTypeRelation in tsobjecttype.relationsFromThisTsObjectType:
                single_tsobjecttyperelation = use_template('Section_Begin',{'language_code':language}, language=language)
                single_tsobjecttyperelation += tsObjectTypeRelation.to_string(language=language)
                single_tsobjecttyperelation += use_template('Section_End',{}, language=language)
                mini_sections[-1] += single_tsobjecttyperelation
                output_file = output_path_tsobjecttyperelations / f"{tsObjectTypeRelation['code']}.txt"
                with open(output_file, 'w', encoding='utf-8') as f:
                    f.write(tsObjectTypeRelation.to_string(language=language))
            # generate documentation for each tsdefinition in this tsobjecttype
            for tsdefinition in tsobjecttype.tsdefinitions:
                single_tsdefinition = use_template('Section_Begin',{'language_code':language}, language=language)
                single_tsdefinition += tsdefinition.to_string(language=language)
                single_tsdefinition += use_template('Section_End',{}, language=language)
                mini_sections[-1] += tsdefinition.to_string(language=language)
                output_file = output_path_tsdefinitions / f"{tsdefinition['code']}.txt"
                with open(output_file, 'w', encoding='utf-8') as f:
                    f.write(tsdefinition.to_string(language=language))

        # generate documentation page by page
        pages = ['']
        for mini_section in mini_sections:
            if len(pages[-1]) + len(mini_section) > 485000:
                pages.append('')
            pages[-1] += mini_section
        # modify links to point to full page urls
        pages = self.modify_links(pages=pages, page_urls=page_urls)
        # generate documentation for all tsdefinitions
        for page_index, page in enumerate(pages):
            doc_entirepage = use_template('Section_Begin',{'language_code':language}, language=language)
            doc_entirepage += page
            doc_entirepage += use_template('Section_End',{}, language=language)
            # write to file
            output_file = output_path_all / f"tsdefinitions_{page_index}.txt"
            with open(output_file, 'w', encoding='utf-8') as f:
                f.write(doc_entirepage)
            
