# -*- coding: utf-8 -*-
# Copyright (c) 2025 Salvador E. Tropea
# Copyright (c) 2025 Instituto Nacional de Tecnología Industrial
# License: AGPL-3.0
# Project: KiBot (formerly KiPlot)
import re
from .error import KiPlotConfigurationError
from .misc import UNITS_2_KICAD, MISSING_TOOL
from .kiplot import run_command
from .gs import GS
from .out_base_3d import Base3DOptions, Base3D
from .macros import macros, document, output_class  # noqa: F401
from . import log

logger = log.get_logger()
BOOLEAN_OPS = ('subst_models', 'cut_vias_in_body', 'fill_all_vias', 'board_only', 'no_board_body', 'no_components',
               'include_tracks', 'include_pads', 'include_zones', 'include_inner_copper', 'include_silkscreen',
               'include_soldermask', 'fuse_shapes')


class Export_3DOptions(Base3DOptions):
    def __init__(self):
        with document:
            self.output = GS.def_global_output
            """ *Name for the generated 3D file (%i='3D' %x='step/glb/stl/xao/brep') """
            self.format = 'step'
            """ *[step,glb,stl,xao,brep] 3D format used.
                - STEP: ISO 10303-21 Clear Text Encoding of the Exchange Structure
                - GLB: Binary version of the glTF, Graphics Library Transmission Format or GL Transmission Format and formerly
                  known as WebGL Transmissions Format or WebGL TF.
                - STL: 3D printer format, from stereolithography CAD software created by 3D Systems.
                - XAO: XAO (SALOME/Gmsh) format, used for FEM and simulations.
                - BRep: Part of Open CASCADE Technology (OCCT) """
            #    - PLY: Polygon File Format or the Stanford Triangle Format.
            self.origin = 'grid'
            """ *[grid,drill,center,*] Determines the coordinates origin.
                Using `grid` the coordinates are the same as you have in the design sheet.
                The `drill` option uses the auxiliary reference defined by the user.
                Using `center` you'll get the center of the board as origin.
                You can define any other origin using the format 'X,Y', i.e. '3.2,-10'. Don't put units here.
                The units used here are the ones specified by the `units` option """
            self.units = 'millimeters'
            """ [millimeters,inches,mils] Units used for the custom origin and `min_distance`. Affected by global options """
            self.min_distance = -1
            """ The minimum distance between points to treat them as separate ones (-1 is KiCad default: 0.01 mm).
                The units for this option are controlled by the `units` option """
            self.subst_models = True
            """ Substitute STEP or IGS models with the same name in place of VRML models """
            self.cut_vias_in_body = False
            """ Cut via holes in board body even if conductor layers are not exported """
            self.fill_all_vias = False
            """ Don't cut via holes in conductor layers """
            self.board_only = False
            """ Only generate a board with no components """
            self.no_board_body = False
            """ Exclude board body """
            self.no_components = False
            """ Exclude 3D models for components """
            self.include_tracks = False
            """ Export tracks and vias """
            self.include_pads = False
            """ Export pads """
            self.include_zones = False
            """ Export zones """
            self.include_inner_copper = False
            """ Export elements on inner copper layers """
            self.include_silkscreen = False
            """ Export silkscreen graphics as a set of flat faces """
            self.include_soldermask = False
            """ Export soldermask layers as a set of flat faces """
            self.fuse_shapes = False
            """ Fuse overlapping geometry together """
            self.net_filter = ''
            """ Only include copper items belonging to nets matching this wildcard """
            self.no_optimize_step = False
            """ Do not optimize STEP file (enables writing parametric curves) """
        # Temporal dir used to store the downloaded files
        self._tmp_dir = None
        super().__init__()

    def config(self, parent):
        super().config(parent)
        # Validate and parse the origin
        val = self.origin
        if (val not in ['grid', 'drill']):
            if val == 'center':
                bb = GS.board.ComputeBoundingBox(True)
                center = bb.GetCenter()
                self._user_x = GS.to_mm(center.x)
                self._user_y = GS.to_mm(center.y)
                self._units = 'mm'
            else:
                user_origin = re.match(r'([-\d\.]+)\s*,\s*([-\d\.]+)\s*$', val)
                if user_origin is None:
                    raise KiPlotConfigurationError('Origin must be `grid` or `drill` or `X,Y` (no units here)')
                self._user_x = float(user_origin.group(1))
                self._user_y = float(user_origin.group(2))
        # Adjust the units
        if self.origin != 'center':
            self._units = UNITS_2_KICAD[self.units]
        if self._units == 'mils':
            self._units = 'in'
            self._scale = 0.001
        else:
            self._scale = 1.0
        # The format indicates the extension
        self._expand_ext = self.format

    def run(self, output):
        if not GS.ki9:
            GS.exit_with_error("`export_3d` needs KiCad 9+", MISSING_TOOL)
        super().run(output)
        # Make units explicit
        # Base command with overwrite
        cmd = ['kicad-cli', 'pcb', 'export', self.format, '-o', output, '-f']
        # Origin
        if self.origin == 'drill':
            cmd.append('--drill-origin')
        elif self.origin == 'grid':
            cmd.append('--grid-origin')
        else:
            cmd.extend(['--user-origin', f"{self._user_x*self._scale}x{self._user_y*self._scale}{self._units}"])
        if self.min_distance >= 0:
            cmd.extend(['--min-distance', f"{self.min_distance*self._scale}{self._units}"])
        if self.net_filter:
            cmd.extend(['--net-filter', self.net_filter])
        for ops in BOOLEAN_OPS:
            if getattr(self, ops):
                cmd.append('--'+ops.replace('_', '-'))
        if self.format == 'step' and self.no_optimize_step:
            cmd.append('--no-optimize-step')
        if self.no_virtual:
            # Is this correct?
            cmd.append('--no-unspecified')
        # The board
        board_name = self.filter_components()
        cmd.append(board_name)
        run_command(cmd)
        if self._files_to_remove:
            self.remove_temporals()


@output_class
class Export_3D(Base3D):
    """ Various 3D models exports using KiCad (BREP/GLB/STL/STEP/XAO)
        Exports the PCB as a 3D model using KiCad 9 or newer.
        Supported formats include:
        - STEP: ISO 10303-21 Clear Text Encoding of the Exchange Structure
        - GLB: Binary version of the glTF, Graphics Library Transmission Format or GL Transmission Format and formerly
          known as WebGL Transmissions Format or WebGL TF.
        - STL: 3D printer format, from stereolithography CAD software created by 3D Systems.
        - XAO: XAO (SALOME/Gmsh) format, used for FEM and simulations.
        - BRep: Part of Open CASCADE Technology (OCCT)
        STEP is the most common 3D format for exchange purposes """
    # OCCT 7.7 needed, how to detect?
    #   - PLY: Polygon File Format or the Stanford Triangle Format.
    def __init__(self):
        super().__init__()
        with document:
            self.options = Export_3DOptions
            """ *[dict={}] Options for the `export_3d` output """
        self._category = 'PCB/3D'

    @staticmethod
    def get_conf_examples(name, layers):
        if not GS.ki9:
            return None
        outs = []
        for o in ('step', 'glb', 'stl', 'xao', 'brep'):
            gb = {}
            gb['name'] = 'basic_'+name+'_'+o
            gb['comment'] = f'3D model in {o.upper()} format'
            gb['type'] = name
            gb['dir'] = '3D'
            gb['options'] = {'format': o}
            outs.append(gb)
        return outs
