
import sys
import os
import subprocess
import fnmatch
import matplotlib.pyplot as plt



# Get the current directory of the module
module_dir = os.path.dirname(os.path.abspath(__file__))
# Add the module's directory to the system path
sys.path.append(module_dir)

import regex
import argparse
import glob
from parser_functions import *




'''
Christian Damböck: 
valeptex Parser for LaTeX 

Version 0.1
Mai 2024

https://valep.vc.univie.ac.at/files/py/valeptex/

See the user manual: 
https://valep.vc.univie.ac.at/files/py/valeptex/valeptex_handbook.tex



To Do List: 
    - BUG Fixes at all levels ... testing of different example tex Documents ...
    
    BUG: single whitespace between two textit{} commands is getting ignored
    BUG: commas are incorrectly converted in formulas if there is no space before and after them

    OCTOBER:
    - docstrings in all functions and classes
    - adjust paragraphs: avoid putting mere system commands in <p> tags
    - jinja: add newline and/or empty lines depending on the Command type
        and remove empty lines from the jinja specs themselves 
        or respectively add strip() to the initial parser of the jinja specs
    - Json serialization
    - XML output
    - EPUB output
    - Parsing for XML/HTML
    - argument --allformats that produces output in all formats plus json
    - PACKAGE DISTRIBUTION: pip installation and the like




    - consistency of tags, especially <p>, probably all misplaced par instances must be deleted ...

    - boxes (should already work but need to be tested, rendering needs to be optimized)
    - rotateboxes (especially in klammerabsatz macros)

    - XML rendering
    - Json output, mere text output
    - docx rendering
    - epub rendering

    - rendering: 
    -- functions in expressions, e.g., to calculate correct lengths and the like
    -- bug fixes, consistent and valid document structure! 

    - tables: 
    -- cline commands (not yet interpreted at all)
    -- tabbing: add interpretation for esoteric tabstop commands
    -- cell width (probably with optional arguments ? )
    -- rowspan and colspan
    
    - data types and functions in rendering expressions
    -- e.g. calculations of lengths 
    -- klammerabsatz e.g. Mai 2-4 1913

    Formats: multiple underlinings in different colors (probably mainly CSS and/or Javascript)

    - graphics with includegraphics
    - batch conversion of eps files to svg (is not working properly)
    - theorems

    later:

    - Formulas need to be optimized the rendering does not yet work well!!

    - def macro definitions    
    - Static Table of Contents
    - Bibliographies

    
'''

###################################################################################
# LOAD DEFAULT FILE OPENING: 
# this needs to be done right at the beginning, before calling parse_latex
def load_default_preamble(stack):
    
    path = os.path.dirname(os.path.abspath(__file__)) + '/custom/default-fileopening.tex'
    if not os.path.exists(path): path = os.path.dirname(os.path.abspath(__file__)) + '/default-fileopening.tex'

    # Read the default file opening
    try:
        with open(path, 'r', encoding='utf-8') as f:
            default_fileopening = f.read()
            f.close()
    except: 
        stack.error += f'\n ERROR: File "{path}" could not be opened for parsing!\n'
        print(f'ERROR: File "{path} could not be opened for parsing!')
        default_fileopening = ''    

    stack = load_preamble_string(stack, default_fileopening)
    
    if stack.defaultpreamble == False: stack.packages.clear()

    return stack

# END LOADING DEFAULT FILE OPENING
####################################



################################################################################
# the following three functions are needed for
# LOADING ALL JINJA SPECIFICATIONS 
# = loads all files with the extension .jinja2s from directory
def find_files(directory, pattern):
    file_paths = []
    for root, _, files in os.walk(directory):
        for filename in files:
            if fnmatch.fnmatch(filename, pattern):
                file_paths.append(os.path.join(root, filename))
    return file_paths

folder_path = "path"  # Replace "path" with your folder path
file_paths = find_files(folder_path, "*.jinja2h")

# this is currently not needed:
def add_jinja_file(path):    
    try:
        with open(f'{path}', 'r', encoding='utf-8') as f:
            string = f.read()
            return f'{string}\n'
    except: 
        print(f'ERROR: File {path} could not be opened for parsing!')
        stack.error += f'\n ERROR: File {path} could not be opened for parsing!\n'
        return ''

