# yes we are using json because 
# i dont want to install a yaml 
# parser on the users computer
import json
import os
import subprocess
import csv
from enum import Enum
from pathlib import Path
from os import listdir
from os.path import isfile, join

KICAD_CLI_PATH = "kicad-cli"
TEMP_FILE_PATH = "docs/"

PCB_IMAGE_OUTPUT_PATH = "res/"

PCB_PDF_OUTPUT_PATH = "docs/"
PCB_PDF_FILE_SUFFIX = "_pcb"

SCHEMATIC_OUTPUT_PATH = "docs/"
SCHEMATIC_FILE_SUFFIX = "_schematic"

BOM_OUTPUT_PATH = "docs/"
BOM_REPORT_NAME = "_bom"

TEMP_DRC_REPORT_NAME = "_drc"
TEMP_ERC_REPORT_NAME = "_erc"

# quiet
KICAD_CLI_STDOUT=subprocess.DEVNULL
# verbose
# KICAD_CLI_STDOUT=subprocess.STDOUT

class OutputReportType(Enum):
    JSON = 1
    REPORT = 2

    def get_file_extension(self) -> str:
        match self:
            case OutputReportType.JSON: return "json" 
            case OutputReportType.REPORT: return "rpt"
            # dont trust it
            case _: return "txt"

