#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 1998-2026 Stephane Galland <galland@arakhne.org>
#
# This program is free library; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or any later version.
#
# This library is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; see the file COPYING.  If not,
# write to the Free Software Foundation, Inc., 59 Temple Place - Suite
# 330, Boston, MA 02111-1307, USA.

"""
General utilities for TeX.
"""

import os
from enum import IntEnum, unique
from pathlib import Path
from typing import Callable
from dataclasses import dataclass

from autolatex2.utils.i18n import T
import autolatex2.utils.utilfunctions as genutils

EXTENDED_TEX_CODE_FILENAME_POSTFIX = "_autolatex_autogenerated"




@dataclass
class TeXMacroParameter:
	"""
	Definition of a parameter for a TeX macro.
	"""
	text : str
	index : int = -1
	optional : bool = False
	evaluable : bool = True
	macro_name : bool = False



@unique
class FileType(IntEnum):
	"""
	Type of file in the making process.
	"""
	aux  = 1
	bcf  = 2
	bbc  = 3
	bbl  = 4
	bbx  = 5
	bib  = 6
	bst  = 7
	cbx  = 8
	cls  = 9
	dvi  = 10
	glo  = 11
	gls  = 12
	idx  = 13
	ind  = 14
	log  = 15
	pdf  = 16
	ps   = 17
	sty  = 18
	tex  = 19
	xdv  = 20
	xdvi = 21

	def is_source_type(self) -> bool:
		"""
		Replies if the given type is assumed to be a source type.
		A source type is usually not the result of a tool usage, but it might contain other source types by inclusion.
		:return: Tue if the type corresponds to a source type; Otherwise, it is a type of file that is the result of
		a computation.
		:rtype: bool
		"""
		return self == FileType.tex or self == FileType.bib or self == FileType.sty

	def extension(self) -> str:
		"""
		Replies the major filename extension for the current file type.
		:return: The major filename extension prefixed with '.'.
		:rtype: str
		"""
		return '.' + self.name

	def extensions(self) -> list[str]:
		"""
		Replies the supported filename extensions for current file type.
		:return: The list of the filename extensions.
		:rtype: list[str]
		"""
		if self == FileType.tex:
			return ['.tex', '.latex', '.ltx']
		else:
			return [ '.' + self.name ]

	def is_file(self, filename : str) -> bool:
		"""
		Replies the given filename has the filename extensions of the current file type.
		:rtype: bool
		"""
		if filename:
			ext = os.path.splitext(filename)[-1]
			if ext:
				return ext.lower() in self.extensions()
		return False

	@staticmethod
	def output_types() -> list['FileType']:
		"""
		Replies all the file types that are related to the output result of TeX tools.
		:return: the list of file types.
		:rtype: list[FileType]
		"""
		return [FileType.pdf, FileType.ps, FileType.dvi, FileType.xdvi, FileType.xdv]

	@staticmethod
	def output_extensions() -> list[str]:
		"""
		Replies all the file types extensions that are related to the output result of TeX tools.
		:return: the list of file extensions.
		:rtype: list[str]
		"""
		exts = list()
		for ext in FileType.output_types():
			exts.extend(ext.extensions())
		return exts

	@staticmethod
	def tex_types() -> list['FileType']:
		"""
		Replies all the file types that are related to the TeX code.
		:return: the list of file types.
		:rtype: list[FileType]
		"""
		return [FileType.tex, FileType.cls, FileType.sty]

	@staticmethod
	def tex_extensions() -> list[str]:
		"""
		Replies the supported filename extensions for TeX files.
		:return: The list of the filename extensions.
		:rtype: list[str]
		"""
		exts = list()
		for ext in FileType.tex_types():
			exts.extend(ext.extensions())
		return exts

	@staticmethod
	def is_tex_extension(ext : str) -> bool:
		"""
		Test if a given string is a standard extension for TeX document. The ext must start with a '.'.
		The test is case-insensitive.
		:param ext: The extension to test.
		:type ext: str
		:return: True if the extension is for a TeX/LaTeX file; otherwise False.
		:rtype: bool
		"""
		ext = ext.lower()
		for file_type in FileType.tex_types():
			if ext in file_type.extensions():
				return True
		return False

	@staticmethod
	def is_tex_document(filename : str) -> bool:
		"""
		Replies if the given filename is a TeX document.
		The test is case-insensitive.
		:param filename: The filename to test.
		:type filename: str
		:return: True if the extension is for a TeX/LaTeX file; otherwise False.
		:rtype: bool
		"""
		if filename:
			ext = os.path.splitext(filename)[-1]
			return FileType.is_tex_extension(ext)
		return False

	@staticmethod
	def bibliography_types() -> list['FileType']:
		"""
		Replies all the file types that are related to the bibliography.
		:return: the list of file types.
		:rtype: list[FileType]
		"""
		return [FileType.bib, FileType.bbl, FileType.bst, FileType.bbc, FileType.bcf, FileType.bbx, FileType.cbx]

	@staticmethod
	def bibliography_extensions() -> list[str]:
		"""
		Replies the supported filename extensions for bibliography files.
		:return: The list of the filename extensions.
		:rtype: list[str]
		"""
		exts = list()
		for ext in FileType.bibliography_types():
			exts.extend(ext.extensions())
		return exts

	@staticmethod
	def glossary_types() -> list['FileType']:
		"""
		Replies all the file types that are related to the glossary.
		:return: the list of file types.
		:rtype: list[FileType]
		"""
		return [FileType.glo, FileType.gls]

	@staticmethod
	def glossary_extensions() -> list[str]:
		"""
		Replies the supported filename extensions for glossary files.
		:return: The list of the filename extensions.
		:rtype: list[str]
		"""
		exts = list()
		for ext in FileType.glossary_types():
			exts.extend(ext.extensions())
		return exts

	@staticmethod
	def index_types() -> list['FileType']:
		"""
		Replies all the file types that are related to the index.
		:return: the list of file types.
		:rtype: list[FileType]
		"""
		return [FileType.idx, FileType.ind]

	@staticmethod
	def index_extensions() -> list[str]:
		"""
		Replies the supported filename extensions for index files.
		:return: The list of the filename extensions.
		:rtype: list[str]
		"""
		exts = list()
		for ext in FileType.index_types():
			exts.extend(ext.extensions())
		return exts

	def ensure_extension(self, filename : str) -> str:
		"""
		Ensure the given filename has the extension of this file type. Any previous file extension is removed
		from the given filename. If multiple file extensions are specified in the given filename, only the
		last one is removed.
		:param filename: the filename to check.
		:type filename: str
		:return: the filename with the correct file extension
		:rtype: str
		"""
		return genutils.ensure_filename_extension(filename, self.extension())

	def add_extension(self, filename : str) -> str:
		"""
		Ensure the given filename has the extension of this file type. Any previous file extension is NOT removed
		from the given filename.
		:param filename: the filename to check.
		:type filename: str
		:return: the filename with the correct file extension
		:rtype: str
		"""
		return filename + self.extension()