def load_jinja_specs(stack, path):
    jinja_paths = find_files(path, '*.jinja2s')

    string = ''
    for jinja_path in jinja_paths:
        string += add_jinja_file(jinja_path)
    
    if string:
        # ... and parse jinja file: 
        stack.jinja = JinjaSpec(string, stack)
    else: 
        print(f'ERROR: No jinja specifications could be found in path "{path}"!')
        return None

    return stack
# END LOADING ALL JINJA SPECS
##########################################



#################################################################
# LOAD JINJA TEMPLATE
# first check the custom subfolder for a file default-layout.jinja2
# if this fails load the default-layout.jinja2 from the root folder
def load_jinja_template(stack, path):
    path = path + '/default-layout.jinja2'
    if not os.path.exists(path): 
        print('ERROR: no jinja template found!!')
        return None

    stack.template_string = add_jinja_file(path)

    return stack

# END LOADING JINJA TEMPLATE
##########################################



##########################################################################################
# Main function parse_latex:
#  - loads the tex file and packages
#  - calls all necessary parsing functions
#  - returns a html string
#

def parse_latex(tex_file_name, stack):
    
    if tex_file_name[-4:] == '.tex' or tex_file_name[-4:] == '.TEX':
        tex_file_name = tex_file_name[0:-4]

    tex_file_name = str(tex_file_name)

    stack.filename = tex_file_name

    print(f'This is valepTeX parser version 0.1 (Mai 2024): parsing of document {tex_file_name} begins:')

    # transient elements of stack need to be reset or emptied:
    stack.reset_counters()
    stack.reset_transient_elements()

    # FIRST STEP
    # start the parsing process
    stack.preamblemode = True
    tex_file, stack = start_parse(tex_file_name, stack)

    # SECOND AND MAIN STEP: 
    # parse blocks
    doc_list = DocNode()

    doc_list, stack = parse_blocks(tex_file, stack)

    print('... optimizes DOM tree and renders document ...')

    # THIRD STEP: brush up the DOM tree
    # (1) deal with notes of the form \NOTEmark and \NOTEtext:
    doc_list.supplement_note_marks(stack)
    # (2) flatten the docnode to facilitate iterating over the docnode lists of the DOM tree:
    doc_list.elements = doc_list.flatten_docnodes()
    # (3) pick out the document part:
    match = doc_list.find_object(document)
    dom_object = DocNode()
    if match: dom_object.elements.append(match)
    else: 
        print('WARNING: incorrect document structure!')
        dom_object = doc_list
    # (4) adjust paragraphs:
    dom_object.adjust_paragraphs()


    # FOURTH STEP:
    # render document 

    string = dom_object.render(stack.jinja)


    # FIFTH STEP:
    # embed the rendered document in the page template
    template = JinjaDocument(string, stack.template_string, stack)
    
    string = dom_object.render_document(template, string)

    # SIXTH STEP: clean up
    # remove empty <p> tags
    string = regex.sub(r'<p>\s*<\/p>','', string)
    string = regex.sub(r'<p>\s*<p>',r'<p>', string) # this is just an ad hoc fix!
    string = regex.sub(r'<\/p>\s*<\/p>',r'</p>', string) # this is just an ad hoc fix!
    
    

    # deal with xml specialities:
    if stack.output_format == 'xml':
        html_entities = {
            "&uuml;": "ü",
            "&auml;": "ä",
            "&ouml;": "ö",
            "&Uuml;": "Ü",
            "&Auml;": "Ä",
            "&Ouml;": "Ö",
            "&szlig;": "ß",
            "&frasl;": "&#8260;",
            "&centerdot;": "&#183;",
            # Fügen Sie hier weitere HTML-Sonderzeichen nach Bedarf hinzu
        }
        for entity, char in html_entities.items():
            string = string.replace(entity, char)

    ##########################################################
    # THIS IS VERY AD HOC AND NEEDS TO BE GENERALIZED FOR OTHER VERBATIM Commands
    # if its html or xml: clean up <pre> tags to avoid getting <tags> interpreted as tags
    def repl(match):
        arg = match.group(1)
        arg = arg.replace('<','&lt;')
        arg = arg.replace('>','&gt;')
        replacement = f'<pre>{arg}</pre>'
        return replacement
    string = regex.sub(r'<pre>([\s\S]*?)</pre>', repl, string)

    if stack.normalize:
        normal_text = doc_list.normalize(stack.jinja).strip()
        try:
            with open(f'{tex_file_name}.txt', 'w', encoding='utf-8') as f:
                f.write(normal_text)
        except: 
            print(f'ERROR: could not write {tex_file_name}.text!')

    # only carnap edition: 
    if stack.carnapedition:
        ######################################################################
        # IDIOSYNCRATIC STUFF CARNAP EDITION
        # POSTPROCESSING HTML FILE WITH 
        # postprocess the html file with functions from htnmlcarnap
        
        # add counters and labels to notes, indices, and readinglists
        string = replace_tag_index(string, 'in-dex class="person"', '')
        string = replace_tag_index(string, 'in-dex class="institution"', '')
        string = replace_tag_index(string, 'in-dex class="location"', '')
        string = replace_tag_index(string, 'in-dex class="sache"', '')
        # needs to be extended with: concepts, other doc categories ...
        string = replace_tag_index(string, 'in-dex class="doc"', 'B')
        string = replace_tag_index(string, 'fac-simile', '')
        string = replace_tag_index(string, 'loc-ation', 'O')
        string = add_all_to_reading_lists(string)
        string = replace_triggers(string)
        # resolve references to diary entries
        string = resolve_diary_references(string)

        # add content to diary entry time tags and add monthly label in left margin
        string = fill_time_tags(string)
        
        # END CARNAP EDITION STUFF
        ######################################################################

    # add title to headline and task bar and produce the table of content
    string, stack = make_title_toc(string, stack)

    # make bilingual representations using the format of the Carnap edition
    string, stack = merge_bilingual(string, stack)
    
    #print(normal_text)

    # SEVENTH STEP:
    # return string and stack:
    html_content = string

    # dirty trick for correct processing of \NOTEmark and \NOTEtext notes:
    stack.firstdoc = False

    return html_content, stack

