################################################################################################################
# This script is a simple management system for series made of exercises and solution.                         #
# It is possible to make zipped series for moodle, a zip containing all series. Furthermore one can            #
# generate previews for one exercise/solution. Two handy functions are the make-workbook and the               #
# make-catalogue. The former one creaets a pdf containig all series, each one followed by its solution         #
# just like they were distributed. The latter one create a sort of index of all available exercises in         #
# the system. Each exercise is followed by its solution.                                                       #
#                                                                                                              #
# The structure for a new exercise should be created by using the make-new-exercise function.                  #
# For further help, please refer to the help function of the software.                                         #
# -------------------------------------------------------------------------------------------------------------#
# Author: Andreas Ruppen                                                                                       #                                                                                                                                                             #
# Licensed under the Apache License, Version 2.0 (the "License");                                              #
#   you may not use this file except in compliance with the License.                                           #
#   You may obtain a copy of the License at                                                                    #
#                                                                                                              #
#       http://www.apache.org/licenses/LICENSE-2.0                                                             #
#                                                                                                              #
#   Unless required by applicable law or agreed to in writing, software                                        #
#   distributed under the License is distributed on an "AS IS" BASIS,                                          #
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.                                   #
#   See the License for the specific language governing permissions and                                        #
#   limitations under the License.                                                                             #
################################################################################################################
import datetime
import logging
import os
import re
import shutil
import subprocess
import sys
from subprocess import DEVNULL
from subprocess import STDOUT

log = logging.getLogger("seriesManagementSystem")


def cleanDSStore(dir: str) -> None:
    def cleaner(currdir: str, dirs: list[str], files: list[str]) -> None:
        for item in files:
            if item.find("DS_Store") != -1:
                log.debug("removing " + currdir + "/" + item)
                os.remove(os.path.join(currdir, item))

    for root, dirs, files in os.walk(dir):
        # self.cleanDSStore(root, dirs, files)
        cleaner(root, dirs, files)


def cleanTempFiles(keepTempFiles: bool) -> None:
    if keepTempFiles:
        return
    log.info("Removing temp files")
    tmpfile = os.listdir("/tmp")
    tmpfile.sort()
    for file in tmpfile:
        if (
            file.find("serie") != -1
            or file.find("solution") != -1
            or file.find("wbtitle") != -1
            or file.find("catalog") != -1
            or file.find("exam") != -1
        ):
            log.debug("Removing /tmp/" + file)
            os.remove("/tmp/" + file)


def nextUnusedExercice(dir: str) -> int:
    lastExo = 0
    dirs = os.listdir(dir)
    # dirs.sort()
    dirs = natsort(dirs)
    for dir in dirs:
        exoNumber = int(dir.partition("ex")[2])
        if exoNumber > lastExo:
            lastExo = exoNumber
    lastExo = int(lastExo) + 1
    return lastExo


def natsort(list_: list[str]) -> list[str]:
    log.debug("Will filter the list " + str(list_))
    filteredList = [elem for elem in list_ if not elem.startswith(".")]
    log.debug("Filtered list: " + str(filteredList))
    # decorate
    tmp = [(int(re.search(r"\d+", i).group(0)), i) for i in filteredList]  # type: ignore
    log.debug("Tmp list: " + str(tmp))
    tmp.sort()
    #   undecorate
    return [i[1] for i in tmp]


def doCheckInstall() -> None:
    """Verifies if all needed packets are installed"""
    log.info("Checking dependencies")
    missingProgs = []
    try:
        subprocess.check_call("which gs", shell=True, stdout=open("/dev/null", "w"))
    except subprocess.CalledProcessError as e:
        log.error("Did not found ghostscript (gs)")
        log.debug(e)
        missingProgs.append("Ghostscript (gs)")
    try:
        subprocess.check_call(
            "which pdflatex", shell=True, stdout=open("/dev/null", "w")
        )
    except subprocess.CalledProcessError as e:
        log.error("Did not found pdflatex")
        log.debug(e)
        missingProgs.append("PdfLaTeX")
    try:
        subprocess.check_call(
            "which latexmk", shell=True, stdout=open("/dev/null", "w")
        )
    except subprocess.CalledProcessError as e:
        log.error("Did not found latexmk")
        log.debug(e)
        missingProgs.append("latexmk")
    try:
        subprocess.check_call("which tar", shell=True, stdout=open("/dev/null", "w"))
    except subprocess.CalledProcessError as e:
        log.error("Did not found Tar utility")
        log.debug(e)
        missingProgs.append("Tar")
    try:
        if len(missingProgs) != 0:
            raise Exception(missingProgs)
    except Exception:
        log.error(
            "Please ensure that the needed utilities ("
            + str(missingProgs)
            + ") are installed and on the $PATH"
        )
        sys.exit(-1)


