import struct

from pathlib import Path
from meshgen.primitives import Hexahedron
import logging

logger = logging.getLogger(__name__)


def read_header_jcm(fobj):
    """Read the header of a JCMsuite file.

    It is assumed that the header starts at the first line of the given file descriptor.
    The start of the header is defined by '/* <BLOBHead>' and its end by the first
    occurrence of '*/'. It reads the coordinate system info from the header and returns
    it in a dict if found.

    Args:
        fobj (file object): handle to the beginning of a jcm file

    Returns:
        dict
    """
    line = next(fobj)
    binary = False
    if isinstance(line, bytes):
        line = line.decode()
        binary = True
    if line != "/* <BLOBHead>\n":
        raise ValueError("missing header in jcm file")
    header = {}
    for line in fobj:
        line = line.decode() if binary else line
        if line == "*/\n":
            break
        key, *val = line.split("=")
        val = "=".join(val)
        header[key] = val[:-1]
    return header


def read_jcm(handle, mode, binary=False):
    """Read numbers from JCM file.

    The next numbers for the file are read and convert according to mode. The file is
    read either as a text or binary file. If the file is in text mode, each line must
    contain exactly one number.

    Args:
        handle (file object): file in text or binary mode
        mode (str): mode definition similar to `struct.unpack`, `idd` means one integer
            and two doubles and can also be written `i2d`
        binary (bool, default False): read file as text or binary file

    Returns:
        tuple
    """
    ndigits = 0
    if binary:
        vals = {"i": 4, "d": 8}
        nbytes = 0
        for char in mode:
            if char.isdigit():
                ndigits = ndigits * 10 + int(char)
                continue
            nbytes += max(ndigits, 1) * vals[char]
            ndigits = 0
        return struct.unpack("<" + mode, handle.read(nbytes))
    vals = {"i": int, "d": float}
    res = []
    for char in mode:
        if char.isdigit():
            ndigits = ndigits * 10 + int(char)
            continue
        for _ in range(max(ndigits, 1)):
            res.append(vals[char](next(handle)))
        ndigits = 0
    return tuple(res)


def jcm_node_order(nodes, element_cls):
    """Change the node order to/from JCMsuite style.

    It is exploited that JCMsuite just swaps the positions of two nodes and not more
    complicated reorderings.

    Args:
        nodes (list): nodes of the element
        element_cls (object): type of element from which the nodes are

    Returns:
        List
    """
    if element_cls == Hexahedron:
        return (
            nodes[0],
            nodes[1],
            nodes[3],
            nodes[2],
            nodes[4],
            nodes[5],
            nodes[7],
            nodes[6],
        )
    return nodes


def jcm_binary_to_ascii(infile, outfile):
    """Convert a binary JCMsuite file to an ASCII JCMsuite file.

    Args:
        infile (str): path to the input binary JCMsuite file
        outfile (str): path to the output ASCII JCMsuite file
    """
    try:
        import jcmwave
    except ImportError:
        raise ImportError(
            "jcmwave is required to convert binary JCMsuite files to ASCII format. Please link with 'find-jcm -l'."
        )

    this_file = Path(__file__).resolve()
    infile = str(Path(infile).resolve())
    outfile = str(Path(outfile).resolve())

    project_file = this_file.parent / "jcm_bin2ascii.jcmpt"
    logger.info(
        f"Converting binary JCMsuite file '{infile}' to ASCII format at '{outfile}' using jcmwave."
    )
    logger.info(f"Using project file: {project_file}")

    jcmwave.solve(
        str(project_file),
        mode="post_process",
        keys={"source": infile, "target": outfile},
    )
