""" Export functionality for doc_file_processing """

from __future__ import annotations

import csv, codecs, re
from datetime import timezone

from configuration import *
from document import Figure, Table
from logutil import *
from winutil import make_valid_filename


PAGE_RE = re.compile(r'^(?P<block>\w+) - (?P<type>[^-]+?) - (?P<appl>\w+)(\.\w+)*\.(?P<module>\w+)( - (?P<description>.*))?$')
SCM_FIGURE_RE = re.compile(r'^Single control module - \w+(\.\w+)*')

QUALIFIED_TABLE_RE = re.compile(r'.* \((?P<title>.*)\)')
PAGE_TABLE_RE = re.compile(r'^(?P<title>[^-]+) - (?P<type>\w+) - (?P<appl>\w+)\.(?P<module>\w+) - (?P<pagedesc>Page \d+)')


def export(config: Configuration, output_path: Path, img_provider: Reader) -> object:
    # Infer top-level chapters for bare exports of lower-level chapters
    synth_appls = dict()
    synth_controllers = dict()
    for d in config.loose_diagrams:
        if d.scope_name in synth_appls:
            a = synth_appls[d.scope_name]
        else:
            a = Application(d.scope_name, 'Not configured')
            synth_appls[a.name] = a
        a.diagrams.append(d)
    for p in config.loose_programs:
        if p.scope_name in synth_appls:
            a = synth_appls[p.scope_name]
        else:
            a = Application(p.scope_name, 'Not configured')
            synth_appls[a.name] = a
        a.programs.append(p)
    for s in config.loose_scms:
        if s.scope_name in synth_appls:
            a = synth_appls[s.scope_name]
        else:
            a = Application(s.scope_name, 'Not configured')
            synth_appls[a.name] = a
        a.scms.append(s)
    for h in config.loose_hardware_chapters:
        if h.controller_name in synth_controllers:
            c = synth_controllers[h.controller_name]
        else:
            c = Controller(h.controller_name, 'Not configured')
            synth_controllers[h.controller_name] = c
        # create synthetic units for a deeply-nested single unit export
        target = c.hardware_chapters
        for i in range(1, len(h.index_path)):
            u = HardwareChapter('', h.controller_name, h.index_path[:i])
            target.append(u)
            target = u.sections
        target.append(h)
        
    if config.libraries:
        docType = 'library'
    elif config.controllers:
        docType = 'controller'
    elif config.loose_hardware_chapters:
        docType = 'hardware'
    elif config.applications:
        docType = 'application'
    elif config.loose_diagrams or config.loose_programs or config.loose_scms:
        docType = 'diagram'
    else:
        docType = 'unknown'

    path_list = list(config.doc_source_path.parts)
    path_list[-1] = config.doc_source_path.stem  # strip file extension

    for t in config.tables:
        export_table(t, output_path)
    
    applications = [export_application(a, output_path, img_provider) for a in [*config.applications, *synth_appls.values()]]
    controllers = [export_controller(c, output_path) for c in [*config.controllers, *synth_controllers.values()]]
    libraries = [export_library(l, output_path, img_provider) for l in config.libraries]

    return {
        'source': str(config.doc_source_path),
        'name': config.doc_title,
        'applications': applications,
        'controllers': controllers,
        'libraries': libraries,
        'path': path_list,
        'timestamp': config.doc_timestamp.astimezone(timezone.utc) \
            .strftime('%Y-%m-%d %H:%M:%S %Z'),
        'docType': docType,
    }

def export_library(l: Library, output_path: Path, img_provider: Reader) -> object:
    path_name = make_valid_filename(l.name)
    lib_output_path = output_path / path_name

    return {
        'name': l.name,
        'path': path_name,
        'types': [export_fbt(fbt, lib_output_path, img_provider) for fbt in l.types]
    }

def export_controller(c: Controller, output_path: Path) -> object:
    path_name = make_valid_filename(c.name)
    ctrl_output_path = output_path / path_name

    if c.header:
        export_text('Header', c.header, ctrl_output_path)

    for t in c.tables:
        export_table(t, ctrl_output_path)

    return {
        'name': c.name,
        'path': path_name,
        'hardwarechps': [export_hardware_chapter(h, ctrl_output_path) for h in c.hardware_chapters],
    }

