import os.path
from ase.data import atomic_masses, atomic_numbers
#------------------------------------------------------------------------------------------
def sort_composition(composition):
    """Sort a composition list by decreasing atomic mass.

in:
    composition (list of list): each entry is [symbol (str), count (int)].
out:
    list of list: same entries sorted by decreasing atomic mass of the element symbol.
"""
    s=[[x[0],x[1], atomic_masses[atomic_numbers[x[0]]]] for x in composition]
    t = sorted(s, key=lambda x: float(x[2]), reverse=True)
    newcomposition=([[x[0],x[1]] for x in t])
    return newcomposition
#------------------------------------------------------------------------------------------
def get_inatoms(composition):
    """Expand a composition list into a flat list of element symbols.

in:
    composition (list of list): each entry is [symbol (str), count (int)].
out:
    list of str: flat list of element symbols sorted by decreasing atomic mass,
    with each symbol repeated according to its count.
"""
    newcomposition=sort_composition(composition)
    inatoms=[]
    for xxx in newcomposition:
        for jj in range(xxx[1]):
            inatoms.append(xxx[0])
    return inatoms
#------------------------------------------------------------------------------------------
def clustername(composition):
    """Build a human-readable cluster name string from a composition.

in:
    composition (list of list): each entry is [symbol (str), count (int)].
out:
    str: space-separated name string, e.g. 'Au10 Cu5', sorted by decreasing mass.
"""
    newcomposition=sort_composition(composition)
    chainname=' '.join([item[0]+str(item[1]) for item in newcomposition])
    return chainname
#------------------------------------------------------------------------------------------
def get_elements(composition):
    """Extract the list of unique element symbols from a composition.

in:
    composition (list of list): each entry is [symbol (str), count (int)].
out:
    list of str: element symbols sorted by decreasing atomic mass.
"""
    newcomposition=sort_composition(composition)
    atoms=([x[0] for x in newcomposition])
    return atoms
#------------------------------------------------------------------------------------------
def read_composition(filename, key):
    """Read a composition block from an AEGOn input file and return a Konpozition object.

in:
    filename (str): path to the input file.
    key (str): block identifier (case-insensitive); the block is delimited by
        ---KEY--- markers.
out:
    Konpozition: object with attributes:
        .comp    (list of [symbol, count]): sorted composition.
        .atoms   (list of str): flat list of element symbols.
        .name    (str): human-readable cluster name.
        .elements (list of str): unique element symbols.
Raises:
    ValueError if a line inside the block has an unexpected format.
"""
    start_marker = f"---{key.upper()}---"
    end_marker = start_marker
    composition = []
    recording = False
    with open(filename, 'r') as file:
        for line in file:
            stripped = line.strip()
            if stripped == start_marker:
                recording = not recording
                continue
            if recording and stripped:
                parts = stripped.split()
                if len(parts) == 2:
                    element, count = parts[0], int(parts[1])
                    composition.append([element, count])
                else:
                    raise ValueError(f"Invalid line format in block '{key}': '{line.strip()}'")
    class Konpozition:
        def __init__(self, xcomposition):
            self.comp    = sort_composition(xcomposition)
            self.atoms   = get_inatoms(self.comp)
            self.name    = clustername(self.comp)
            self.elements= get_elements(self.comp)
    classcomp=Konpozition(composition)
    return classcomp
#------------------------------------------------------------------------------------------
#PENDIENTE DE ELIMINAR
def read_block_from_file_antiguo(filepath, id):
    """Read lines from a delimited block in a file (legacy version, pending removal).

in:
    filepath (str): path to the input file.
    id (str): block identifier; the block is delimited by ---ID--- markers.
out:
    list of str: lines within the block (including trailing newlines).
"""
    bilfile=open(filepath,"r")
    chainchar='---'+id.upper()+'---'
    printer=0
    data_block=[]
    for line in bilfile:
         lin = line.lstrip()
         if lin.startswith(chainchar): printer=1+printer
         if printer == 1 and not lin.startswith(chainchar): data_block.append(line)
    bilfile.close()
    return data_block
#------------------------------------------------------------------------------------------
def read_block_from_file(filepath, id):
    """Read lines from a delimited block in a file.

in:
    filepath (str): path to the input file.
    id (str): block identifier (case-insensitive); the block is delimited by
        ---ID--- markers.
out:
    list of str: lines within the block with trailing whitespace stripped.
"""
    block = []
    inside = False
    chainchar='---'+id.upper()+'---'
    with open(filepath, "r") as f:
        for line in f:
            if line.strip() == chainchar:
                inside = not inside
                continue
            if inside:
                block.append(line.rstrip())
    #block="\n".join(block)
    #return block.splitlines()
    return block
