#!/usr/bin/env python3
# coding: utf-8

# Assembles local KiCad component libraries from downloaded Octopart,
# Samacsys, Ultralibrarian and Snapeda zipfiles. Currently assembles just
# symbols and footptints. Tested with KiCad 7.0 for Ubuntu.
import pathlib
from enum import Enum
from zipfile import Path
from typing import Tuple, Union, Any
import re
import zipfile
from os import stat, remove
from os.path import isfile
import logging


if __name__ == "__main__":
    from kicad_cli import kicad_cli
    from s_expression_parse import parse_sexp, search_recursive, extract_properties
else:
    from .kicad_cli import kicad_cli
    from .s_expression_parse import parse_sexp, search_recursive, extract_properties

cli = kicad_cli()


class Modification(Enum):
    MKDIR = 0
    TOUCH_FILE = 1
    MODIFIED_FILE = 2
    EXTRACTED_FILE = 3


class ModifiedObject:
    def __init__(self):
        self.dict = {}

    def append(self, obj: pathlib.Path, modification: Modification):
        self.dict[obj] = modification


# keeps track of which files were modified in case an error occurs we can revert these changes before exiting
modified_objects = ModifiedObject()


def check_file(path: pathlib.Path):
    """
    Check if file exists, if not create parent directories and touch file
    :param path:
    """
    if not path.exists():
        if not path.parent.is_dir():
            path.parent.mkdir(parents=True)
            modified_objects.append(path.parent, Modification.MKDIR)
        path.touch(mode=0o666)
        modified_objects.append(path, Modification.TOUCH_FILE)


def unzip(root, suffix):
    """
    return zipfile.Path starting with root ending with suffix else return None
    """
    match = None

    def zipper(parent):
        if parent.name.endswith(suffix):
            return parent
        elif parent.is_dir():
            for child in parent.iterdir():
                match = zipper(child)
                if match:
                    return match

    match = zipper(root)

    return match


class REMOTE_TYPES(Enum):
    Octopart = 0
    Samacsys = 1
    UltraLibrarian = 2
    Snapeda = 3