def export_hardware_chapter(h: HardwareChapter, output_path: Path) -> object:
    index_str = ".".join(str(i) for i in h.index_path)
    name = index_str + (" " if index_str and h.name else "") + h.name
    path_name = make_valid_filename(name)
    chapter_output_path = output_path / path_name

    # merge channel tables into one large table
    merge_headers = []
    merge_data = [[] for _ in range(len(h.channel_tables[0]))] if h.channel_tables else []
    for t in h.channel_tables:
        if t is None: continue
        for ci, c in enumerate(t.headers):
            if c not in merge_headers:
                merge_headers.append(c)
                for ri, row in enumerate(t.data):
                    merge_data[ri].append(row[ci])

    merge = Table(
        None,
        'Channels',
        tuple(merge_headers),
        tuple(tuple(row) for row in merge_data))

    if merge.data: export_table(merge, chapter_output_path)

    for t in h.other_tables:
        export_table(t, chapter_output_path)

    return {
        'name': name if h.name else index_str,
        'path': path_name if h.name else None,
        'index': h.index_path[-1] if h.index_path else None,
        'sections': [export_hardware_chapter(hh, output_path) for hh in h.sections]
    }

def export_application(a: Application, output_path: Path, img_provider: Reader) -> object:
    path_name = make_valid_filename(a.name)
    appl_output_path = output_path / path_name

    if a.header:
        export_text('Header', a.header, appl_output_path)

    for t in a.tables:
        export_table(t, appl_output_path)

    if a.cmis:
        export_cmi_connections(a.cmis, appl_output_path)

    return {
        'name': a.name,
        'path': path_name,
        'diagrams': [export_module(d, appl_output_path, img_provider) for d in a.diagrams],
        'programs': [export_module(p, appl_output_path, img_provider) for p in a.programs],
        'controlmodules': [export_scm(s, appl_output_path, img_provider) for s in a.scms],
        'types': [export_fbt(fbt, appl_output_path, img_provider) for fbt in a.types],
    }

def export_module(m: Module, output_path: Path, img_provider: Reader) -> object:
    path_name = make_valid_filename(m.name)
    module_output_path = output_path / path_name

    if m.header:
        export_text('Header', m.header, module_output_path)

    for t in m.tables:
        export_table(t, module_output_path)

    image_code_blocks = []
    text_code_blocks = []

    for b in m.code_blocks:
        if b.figures:
            image_code_blocks.append(export_image_code_block(b, module_output_path, img_provider))
        if b.text_blocks:
            text_code_blocks.append(export_text_code_block(b, module_output_path))

    return {
        'name': m.name,
        'path': path_name,
        'image_sections': image_code_blocks,
        'text_docs': text_code_blocks,
    }

def export_fbt(t: TypeDefinition, output_path: Path, img_provider: Reader) -> object:
    path_name = make_valid_filename(t.name)
    if t.is_description_only():
        export_text(path_name, t.module.header, output_path)
        if t.params_table:
            export_table(t.params_table, output_path, f'{path_name} Parameters')
        if t.xparams_table:
            export_table(t.xparams_table, output_path, f'{path_name} Extensible Parameters')

        return {
            'name': t.name,
            'path': None,
        }
    else:
        module_output_path = output_path / path_name
        export_text('Header', t.module.header, module_output_path)
        
        export_text(path_name, t.module.header, module_output_path)
        if t.params_table:
            export_table(t.params_table, module_output_path, f'{path_name} Parameters')
        if t.xparams_table:
            export_table(t.xparams_table, module_output_path, f'{path_name} Extensible Parameters')

        for tb in t.module.tables:
            export_table(tb, module_output_path)

        image_code_blocks = []
        text_code_blocks = []

        for b in t.module.code_blocks:
            if b.figures:
                image_code_blocks.append(export_image_code_block(b, module_output_path, img_provider))
            if b.text_blocks:
                text_code_blocks.append(export_text_code_block(b, module_output_path))
        
        if t.module.cmis:
            export_cmi_connections(t.module.cmis, module_output_path)
        
        return {
            'name': t.name,
            'path': path_name,
            'image_sections': image_code_blocks,
            'text_docs': text_code_blocks,
            'controlmodules': [export_scm(submodule, output_path, img_provider) for submodule in t.module.scms]
        }