# 
# end main function parse_latex
#######################################


# write the log and/or unknown commands in a logfile
def write_log(stack):
    log = ''
    if stack.writeunknown: log = stack.expand_unknown()
    if stack.writelog: 
        log = stack.expand()
        log += stack.error
    if log:
        path = os.path.dirname(os.path.abspath(__file__)) + '/valeptex.log'
        with open(path, 'w', encoding = 'utf-8') as f:
            f.write(log)
            f.close()


#######################################
# calls parse_latex and 
# writes the returned html string 
# in a file tex_tile_name.html
def parse_latex_store_html(tex_file_name, stack):
    
    # remove file ending .tex if applicable
    if tex_file_name[-4:] == '.tex':
        tex_file_name = tex_file_name[0:-4]

    # call main function parse_latex
    html_content, stack = parse_latex(tex_file_name, stack)

    # Write rendered file to a .html file
    temp_tex_file_name = f'{tex_file_name}.{stack.output_format}'
    try:
        with open(temp_tex_file_name, 'w', encoding='utf-8') as f:
            f.write(html_content)
    except: 
        print(f'ERROR: could not write {temp_tex_file_name}!')

    undefined = stack.expand_unknown()
    #print(undefined)

    return stack



###############################################################################################
# BATCH PROCESSING
# batch processing of parse_latex: either convert all .tex files in the current folder or
# in the current folder together with all its subfolders ::: options --folder and --subfolders

def convert_folder_tex_to_html(folder_path, stack, include_subfolders=False):

    pattern = os.path.join(folder_path, '**/*.tex') if include_subfolders else os.path.join(folder_path, '*.tex')
    error = ""
    for tex_file in glob.iglob(pattern, recursive=True):
        # change to subdir
        directory = os.path.dirname(tex_file)
        os.chdir(directory)
        # use relativ filepath 
        tex_file = os.path.relpath(tex_file)  
        
        try:
            stack = parse_latex_store_html(tex_file, stack)
        except: 
            print(f"ERROR: conversion of file {tex_file} failed.")
            error += f"ERROR: conversion of file {tex_file} failed."

    write_log(stack)