class import_lib:
    def print(self, txt):
        print("->" + txt)

    def __init__(self):
        self.KICAD_3RD_PARTY_LINK = "${KICAD_3RD_PARTY}"

    def set_DEST_PATH(self, DEST_PATH_=pathlib.Path.home() / "KiCad"):
        self.DEST_PATH = pathlib.Path(DEST_PATH_)

    def cleanName(self, name):
        invalid = '<>:"/\\|?* '
        name = name.strip()
        for char in invalid:  # remove invalid characters
            name = name.replace(char, "_")
        return name

    def get_remote_info(
        self, zf: zipfile.ZipFile
    ) -> Tuple[Path, Path, Path, Path, REMOTE_TYPES]:
        """
        :param root_path:
        :type root_path: Path
        :return: dcm_path, lib_path, footprint_path, model_path, remote_type
        """
        self.footprint_name = None

        root_path = zipfile.Path(zf)
        self.dcm_path = root_path / "device.dcm"
        self.lib_path = root_path / "device.lib"
        self.footprint_path = root_path / "device.pretty"
        self.model_path = root_path / "device.step"
        # todo fill in model path for OCTOPART
        if (
            self.dcm_path.exists()
            and self.lib_path.exists()
            and self.footprint_path.exists()
        ):
            remote_type = REMOTE_TYPES.Octopart
            return (
                self.dcm_path,
                self.lib_path,
                self.footprint_path,
                self.model_path,
                remote_type,
            )

        self.lib_path_new = unzip(root_path, ".kicad_sym")

        directory = unzip(root_path, "KiCad")
        if directory:
            self.dcm_path = unzip(directory, ".dcm")
            self.lib_path = unzip(directory, ".lib")
            self.footprint_path = directory
            self.model_path = unzip(root_path, ".step")
            if not self.model_path:
                self.model_path = unzip(root_path, ".stp")

            assert self.dcm_path and (
                self.lib_path or self.lib_path_new
            ), "Not in samacsys format"
            remote_type = REMOTE_TYPES.Samacsys
            return (
                self.dcm_path,
                self.lib_path,
                self.footprint_path,
                self.model_path,
                remote_type,
            )

        directory = root_path / "KiCAD"
        if directory.exists():
            self.dcm_path = unzip(directory, ".dcm")
            self.lib_path = unzip(directory, ".lib")
            self.footprint_path = unzip(directory, ".pretty")
            self.model_path = unzip(root_path, ".step")
            if not self.model_path:
                self.model_path = unzip(root_path, ".stp")

            assert (
                self.lib_path or self.lib_path_new
            ) and self.footprint_path, "Not in ultralibrarian format"
            remote_type = REMOTE_TYPES.UltraLibrarian
            return (
                self.dcm_path,
                self.lib_path,
                self.footprint_path,
                self.model_path,
                remote_type,
            )

        footprint = unzip(root_path, ".kicad_mod")
        self.lib_path = unzip(root_path, ".lib")
        if self.lib_path or self.lib_path_new:
            self.dcm_path = unzip(root_path, ".dcm")
            # self.footprint_path = root_path
            self.footprint_path = None
            if footprint:
                self.footprint_path = footprint.parent
            self.model_path = unzip(root_path, ".step")
            remote_type = REMOTE_TYPES.Snapeda

            assert (
                self.lib_path or self.lib_path_new
            ) and self.footprint_path, "Not in Snapeda format"
            return (
                self.dcm_path,
                self.lib_path,
                self.footprint_path,
                self.model_path,
                remote_type,
            )

        if footprint or self.lib_path_new:
            assert False, "Unknown library zipfile"
        else:
            assert False, "zipfile is probably not a library to import"

    def import_dcm(
        self,
        device: str,
        remote_type: REMOTE_TYPES,
        dcm_path: pathlib.Path,
        overwrite_if_exists=True,
        file_ending="",
    ) -> Tuple[Union[pathlib.Path, None], Union[pathlib.Path, None]]:
        """
        # .dcm file parsing
        # Note this reads in the existing dcm file for the particular remote repo, and tries to catch any duplicates
        # before overwriting or creating duplicates. It reads the existing dcm file line by line and simply copy+paste
        # each line if nothing will be overwritten or duplicated. If something could be overwritten or duplicated, the
        # terminal will prompt whether to overwrite or to keep the existing content and ignore the new file contents.
        :returns: dcm_file_read, dcm_file_write
        """

        self.dcm_skipped = False

        # Array of values defining all attributes of .dcm file
        dcm_attributes = (
            dcm_path.read_text(encoding="utf-8").splitlines()
            if dcm_path
            else ["#", "# " + device, "#", "$CMP " + device, "D", "F", "$ENDCMP"]
        )

        # Find which lines contain the component information (ignore the rest).
        index_start = None
        index_end = None
        index_header_start = None

        for attribute_idx, attribute in enumerate(dcm_attributes):
            if index_start is None:
                if attribute.startswith("#"):
                    if attribute.strip() == "#" and index_header_start is None:
                        index_header_start = attribute_idx  # header start
                elif attribute.startswith("$CMP "):
                    component_name = attribute[5:].strip()
                    if not self.cleanName(component_name) == self.cleanName(device):
                        raise Warning("Unexpected device in " + dcm_path.name)
                    dcm_attributes[attribute_idx] = attribute.replace(
                        component_name, device, 1
                    )
                    index_start = attribute_idx
                else:
                    index_header_start = None
            elif index_end is None:
                if attribute.startswith("$CMP "):
                    raise Warning("Multiple devices in " + dcm_path.name)
                elif attribute.startswith("$ENDCMP"):
                    index_end = attribute_idx + 1
                elif attribute.startswith("D"):
                    description = attribute[2:].strip()
                    if description:
                        dcm_attributes[attribute_idx] = "D " + description
                elif attribute.startswith("F"):
                    datasheet = attribute[2:].strip()
                    if datasheet:
                        dcm_attributes[attribute_idx] = "F " + datasheet
        if index_end is None:
            raise Warning(device + "not found in " + dcm_path.name)

        dcm_file_read = self.DEST_PATH / (remote_type.name + file_ending + ".dcm")

        dcm_file_write = self.DEST_PATH / (remote_type.name + ".dcm~")
        overwrite_existing = overwrote_existing = False

        check_file(dcm_file_read)
        check_file(dcm_file_write)

        with dcm_file_read.open("rt", encoding="utf-8") as readfile:
            with dcm_file_write.open("wt", encoding="utf-8") as writefile:
                if stat(dcm_file_read).st_size == 0:
                    # todo Handle appending to empty file
                    with dcm_file_read.open("wt", encoding="utf-8") as template_file:
                        template = ["EESchema-DOCLIB  Version 2.0", "#End Doc Library"]
                        template_file.writelines(line + "\n" for line in template)
                        template_file.close()

                for line in readfile:
                    if re.match("# *end ", line, re.IGNORECASE):
                        if not overwrote_existing:
                            writefile.write(
                                "\n".join(
                                    dcm_attributes[
                                        (
                                            index_start
                                            if index_header_start is None
                                            else index_header_start
                                        ) : index_end
                                    ]
                                )
                                + "\n"
                            )
                        writefile.write(line)
                        break
                    elif line.startswith("$CMP "):
                        component_name = line[5:].strip()
                        if component_name.startswith(device):
                            if overwrite_if_exists:
                                overwrite_existing = True
                                self.print("Overwrite existing dcm")
                            else:
                                overwrite_existing = False
                                # self.print("Import of dcm skipped")
                                self.dcm_skipped = True
                                return dcm_file_read, dcm_file_write
                            writefile.write(
                                "\n".join(dcm_attributes[index_start:index_end]) + "\n"
                            )
                            overwrote_existing = True
                        else:
                            writefile.write(line)
                    elif overwrite_existing:
                        if line.startswith("$ENDCMP"):
                            overwrite_existing = False
                    else:
                        writefile.write(line)

        return dcm_file_read, dcm_file_write

    def import_model(
        self, model_path: pathlib.Path, remote_type: REMOTE_TYPES, overwrite_if_exists
    ) -> Union[pathlib.Path, None]:
        # --------------------------------------------------------------------------------------------------------
        # 3D Model file extraction
        # --------------------------------------------------------------------------------------------------------

        if not model_path:
            return False

        write_file = self.DEST_PATH / (remote_type.name + ".3dshapes") / model_path.name

        self.model_skipped = False

        if write_file.exists():
            if overwrite_if_exists:
                overwrite_existing = True
            else:
                self.print("Import of 3d model skipped")
                self.model_skipped = True
                return model_path

        if model_path.is_file():
            check_file(write_file)
            write_file.write_bytes(model_path.read_bytes())
            modified_objects.append(write_file, Modification.EXTRACTED_FILE)
            if overwrite_if_exists:
                self.print("Overwrite existing 3d model")
            else:
                self.print("Import 3d model")

        return model_path

    def import_footprint(
        self,
        remote_type: REMOTE_TYPES,
        footprint_path: pathlib.Path,
        found_model: pathlib.Path,
        overwrite_if_exists=True,
    ) -> Tuple[Union[pathlib.Path, None], Union[pathlib.Path, None]]:
        """
        # Footprint file parsing
        :returns: footprint_file_read, footprint_file_write
        """

        footprint_file_read = None
        footprint_file_write = None
        self.footprint_skipped = False

        footprint_path_item_tmp = None
        for (
            footprint_path_item
        ) in footprint_path.iterdir():  # try to use only newer file
            if footprint_path_item.name.endswith(".kicad_mod"):
                footprint_path_item_tmp = footprint_path_item
                break
            elif footprint_path_item.name.endswith(".mod"):
                footprint_path_item_tmp = footprint_path_item

        footprint_path_item = footprint_path_item_tmp
        if not footprint_path_item:
            self.print("No footprint found")
            return footprint_file_read, footprint_file_write

        if footprint_path_item.name.endswith("mod"):
            footprint = footprint_path_item.read_text(encoding="utf-8")

            footprint_write_path = self.DEST_PATH / (remote_type.name + ".pretty")
            footprint_file_read = footprint_write_path / footprint_path_item.name
            footprint_file_write = footprint_write_path / (
                footprint_path_item.name + "~"
            )

            if found_model:
                footprint.splitlines()
                model_path = f"{self.KICAD_3RD_PARTY_LINK}/{remote_type.name}.3dshapes/{found_model.name}"
                model = [
                    f'  (model "{model_path}"',
                    "    (offset (xyz 0 0 0))",
                    "    (scale (xyz 1 1 1))",
                    "    (rotate (xyz 0 0 0))",
                    "  )",
                ]

                overwrite_existing = overwrote_existing = False

                if footprint_file_read.exists():
                    if overwrite_if_exists:
                        overwrite_existing = True
                        self.print("Overwrite existing footprint")
                    else:
                        self.print("Import of footprint skipped")
                        self.footprint_skipped = True
                        return footprint_file_read, footprint_file_write

                check_file(footprint_file_read)
                with footprint_file_read.open("wt", encoding="utf-8") as wr:
                    wr.write(footprint)
                    overwrote_existing = True

                check_file(footprint_file_write)

                with footprint_file_read.open("rt", encoding="utf-8") as readfile:
                    with footprint_file_write.open("wt", encoding="utf-8") as writefile:
                        if stat(footprint_file_read).st_size == 0:
                            # todo Handle appending to empty file?
                            pass

                        lines = readfile.readlines()

                        write_3d_into_file = False
                        for line_idx, line in enumerate(lines):
                            if not write_3d_into_file and line_idx == len(lines) - 1:
                                writefile.writelines(
                                    model_line + "\n" for model_line in model
                                )
                                writefile.write(line)
                                break
                            elif line.strip().startswith(r"(model"):
                                writefile.write(model[0] + "\n")
                                write_3d_into_file = True
                            else:
                                writefile.write(line)
                    self.print("Import footprint")
            else:
                check_file(footprint_file_write)
                with footprint_file_write.open("wt", encoding="utf-8") as wr:
                    wr.write(footprint)
                    self.print("Import footprint")

        return footprint_file_read, footprint_file_write

    def import_lib(
        self,
        remote_type: REMOTE_TYPES,
        lib_path: pathlib.Path,
        overwrite_if_exists=True,
    ) -> Tuple[str, Union[pathlib.Path, None], Union[pathlib.Path, None]]:
        """
        .lib file parsing
        Note this reads in the existing lib file for the particular remote repo, and tries to catch any duplicates
        before overwriting or creating duplicates. It reads the existing dcm file line by line and simply copy+paste
        each line if nothing will be overwritten or duplicated. If something could be overwritten or duplicated, the
        terminal will prompt whether to overwrite or to keep the existing content and ignore the new file contents.
        :returns: device, lib_file_read, lib_file_write
        """

        self.lib_skipped = False

        device = None
        lib_lines = lib_path.read_text(encoding="utf-8").splitlines()

        # Find which lines contain the component information in file to be imported
        index_start = None
        index_end = None
        index_header_start = None
        for line_idx, line in enumerate(lib_lines):
            if index_start is None:
                if line.startswith("#"):
                    if line.strip() == "#" and index_header_start is None:
                        index_header_start = line_idx  # header start
                elif line.startswith("DEF "):
                    device = line.split()[1]
                    index_start = line_idx
                else:
                    index_header_start = None
            elif index_end is None:
                if line.startswith("F2"):
                    footprint = line.split()[1]
                    footprint = footprint.strip('"')
                    self.footprint_name = self.cleanName(footprint)
                    lib_lines[line_idx] = line.replace(
                        footprint, remote_type.name + ":" + self.footprint_name, 1
                    )
                elif line.startswith("ENDDEF"):
                    index_end = line_idx + 1
                elif line.startswith("F1 "):
                    lib_lines[line_idx] = line.replace(device, device, 1)
            elif line.startswith("DEF "):
                raise Warning("Multiple devices in " + lib_path.name)
        if index_end is None:
            raise Warning(device + " not found in " + lib_path.name)

        lib_file_read = self.DEST_PATH / (remote_type.name + ".lib")
        lib_file_write = self.DEST_PATH / (remote_type.name + ".lib~")
        overwrite_existing = overwrote_existing = overwritten = False

        check_file(lib_file_read)
        check_file(lib_file_write)

        with lib_file_read.open("rt", encoding="utf-8") as readfile:
            with lib_file_write.open("wt", encoding="utf-8") as writefile:
                if stat(lib_file_read).st_size == 0:
                    # todo Handle appending to empty file
                    with lib_file_read.open("wt", encoding="utf-8") as template_file:
                        template = [
                            "EESchema-LIBRARY Version 2.4",
                            "#encoding utf-8",
                            "# End Library",
                        ]
                        template_file.writelines(line + "\n" for line in template)
                        template_file.close()

                # For each line in the existing lib file (not the file being read from the zip. The lib file you will
                # add it to.)
                for line in readfile:
                    # Is this trying to match ENDDRAW, ENDDEF, End Library or any of the above?
                    if re.match("# *end ", line, re.IGNORECASE):
                        # If you already overwrote the new info don't add it to the end
                        if not overwrote_existing:
                            writefile.write(
                                "\n".join(
                                    lib_lines[
                                        (
                                            index_start
                                            if index_header_start is None
                                            else index_header_start
                                        ) : index_end
                                    ]
                                )
                                + "\n"
                            )
                        writefile.write(line)
                        break
                    # Catch start of new component definition
                    elif line.startswith("DEF "):
                        component_name = line.split()[1]
                        # Catch if the currently read component matches the name of the component you are planning to
                        # write
                        if component_name.startswith(device):
                            # Ask if you want to overwrite existing component

                            if overwrite_if_exists:
                                overwrite_existing = True
                                overwritten = True
                                self.print("Overwrite existing lib")
                            else:
                                self.print("Import of lib skipped")
                                self.lib_skipped = True
                                return device, lib_file_read, lib_file_write
                            writefile.write(
                                "\n".join(lib_lines[index_start:index_end]) + "\n"
                            )
                            overwrote_existing = True
                        else:
                            writefile.write(line)
                    elif overwrite_existing:
                        if line.startswith("ENDDEF"):
                            overwrite_existing = False
                    else:
                        writefile.write(line)
        if not overwritten:
            self.print("Import lib")
        return device, lib_file_read, lib_file_write

    def import_lib_new(
        self,
        remote_type: REMOTE_TYPES,
        lib_path: pathlib.Path,
        overwrite_if_exists=True,
    ) -> Tuple[str, Union[pathlib.Path, None], Union[pathlib.Path, None]]:
        symbol_name = None

        def extract_symbol_names(input_text):
            pattern = r'"(.*?)"'  # Searches for text in quotes
            # Searches for "(symbol" followed by text in quotes
            pattern = r'\(symbol\s+"(.*?)"'
            matches = re.findall(pattern, input_text)
            return matches

        symbol_name = None

        def replace_footprint_name(string, original_name, remote_type_name, new_name):
            escaped_original_name = re.escape(original_name)
            pattern = rf'\(property\s+"Footprint"\s+"{escaped_original_name}"'
            replacement = f'(property "Footprint" "{remote_type_name}:{new_name}"'
            modified_string = re.sub(pattern, replacement, string, flags=re.MULTILINE)
            return modified_string

        def extract_symbol_section_new(sexpression: str, symbol_name):
            pattern = re.compile(
                r'\(symbol "{}"(.*?)\)\n\)'.format(re.escape(symbol_name)), re.DOTALL
            )
            match = pattern.search(sexpression)
            if match:
                return f'(symbol "{symbol_name}"{match.group(1)})'
            return None

        RAW_text = lib_path.read_text(encoding="utf-8")  # new
        sexp_list = parse_sexp(RAW_text)  # new

        symbol_name = search_recursive(sexp_list, "symbol")  # new
        symbol_properties = extract_properties(sexp_list)  # new

        symbol_section = extract_symbol_section_new(RAW_text, symbol_name)

        Footprint_name_raw = symbol_properties["Footprint"]  # new
        self.footprint_name = self.cleanName(Footprint_name_raw)

        symbol_section = replace_footprint_name(
            symbol_section, Footprint_name_raw, remote_type.name, self.footprint_name
        )

        lib_file_read = self.DEST_PATH / (remote_type.name + ".kicad_sym")
        lib_file_read_old = self.DEST_PATH / (remote_type.name + "_kicad_sym.kicad_sym")
        lib_file_write = self.DEST_PATH / (remote_type.name + ".kicad_sym~")
        if isfile(lib_file_read_old) and not isfile(lib_file_read):
            lib_file_read = lib_file_read_old

        if not lib_file_read.exists():  # library does not yet exist
            with lib_file_write.open("wt", encoding="utf-8") as writefile:
                text = RAW_text.strip().split("\n")
                writefile.write(text[0] + "\n")
                writefile.write(symbol_section + "\n")
                writefile.write(text[-1])

            check_file(lib_file_read)
            self.print("Import kicad_sym")
            return symbol_name, lib_file_read, lib_file_write

        check_file(lib_file_read)

        lib_file_txt = lib_file_read.read_text(encoding="utf-8")
        existing_libs = extract_symbol_names(lib_file_txt)

        if symbol_name in existing_libs:
            if overwrite_if_exists:
                self.print("Overwrite existing kicad_sym is not implemented")  # TODO
            else:
                self.print("Import of kicad_sym skipped")

            return symbol_name, lib_file_read, lib_file_write

        closing_bracket = lib_file_txt.rfind(")")

        with lib_file_write.open("wt", encoding="utf-8") as writefile:
            writefile.write(lib_file_txt[:closing_bracket])
            writefile.write(symbol_section + "\n")
            writefile.write(lib_file_txt[closing_bracket:])

        self.print("Import kicad_sym")

        return symbol_name, lib_file_read, lib_file_write

    def import_all(
        self, zip_file: pathlib.Path, overwrite_if_exists=True, import_old_format=True
    ):
        """zip is a pathlib.Path to import the symbol from"""
        if not zipfile.is_zipfile(zip_file):
            return None

        self.print(f"Import: {zip_file}")

        with zipfile.ZipFile(zip_file) as zf:
            (
                dcm_path,
                lib_path,
                footprint_path,
                model_path,
                remote_type,
            ) = self.get_remote_info(zf)

            self.print("Identify " + remote_type.name)

            CompatibilityMode = False
            if lib_path and not self.lib_path_new:
                CompatibilityMode = True

                temp_path = self.DEST_PATH / "temp.lib"
                temp_path_new = self.DEST_PATH / "temp.kicad_sym"

                if temp_path.exists():
                    remove(temp_path)
                if temp_path_new.exists():
                    remove(temp_path_new)

                with temp_path.open("wt", encoding="utf-8") as writefile:
                    text = lib_path.read_text(encoding="utf-8")
                    writefile.write(text)

                if temp_path.exists() and cli.exists():
                    cli.upgrade_sym_lib(temp_path, temp_path_new)
                    self.print("compatibility mode: convert from .lib to .kicad_sym")

                if temp_path_new.exists() and temp_path_new.is_file():
                    self.lib_path_new = temp_path_new
                else:
                    self.print("error during conversion")

            if self.lib_path_new:
                device, lib_file_new_read, lib_file_new_write = self.import_lib_new(
                    remote_type, self.lib_path_new, overwrite_if_exists
                )

                file_ending = ""
                if "_kicad_sym" in lib_file_new_read.name:
                    file_ending = "_kicad_sym"

                dcm_file_new_read, dcm_file_new_write = self.import_dcm(
                    device,
                    remote_type,
                    dcm_path,
                    overwrite_if_exists,
                    file_ending=file_ending,
                )
                if not import_old_format:
                    lib_path = None

            if CompatibilityMode:
                if temp_path.exists():
                    remove(temp_path)
                if temp_path_new.exists():
                    remove(temp_path_new)

            if lib_path:
                device, lib_file_read, lib_file_write = self.import_lib(
                    remote_type, lib_path, overwrite_if_exists
                )

                dcm_file_read, dcm_file_write = self.import_dcm(
                    device, remote_type, dcm_path, overwrite_if_exists
                )

            found_model = self.import_model(
                model_path, remote_type, overwrite_if_exists
            )

            footprint_file_read, footprint_file_write = self.import_footprint(
                remote_type, footprint_path, found_model, overwrite_if_exists
            )

            # replace read files with write files only after all operations succeeded
            if self.lib_path_new:
                if lib_file_new_write.exists():
                    lib_file_new_write.replace(lib_file_new_read)

                if dcm_file_new_write.exists() and not self.dcm_skipped:
                    dcm_file_new_write.replace(dcm_file_new_read)
                elif dcm_file_new_write.exists():
                    remove(dcm_file_new_write)

            if lib_path:
                if dcm_file_write.exists() and not self.dcm_skipped:
                    dcm_file_write.replace(dcm_file_read)
                elif dcm_file_write.exists():
                    remove(dcm_file_write)

                if lib_file_write.exists() and not self.lib_skipped:
                    lib_file_write.replace(lib_file_read)
                elif lib_file_write.exists():
                    remove(lib_file_write)

            if (
                footprint_file_read
                and (self.footprint_name != footprint_file_read.stem)
                and not self.footprint_skipped
            ):
                self.print(
                    'Warning renaming footprint file "'
                    + footprint_file_read.stem
                    + '" to "'
                    + self.footprint_name
                    + '"'
                )

                if footprint_file_read.exists():
                    remove(footprint_file_read)
                footprint_file_read = footprint_file_read.parent / (
                    self.footprint_name + footprint_file_read.suffix
                )

            if (
                footprint_file_write
                and footprint_file_write.exists()
                and not self.footprint_skipped
            ):
                footprint_file_write.replace(footprint_file_read)
            elif footprint_file_write and footprint_file_write.exists():
                remove(footprint_file_write)

        return ("OK",)