#------------------------------------------------------------------------------------------
def get_value_from_file(filepath, key, dtype=str, default=None):
    """Read a single key-value pair from a parameter file, ignoring comments.

in:
    filepath (str): path to the input file.
    key (str): parameter name to search for (case-insensitive).
    dtype (type): Python type to cast the value to (int, float, or str).
    default: value to return if the key is not found or the file does not exist.
out:
    parsed value of type dtype, or default if the key is missing or conversion fails.
"""
    if not os.path.isfile(filepath):
        return default
    with open(filepath, 'r') as f:
        for line in f:
            line = line.split('#')[0].strip()
            if not line:
                continue
            parts = line.split()
            if len(parts) >= 2 and parts[0].lower() == key.lower():
                value_str = parts[1]
                try:
                    return dtype(value_str)
                except ValueError:
                    print(f"Warning: Could not convert '{value_str}' to {dtype.__name__}. Returning default.")
                    return default
    return default
#------------------------------------------------------------------------------------------
def get_list_from_file(filepath, key, datatype, default=None):
    """Read a whitespace-separated list of values for a given key from a parameter file.

in:
    filepath (str): path to the input file.
    key (str): parameter name to search for (case-insensitive).
    datatype (type): Python type to cast each list element to (int, float, or str).
    default (list): value to return if the key is not found (default None -> []).
out:
    list: values of type datatype parsed from the line following the key, or default
    if the key is missing, the file does not exist, or conversion fails.
"""
    if default is None:
        default = []
    if not os.path.isfile(filepath):
        return default
    with open(filepath, 'r') as f:
        for line in f:
            line = line.split('#')[0].strip()
            if not line:
                continue
            parts = line.split()
            if parts and parts[0].lower() == key.lower():
                try:
                    return [datatype(x) for x in parts[1:]]
                except ValueError:
                    print(f"Warning: Could not convert values in key '{key}' to {datatype.__name__}.")
                    return default
    return default
#------------------------------------------------------------------------------------------
class read_main_input:
    """Convenience class for reading typed parameters and blocks from an AEGOn input file.

    Wraps get_value_from_file, get_list_from_file, read_block_from_file, and
    read_composition with a file-bound interface.
    """

    def __init__(self, filename):
        """Initialize the input reader bound to a specific file.

in:
    filename (str): path to the AEGOn input file.
out:
    read_main_input instance.
"""
        self.filename = filename

    def get_int(self, key, default):
        """Read an integer parameter from the input file.

in:
    key (str): parameter name.
    default (int): fallback value if key is not found.
out:
    int: parsed value or default.
"""
        return get_value_from_file(self.filename, key, int, default)

    def get_float(self, key, default):
        """Read a float parameter from the input file.

in:
    key (str): parameter name.
    default (float): fallback value if key is not found.
out:
    float: parsed value or default.
"""
        return get_value_from_file(self.filename, key, float, default)

    def get_str(self, key, default):
        """Read a string parameter from the input file.

in:
    key (str): parameter name.
    default (str): fallback value if key is not found.
out:
    str: parsed value or default.
"""
        return get_value_from_file(self.filename, key, str, default)

    def get_int_list(self, key, default):
        """Read a list of integers from the input file.

in:
    key (str): parameter name.
    default (list): fallback list if key is not found.
out:
    list of int: parsed values or default.
"""
        return get_list_from_file(self.filename, key, int, default)

    def get_float_list(self, key, default):
        """Read a list of floats from the input file.

in:
    key (str): parameter name.
    default (list): fallback list if key is not found.
out:
    list of float: parsed values or default.
"""
        return get_list_from_file(self.filename, key, float, default)

    def get_str_list(self, key, default):
        """Read a list of strings from the input file.

in:
    key (str): parameter name.
    default (list): fallback list if key is not found.
out:
    list of str: parsed values or default.
"""
        return get_list_from_file(self.filename, key, str, default)

    def get_block(self, key):
        """Read a multi-line block from the input file.

in:
    key (str): block identifier delimited by ---KEY--- markers.
out:
    list of str: lines within the block.
"""
        return read_block_from_file(self.filename, key)

    def get_comp(self, key, index=0):
        """Read a composition block and return a Konpozition object.

in:
    key (str): block identifier for the composition section.
    index (int): unused, kept for API compatibility.
out:
    Konpozition: object with .comp, .atoms, .name, and .elements attributes.
"""
        return read_composition(self.filename, key)
#------------------------------------------------------------------------------------------