def doLatex(texFile: str, outputDir: str, doBibTex: bool = False) -> None:
    log.info("Running latex in %s on file %s", outputDir, texFile)
    log.debug(
        f"LaTeX command is: latexmk -pdf -silent -outdir={outputDir:s} {texFile:s}"
    )
    subprocess.call(
        ["latexmk", "-pdf", "-silent", "-outdir=" + outputDir, texFile],
        cwd="./",
        stdout=DEVNULL,
        stderr=STDOUT,
    )
    log.info("Compilation succeded " + texFile)
    # Alternatively use latexmk -c -jobname=texFile plus remove the *.tex file
    tmpfiles = os.listdir(outputDir)
    tmpfiles.sort()
    basename = os.path.split(texFile)[1].split(".")[0]
    for file in tmpfiles:
        if file.find(".pdf") == -1 and file.find(basename) != -1:
            os.remove(os.path.join(outputDir, file))


def doLatex2(texFile: str, outputDir: str, doBibTex: bool = False) -> None:
    log.info("Running latex in %s on file %s", outputDir, texFile)
    log.debug(
        "LaTeX command is: pdflatex -output-directory=" + outputDir + " " + texFile
    )
    # Genral settings for latex copmiling
    latex_error_messages = (  # noqa: F841
        "Type X to quit or <RETURN> to proceed",
        "! Undefined control sequence.",
        "? ",
        "Type  H <return>  for immediate help.",
        "Enter file name: ",
        "or enter new name. (Default extension: sty)",
    )
    latex_recompile_messages = ("recompile", "re-run", "undefined references", "rerun")
    auxFile = os.path.split(texFile)[1].split(".")[0] + ".aux"
    logFile = os.path.split(texFile)[1].split(".")[0] + ".log"
    recompile = True
    counter = 0
    while recompile and counter < 5:
        recompile = False
        counter += 1
        status = subprocess.call(
            ["pdflatex", "-output-directory=" + outputDir, "-halt-on-error", texFile],
            cwd="./",
            stdout=open("/dev/null", "w"),
        )
        if doBibTex:
            bibstatus = subprocess.call(
                ["bibtex", os.path.join(outputDir, auxFile)],
                cwd="./",
                stdout=open("/dev/null", "w"),
            )
        else:
            bibstatus = 0
        if status != 0:
            log.error(
                "Compilation error occured. Try executing by hand pdflatex -output-directory="
                + outputDir
                + " -halt-on-error "
                + texFile
            )
            exit(1)
        if bibstatus != 0:
            log.error(
                "Compilation error occured. Try executing by hand bibtex "
                + os.path.join(outputDir, auxFile)
            )
            exit(1)
        logfile = open(os.path.join(outputDir, logFile))
        for line in logfile:
            for msg in latex_recompile_messages:
                if msg in line:
                    recompile = True
                    log.info("Need to recompile " + texFile)
                    break
        logfile.close()
    log.info("Compilation succeded " + texFile)
    tmpfiles = os.listdir(outputDir)
    tmpfiles.sort()
    basename = os.path.split(texFile)[1].split(".")[0]
    for file in tmpfiles:
        if file.find(".pdf") == -1 and file.find(basename) != -1:
            os.remove(os.path.join(outputDir, file))


def doUpdateBibTex(bibtexfile: str, noCiteList: list[str]) -> None:
    """Updates the last visited date of the nocite bibtex items"""
    log.info("Updating BibTex Last visited time stamp")
    bibtex = open(bibtexfile)
    bibtexnew = open(os.path.join("/tmp/", bibtexfile), "w")
    start = False
    year = datetime.datetime.now().strftime("%Y")
    month = datetime.datetime.now().strftime("%b")
    day = datetime.datetime.now().strftime("%d")
    dateExpr = re.compile(r"\{\d*\}\{\w+?\}\{\d*\}")
    for line in bibtex:
        if line.find("biburl{") != -1 and start:
            partitions = dateExpr.split(line)
            log.debug(partitions)
            bibtexnew.write(
                partitions[0]
                + "{"
                + day
                + "}{"
                + month
                + "}{"
                + year
                + "}"
                + partitions[1]
            )
        else:
            bibtexnew.write(line)
        for cite in noCiteList:
            if line.find(cite) != -1:
                start = True
        if line.find("}") != -1 and line.find("},") == -1:
            start = False
    bibtex.close()
    bibtexnew.close()
    shutil.copy(os.path.join("/tmp/", bibtexfile), bibtexfile)