def main(
    lib_file, lib_folder, overwrite=False, KICAD_3RD_PARTY_LINK="${KICAD_3RD_PARTY}"
):
    lib_folder = pathlib.Path(lib_folder)
    lib_file = pathlib.Path(lib_file)

    print("overwrite", overwrite)

    if not lib_folder.is_dir():
        print(f"Error destination folder {lib_folder} does not exist!")
        return 0

    if not lib_file.is_file():
        print(f"Error file {lib_folder} to be imported was not found!")
        return 0

    impart = import_lib()
    impart.KICAD_3RD_PARTY_LINK = KICAD_3RD_PARTY_LINK
    impart.set_DEST_PATH(lib_folder)
    try:
        (res,) = impart.import_all(lib_file, overwrite_if_exists=overwrite)
        print(res)
    except AssertionError as e:
        print(e)
    except Exception as e:
        print(e)


if __name__ == "__main__":
    import argparse
    logging.basicConfig(level=logging.ERROR)

    # Example: python plugins/KiCadImport.py --download-folder Demo/libs --lib-folder import_test

    parser = argparse.ArgumentParser(
        description="Import KiCad libraries from a file or folder."
    )

    # Create mutually exclusive arguments for file or folder
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument(
        "--download-folder",
        help="Path to the folder with the zip files to be imported.",
    )
    group.add_argument("--download-file", help="Path to the zip file to import.")

    group.add_argument("--easyeda", help="Import easyeda part. example: C2040")

    parser.add_argument(
        "--lib-folder",
        required=True,
        help="Destination folder for the imported KiCad files.",
    )

    parser.add_argument(
        "--overwrite-if-exists",
        action="store_true",
        help="Overwrite existing files if they already exist",
    )

    parser.add_argument(
        "--path-variable",
        help="Example: if only project-specific '${KIPRJMOD}' standard is '${KICAD_3RD_PARTY}'",
    )

    args = parser.parse_args()

    lib_folder = pathlib.Path(args.lib_folder)

    if args.path_variable:
        path_variable = str(args.path_variable).strip()
    else:
        path_variable = "${KICAD_3RD_PARTY}"

    if args.download_file:
        main(
            lib_file=args.download_file,
            lib_folder=args.lib_folder,
            overwrite=args.overwrite_if_exists,
            KICAD_3RD_PARTY_LINK=path_variable,
        )
    elif args.download_folder:
        download_folder = pathlib.Path(args.download_folder)
        if not download_folder.is_dir():
            print(f"Error Source folder {download_folder} does not exist!")
        elif not lib_folder.is_dir():
            print(f"Error destination folder {lib_folder} does not exist!")
        else:
            for zip_file in download_folder.glob("*.zip"):
                if (
                    zip_file.is_file() and zip_file.stat().st_size >= 1024
                ):  # Check if it's a file and at least 1 KB
                    main(
                        lib_file=zip_file,
                        lib_folder=args.lib_folder,
                        overwrite=args.overwrite_if_exists,
                        KICAD_3RD_PARTY_LINK=path_variable,
                    )
    elif args.easyeda:
        if not lib_folder.is_dir():
            print(f"Error destination folder {lib_folder} does not exist!")
        else:
            component_id = str(args.easyeda).strip
            print("Try to import EeasyEDA /  LCSC Part# : ", component_id)
            from impart_easyeda import easyeda2kicad_wrapper

            easyeda_import = easyeda2kicad_wrapper()

            easyeda_import.full_import(
                component_id="C2040",
                base_folder=lib_folder,
                overwrite=False,
                lib_var=path_variable,
            )