def find_aux_files(tex_file : str, selector : Callable[[str], bool] | None = None) -> list[str]:
	"""
	Recursively find all aux files that are located in the same folder as the given TeX file, or
	in one of its subfolders. For subfolders, it is mandatory that a TeX file with the name basename
	as the aux file exists. In the folder of the provided TeX file, all the aux files are considered.
	:param tex_file: The filename of the TeX file.
	:type tex_file: str
	:param selector: A lambda function that permits is used as a filtering function for the auxiliary files.
	The lambda takes one formal argument that is the auxiliary file's name. It replies True if the
	auxiliary file is accepted; Otherwise False.
	:type selector: Callable[[str], bool] | None
	:return: the list of the aux files that are validated the constraints and the given lambda selector.
	:rtype: list[str]
	"""
	folder_name = os.path.normpath(os.path.dirname(tex_file))
	directory = Path(folder_name)
	if not directory.exists():
		raise FileNotFoundError(T("Directory does not exist: %s") % folder_name)
	if not directory.is_dir():
		raise NotADirectoryError(T("Path is not a directory: %s") % folder_name)
	# Recursively find all .aux files
	aux_files : list[str] = list()
	for candidate in directory.rglob("*.aux"):
		aux_dir = os.path.normpath(os.path.dirname(candidate))
		candidate_name = str(candidate)
		if aux_dir == folder_name:
			if selector is None or selector(candidate_name):
				aux_files.append(candidate_name)
		else:
			additional_tex_file = FileType.tex.ensure_extension(candidate_name)
			if os.path.isfile(additional_tex_file) and (selector is None or selector(candidate_name)):
				aux_files.append(candidate_name)
	return aux_files

def create_extended_tex_filename(filename : str) -> str:
	"""
	Replies the filename of the TeX file when it is extending with code dedicated to the extended warning support.
	:param filename: the original filename.
	:type filename: str
	:return: the filename for the extended TeX code.
	:rtype: str
	"""
	ext = genutils.get_filename_extension_from(filename, *FileType.tex_extensions())
	if ext is not None:
		new_basename = genutils.basename_with_path(filename, ext)
	else:
		new_basename = filename
	new_basename += EXTENDED_TEX_CODE_FILENAME_POSTFIX
	if ext is not None:
		new_basename += ext
	return new_basename

def get_original_tex_filename(filename : str) -> str:
	"""
	Replies the filename of the TeX file when it is extending with specific code for supporting extended warnings.
	:param filename: the original filename.
	:type filename: str
	:return: the filename for the extended TeX code.
	:rtype: str
	"""
	ext = genutils.get_filename_extension_from(filename, *FileType.tex_extensions())
	if ext is not None:
		new_basename = genutils.basename_with_path(filename, ext)
	else:
		new_basename = filename
	if new_basename.endswith(EXTENDED_TEX_CODE_FILENAME_POSTFIX):
		new_basename = new_basename[0:-len(EXTENDED_TEX_CODE_FILENAME_POSTFIX)]
	if ext is not None:
		new_basename += ext
	return new_basename