def export_scm(s: SCM, output_path: Path, img_provider: Reader) -> object:
    path_name = make_valid_filename(s.name)
    scm_output_path = output_path / path_name

    if s.header:
        export_text('Header', s.header, scm_output_path)

    if s.cmis:
        export_cmi_connections(s.cmis, scm_output_path)

    for t in s.tables:
        export_table(t, scm_output_path)

    for f in s.figures:
        export_figure(f, scm_output_path, img_provider)

    image_code_blocks = []
    text_code_blocks = []

    for b in s.code_blocks:
        if b.figures:
            image_code_blocks.append(export_image_code_block(b, scm_output_path, img_provider))
        if b.text_blocks:
            text_code_blocks.append(export_text_code_block(b, scm_output_path))

    return {
        'name': s.name.split('.')[-1],
        'path': path_name,
        'image_sections': image_code_blocks,
        'text_docs': text_code_blocks,
        'controlmodules': [export_scm(submodule, output_path, img_provider) for submodule in s.scms]
    }

def export_cmi_connections(lc: List[CMI], output_path: Path) -> None:
    if not lc: return

    joined_data = []
    for cmi in lc:
        for row in cmi.connections.data:
            joined_data.append([cmi.name, *row])

    joined_table = Table(
        None,
        'Module',
        ('Control Module', *lc[0].connections.headers),
        tuple(tuple(row) for row in joined_data))

    export_table(joined_table, output_path)

def export_image_code_block(b: CodeBlock, output_path: Path, img_provider: Reader) -> object:
    for t in b.tables:
        export_table(t, output_path)

    return {
        'name': b.name,
        'images': [export_figure(f, output_path, img_provider) for f in b.figures],
    }

def export_text_code_block(b: CodeBlock, output_path: Path) -> object:
    text = '\n'.join(b.text_blocks)
    return [b.name, export_text(b.name, text, output_path)]

def export_text(name: str, x: str, output_path: Path) -> str:
    logmsg(f'    Exporting text: {name}')
    file_name = make_valid_filename(name) + '.txt'
    output_path.mkdir(parents=True, exist_ok=True)

    file_path = output_path / file_name
    with open(file_path, 'w') as textfile:
        textfile.write(x)
    return file_name

def export_figure(f: Figure, output_path: Path, img_provider: Reader) -> object:
    if match := PAGE_RE.match(f.caption):
        if match['description']:
            title = match['description']
        else:
            title = match['block'] + f' (fig {f.index})'
    elif match := SCM_FIGURE_RE.match(f.caption):
        title = f'Module (fig {f.index})'
    else:
        title = f.caption + f' (fig {f.index})'
    file_stem = make_valid_filename(title)
    out_filename = output_path / (file_stem + '.emf')
    logmsg(f"    Exporting figure {f.caption}")
    img_provider.write_image(f.key, out_filename)
    return [
        title,
        str(out_filename.name),
    ]

def export_table(t: Table, output_path: Path, title: Optional[str] = None) -> None:
    if title is None:
        if match := PAGE_TABLE_RE.match(t.caption):
            title = match['pagedesc'] + ' ' + match['title']
        elif match := QUALIFIED_TABLE_RE.match(t.caption):
            title = match['title']
        else:
            title = t.caption

        # name compatibility
        if title == 'Function blocks': title = 'AC 800M Function Blocks'

    file_name = make_valid_filename(title) + '.csv'
    output_path.mkdir(parents=True, exist_ok=True)
    file_path = output_path / file_name
    logmsg(f"    Exporting table {t.caption}")
    with open(file_path, 'wb') as csvfile:
        csvfile.write(codecs.BOM_UTF16_LE)
    with open(file_path, 'a', newline='', encoding='utf_16_le') as csvfile:
        csvwriter = csv.writer(csvfile)
        csvwriter.writerow(t.headers)
        for row in t.data:
            csvwriter.writerow(row)