# this is a thin vale on the kicad cli tool
class KicadProject:
    
    def __init__(self, path : Path) -> None:
        self.project_path = path.parent
        self.project_name = path.name.removesuffix(".kicad_pro")
        self.created_files : list[Path] = []
        print(f"{self.project_path=}")
        print(f"{self.project_name=}")

    def erc_check(
        self, 
        report_format : OutputReportType = OutputReportType.JSON, 
        return_report : bool = False,
        additional_args : str = ""
    ) -> None | dict | str:
        format_type = report_format.name.lower()
        sch_file_path = self.project_path / f"{self.project_name}.kicad_sch"
        erc_report_path = Path(TEMP_FILE_PATH) / f"{self.project_name}{TEMP_ERC_REPORT_NAME}.{report_format.get_file_extension()}"
        
        retcode = subprocess.call(
            f'{KICAD_CLI_PATH} sch erc {sch_file_path} --output {erc_report_path} --format {format_type} {additional_args}', 
            shell=True,
            stdout=KICAD_CLI_STDOUT
        )

        if (retcode != 0):
            print(f"erc check failed return code {retcode}")
            exit(1)
        
        self.created_files.append(erc_report_path)

        if (return_report):
            with open(erc_report_path, "r") as txt:
                if format_type == OutputReportType.JSON:
                    return json.loads(txt.read())
                if format_type == OutputReportType.RPT:
                    return txt.read()

    def drc_check(
        self, 
        report_format : OutputReportType = OutputReportType.JSON,
        return_report : bool = False,
        additional_args : str = ""
    ) -> None | dict | str:
        format_type = report_format.name.lower()
        pcb_file_path = self.project_path / f"{self.project_name}.kicad_pcb"
        drc_report_path = Path(TEMP_FILE_PATH) / f"{self.project_name}{TEMP_DRC_REPORT_NAME}.{report_format.get_file_extension()}"
        print(f"{format_type=}, {drc_report_path=}")
        retcode = subprocess.call(
            f'{KICAD_CLI_PATH} pcb drc {pcb_file_path} --output {drc_report_path} --format {format_type} {additional_args}', 
            shell=True,
            stdout=KICAD_CLI_STDOUT
        )
        print(f"{retcode=}")
        if (retcode != 0):
            print(f"drc check failed return code {retcode}")
            exit(1)
        
        self.created_files.append(drc_report_path)

        if (return_report):
            with open(drc_report_path, "r") as txt:
                if format_type == OutputReportType.JSON:
                    return json.loads(txt.read())
                if format_type == OutputReportType.RPT:
                    return txt.read()

    def process_bom(
        self, 
        return_csv : bool = False,
        additional_args : str = ""
    ) -> None | list[list[str]]:
        sch_file_path = self.project_path / f"{self.project_name}.kicad_sch"
        bom_output_path = Path(BOM_OUTPUT_PATH) / f"{self.project_name}{BOM_REPORT_NAME}.csv"
        retcode = subprocess.call(
            f'{KICAD_CLI_PATH} sch export bom {sch_file_path} --output {bom_output_path} {additional_args}', 
            shell=True,
            stdout=KICAD_CLI_STDOUT
        )

        if (retcode != 0):
            print(f"process_bom failed return code {retcode}")
            exit(1)
        
        self.created_files.append(bom_output_path)
        if (return_csv):
            with open(bom_output_path, "r") as csvfile:
                bom_csv = csv.reader(csvfile, delimiter=',', quotechar='"')
                return [row for row in bom_csv]

    def get_image(self, 
        image_type : str = "png",
        height : int = 900,
        width : int = 1600,
        side : str = "top",
        background : str = "default",
        preset : str = "follow_pcb_editor",
        zoom : int = 2,
        additional_args : str = ""
    ) -> None:
        """
            image_typ = "png" | "jpg"
            side = "top" | "bottom" | "left" | "right" | "front" | "back"
            background = "default" | "transparent" | "opaque"
        """
        pcb_file_path = self.project_path / f"{self.project_name}.kicad_pcb"
        render_output_path = Path(PCB_IMAGE_OUTPUT_PATH) / f"{self.project_name}_render.{image_type}"
        retcode = subprocess.call(
            f'{KICAD_CLI_PATH} pcb render {pcb_file_path} --output {render_output_path} --preset {preset} --zoom {zoom} {additional_args}', 
            shell=True,
            stdout=KICAD_CLI_STDOUT
        )

        if (retcode != 0):
            print(f"get_image failed return code {retcode}")
            exit(1)
        
        self.created_files.append(render_output_path)
    
    # i am not giving you the pdf to output if you want to do that yourself go ahead
    def create_schmatic_pdf(self, additional_args="") -> None:
        sch_file_path = self.project_path / f"{self.project_name}.kicad_sch"
        sch_report_path = Path(SCHEMATIC_OUTPUT_PATH) / f"{self.project_name}{SCHEMATIC_FILE_SUFFIX}.pdf"
        retcode = subprocess.call(
            f'{KICAD_CLI_PATH} sch export pdf {sch_file_path} --output {sch_report_path} {additional_args}', 
            shell=True,
            stdout=KICAD_CLI_STDOUT
        )

        if (retcode != 0):
            print(f"create_schmatic_pdf failed return code {retcode}")
            exit(1)
        
        self.created_files.append(sch_report_path)
    
        
    def create_pcb_pdf(self, layers : list[str] = ["F.Cu", "B.Cu"], additional_args : str = "") -> None:
        pcb_file_path = self.project_path / f"{self.project_name}.kicad_pcb"
        pcb_report_path = Path(PCB_PDF_OUTPUT_PATH) / f"{self.project_name}{PCB_PDF_FILE_SUFFIX}.pdf"
        retcode = subprocess.call(
            f'{KICAD_CLI_PATH} pcb export pdf {pcb_file_path} --output {pcb_report_path} --layers {",".join(layers)} {additional_args}', 
            shell=True,
            stdout=KICAD_CLI_STDOUT
        )

        if (retcode != 0):
            print(f"create_pcb_pdf failed return code {retcode}")
            exit(1)
        
        self.created_files.append(pcb_report_path)

def commit_files(files: list[Path], commit_message : str) -> None:
    for file in files:
        # add & commit, could use the return code however these should never fail
        print(f"adding and commiting {file}")
        ret_add = subprocess.call(f"git add {file}", shell=True)
        ret_commit = subprocess.call(f"git commit -m \"{commit_message}\"", shell=True)

def main() -> None:
    # find all kicad project files to operate on
    for path in Path(".").rglob('*.kicad_pro'):
        k = KicadProject(path)
        k.drc_check(report_format = OutputReportType.REPORT)
        k.erc_check(report_format = OutputReportType.REPORT)
        k.process_bom()
        k.create_schmatic_pdf()
        k.get_image()
        commit_files(k.created_files, "auto commited")

if __name__ == "__main__":
    try:
        main()
        exit(0)
    except Exception as e:
        exit(1)