# Function to convert EPS to SVG using matplotlib with improved quality
def convert_eps_to_svg(input_path, output_path, dpi=300):
    try:
        # Load the EPS file using matplotlib
        fig = plt.figure(dpi=dpi)
        plt.axis('off')
        plt.imshow(plt.imread(input_path))
        
        # Save the figure as SVG with improved quality
        plt.savefig(output_path, format='svg', bbox_inches='tight', pad_inches=0)
        plt.close(fig)
        
        print(f"Converted {input_path} to {output_path} with DPI={dpi}")
    except Exception as e:
        print(f"Error converting {input_path}: {e}")

# Function to recursively search for EPS files and convert them to SVG
def convert_eps_in_directory(directory):
    for root, _, files in os.walk(directory):
        for file in files:
            if file.lower().endswith('.eps'):
                input_path = os.path.join(root, file)
                output_path = os.path.splitext(input_path)[0] + '.svg'
                convert_eps_to_svg(input_path, output_path)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Parse LaTeX")
    parser.add_argument("tex_file", nargs="?", help="Path to the .tex file to convert")
    parser.add_argument("--folder", action="store_true", help="Convert .tex files in the current folder")
    parser.add_argument("--subfolders", action="store_true", help="Convert .tex files in all subfolders")
    parser.add_argument("--normalize", action="store_true", help="save also a .txt version of the document")
    parser.add_argument("--nologfile", action="store_true", help="do not save the valeptex.log")
    parser.add_argument("--writeunknown", action="store_true", help="only write the unknown commands in the logfile")
    parser.add_argument("--useownpreamble", action="store_true", help="use the original preamble of the file")
    parser.add_argument("--convertsvg", action="store_true", help="use the original preamble of the file")
    parser.add_argument("--format", help="Specify the file format by its file ending")

    args = parser.parse_args()

    # ATTENTION: DocStack needs to be created and filled before calling parse_latex
    # this would be necessary also when calling parse_latex from somewhere else
    stack = DocStack()

    if args.format and args.format != 'html': 
        file_format = args.format
        script_dir = os.path.dirname(os.path.abspath(__file__))
        subfolder_path = os.path.join(script_dir, file_format)
        if os.path.exists(subfolder_path) and os.path.isdir(subfolder_path):
            stack.output_format = args.format
            path = subfolder_path
            stack = load_jinja_specs(stack, path)
            if stack == None: sys.exit(1)
            # load the jinja template
            stack = load_jinja_template(stack, path)
            if stack == None: sys.exit(1)

            print(f"Identified format folder {file_format} and loaded jinja specs.")
        else:
            print(f"WARNING: subfolder '{file_format}' does not exist! HTML rendering is chosen instead ...")
            stack.output_format = 'html'

    if stack.output_format == 'html':
        script_dir = os.path.dirname(os.path.abspath(__file__))
        path = os.path.join(script_dir, 'html')
        stack = load_jinja_specs(stack, path)
        if stack == None: sys.exit(1)
        # load the jinja template
        stack = load_jinja_template(stack, path)
        if stack == None: sys.exit(1)
        print('Rendering information for html has been loaded.')



    stack = load_default_preamble(stack)
    # put all jinja specs in stack

    if args.normalize: stack.normalize = True
    if args.nologfile: stack.writelog = False
    if args.writeunknown: stack.writeunknown = True
    if args.useownpreamble: stack.defaultpreamble = False
    

    if args.tex_file:
        stack = parse_latex_store_html(args.tex_file, stack)
        write_log(stack)
    elif args.folder:
        current_folder = os.getcwd()
        print("Converting .tex files in the current folder...")
        convert_folder_tex_to_html(current_folder, stack)
    elif args.subfolders:
        current_folder = os.getcwd()
        print("Converting .tex files in all subfolders...")
        convert_folder_tex_to_html(current_folder, stack, include_subfolders=True)    
    elif args.convertsvg:
        eps_directory = os.getcwd()
        #convert_eps_in_directory(eps_directory)
        inkscape_path = r"C:\Program Files\Inkscape\bin\inkscape.exe"
        for root, dirs, files in os.walk(eps_directory):
            for file in files:
                if file.endswith('.eps'):
                    eps_file = os.path.join(root, file)
                    svg_file = os.path.splitext(eps_file)[0] + '.svg'
                    print(f"Converting {eps_file} to {svg_file}")
                    subprocess.run([inkscape_path, '--export-plain-svg=' + svg_file, eps_file], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    else:
        print("Error: Please specify either a .tex file or --folder, --subfolders, or both.")

