"""Config object"""

import argparse
import os
import re

from wx import FileConfig

from .. import dialog


class Config:
    FILE_NAME_FORMAT_HINT = (
        'Output file name format supports substitutions:\n'
        '\n'
        '    %f : original pcb file name without extension.\n'
        '    %p : pcb/project title from pcb metadata.\n'
        '    %c : company from pcb metadata.\n'
        '    %r : revision from pcb metadata.\n'
        '    %d : pcb date from metadata if available, '
        'file modification date otherwise.\n'
        '    %D : bom generation date.\n'
        '    %T : bom generation time.\n'
        '\n'
        'Extension .html will be added automatically.'
    )  # type: str

    # Helper constants
    config_file = os.path.join(os.path.dirname(__file__), '..', 'config.ini')
    bom_view_choices = ['bom-only', 'left-right', 'top-bottom']
    layer_view_choices = ['F', 'FB', 'B']
    default_sort_order = [
        'C', 'R', 'L', 'D', 'U', 'Y', 'X', 'F', 'SW', 'A',
        '~',
        'HS', 'CNN', 'J', 'P', 'NT', 'MH',
    ]
    default_checkboxes = ['Sourced', 'Placed']
    html_config_fields = [
        'dark_mode', 'show_pads', 'show_fabrication', 'show_silkscreen',
        'highlight_pin1', 'redraw_on_drag', 'board_rotation', 'checkboxes',
        'bom_view', 'layer_view', 'extra_fields'
    ]

    # Defaults

    # HTML section
    dark_mode = False
    show_pads = True
    show_fabrication = False
    show_silkscreen = True
    highlight_pin1 = False
    redraw_on_drag = True
    board_rotation = 0
    checkboxes = ','.join(default_checkboxes)
    bom_view = bom_view_choices[1]
    layer_view = layer_view_choices[1]
    open_browser = True

    # General section
    bom_dest_dir = 'bom/'  # This is relative to pcb file directory
    bom_name_format = 'ibom'
    component_sort_order = default_sort_order
    component_blacklist = []
    blacklist_virtual = True
    blacklist_empty_val = False
    include_tracks = False
    include_nets = False

    # Extra fields section
    netlist_file = None
    netlist_initial_directory = ''  # This is relative to pcb file directory
    extra_fields = []
    normalize_field_case = False
    board_variant_field = ''
    board_variant_whitelist = []
    board_variant_blacklist = []
    dnp_field = ''

    @staticmethod
    def _split(s):
        """Splits string by ',' and drops empty strings from resulting array."""
        return [a.replace('\\,', ',') for a in re.split(r'(?<!\\),', s) if a]

    @staticmethod
    def _join(lst):
        return ','.join([s.replace(',', '\\,') for s in lst])

    def __init__(self, version):
        self.version = version

    def load_from_ini(self):
        """Init from config file if it exists."""
        if not os.path.isfile(self.config_file):
            return
        f = FileConfig(localFilename=self.config_file)

        f.SetPath('/html_defaults')
        self.dark_mode = f.ReadBool('dark_mode', self.dark_mode)
        self.show_pads = f.ReadBool('show_pads', self.show_pads)
        self.show_fabrication = f.ReadBool(
                'show_fabrication', self.show_fabrication)
        self.show_silkscreen = f.ReadBool(
                'show_silkscreen', self.show_silkscreen)
        self.highlight_pin1 = f.ReadBool('highlight_pin1', self.highlight_pin1)
        self.redraw_on_drag = f.ReadBool('redraw_on_drag', self.redraw_on_drag)
        self.board_rotation = f.ReadInt('board_rotation', self.board_rotation)
        self.checkboxes = f.Read('checkboxes', self.checkboxes)
        self.bom_view = f.Read('bom_view', self.bom_view)
        self.layer_view = f.Read('layer_view', self.layer_view)
        self.open_browser = f.ReadBool('open_browser', self.open_browser)

        f.SetPath('/general')
        self.bom_dest_dir = f.Read('bom_dest_dir', self.bom_dest_dir)
        self.bom_name_format = f.Read('bom_name_format', self.bom_name_format)
        self.component_sort_order = self._split(f.Read(
                'component_sort_order',
                ','.join(self.component_sort_order)))
        self.component_blacklist = self._split(f.Read(
                'component_blacklist',
                ','.join(self.component_blacklist)))
        self.blacklist_virtual = f.ReadBool(
                'blacklist_virtual', self.blacklist_virtual)
        self.blacklist_empty_val = f.ReadBool(
                'blacklist_empty_val', self.blacklist_empty_val)
        self.include_tracks = f.ReadBool('include_tracks', self.include_tracks)
        self.include_nets = f.ReadBool('include_nets', self.include_nets)

        f.SetPath('/extra_fields')
        self.extra_fields = self._split(f.Read(
                'extra_fields',
                self._join(self.extra_fields)))
        self.normalize_field_case = f.ReadBool(
                'normalize_field_case', self.normalize_field_case)
        self.board_variant_field = f.Read(
                'board_variant_field', self.board_variant_field)
        self.board_variant_whitelist = self._split(f.Read(
                'board_variant_whitelist',
                ','.join(self.board_variant_whitelist)))
        self.board_variant_blacklist = self._split(f.Read(
                'board_variant_blacklist',
                ','.join(self.board_variant_blacklist)))
        self.dnp_field = f.Read('dnp_field', self.dnp_field)

    def save(self):
        f = FileConfig(localFilename=self.config_file)

        f.SetPath('/html_defaults')
        f.WriteBool('dark_mode', self.dark_mode)
        f.WriteBool('show_pads', self.show_pads)
        f.WriteBool('show_fabrication', self.show_fabrication)
        f.WriteBool('show_silkscreen', self.show_silkscreen)
        f.WriteBool('highlight_pin1', self.highlight_pin1)
        f.WriteBool('redraw_on_drag', self.redraw_on_drag)
        f.WriteInt('board_rotation', self.board_rotation)
        f.Write('checkboxes', self.checkboxes)
        f.Write('bom_view', self.bom_view)
        f.Write('layer_view', self.layer_view)
        f.WriteBool('open_browser', self.open_browser)

        f.SetPath('/general')
        bom_dest_dir = self.bom_dest_dir
        if bom_dest_dir.startswith(self.netlist_initial_directory):
            bom_dest_dir = os.path.relpath(
                    bom_dest_dir, self.netlist_initial_directory)
        f.Write('bom_dest_dir', bom_dest_dir)
        f.Write('bom_name_format', self.bom_name_format)
        f.Write('component_sort_order',
                ','.join(self.component_sort_order))
        f.Write('component_blacklist',
                ','.join(self.component_blacklist))
        f.WriteBool('blacklist_virtual', self.blacklist_virtual)
        f.WriteBool('blacklist_empty_val', self.blacklist_empty_val)
        f.WriteBool('include_tracks', self.include_tracks)
        f.WriteBool('include_nets', self.include_nets)

        f.SetPath('/extra_fields')
        f.Write('extra_fields', self._join(self.extra_fields))
        f.WriteBool('normalize_field_case', self.normalize_field_case)
        f.Write('board_variant_field', self.board_variant_field)
        f.Write('board_variant_whitelist',
                ','.join(self.board_variant_whitelist))
        f.Write('board_variant_blacklist',
                ','.join(self.board_variant_blacklist))
        f.Write('dnp_field', self.dnp_field)
        f.Flush()

    def set_from_dialog(self, dlg):
        # type: (dialog.settings_dialog.SettingsDialogPanel) -> None
        # Html
        self.dark_mode = dlg.html.darkModeCheckbox.IsChecked()
        self.show_pads = dlg.html.showPadsCheckbox.IsChecked()
        self.show_fabrication = dlg.html.showFabricationCheckbox.IsChecked()
        self.show_silkscreen = dlg.html.showSilkscreenCheckbox.IsChecked()
        self.highlight_pin1 = dlg.html.highlightPin1Checkbox.IsChecked()
        self.redraw_on_drag = dlg.html.continuousRedrawCheckbox.IsChecked()
        self.board_rotation = dlg.html.boardRotationSlider.Value
        self.checkboxes = dlg.html.bomCheckboxesCtrl.Value
        self.bom_view = self.bom_view_choices[dlg.html.bomDefaultView.Selection]
        self.layer_view = self.layer_view_choices[
            dlg.html.layerDefaultView.Selection]
        self.open_browser = dlg.html.openBrowserCheckbox.IsChecked()

        # General
        self.bom_dest_dir = dlg.general.bomDirPicker.Path
        self.bom_name_format = dlg.general.fileNameFormatTextControl.Value
        self.component_sort_order = dlg.general.componentSortOrderBox.GetItems()
        self.component_blacklist = dlg.general.blacklistBox.GetItems()
        self.blacklist_virtual = \
            dlg.general.blacklistVirtualCheckbox.IsChecked()
        self.blacklist_empty_val = \
            dlg.general.blacklistEmptyValCheckbox.IsChecked()
        self.include_tracks = dlg.general.includeTracksCheckbox.IsChecked()
        self.include_nets = dlg.general.includeNetsCheckbox.IsChecked()

        # Extra fields
        self.netlist_file = dlg.extra.netlistFilePicker.Path
        self.extra_fields = list(dlg.extra.extraFieldsList.GetCheckedStrings())
        self.normalize_field_case = dlg.extra.normalizeCaseCheckbox.Value
        self.board_variant_field = dlg.extra.boardVariantFieldBox.Value
        if self.board_variant_field == dlg.extra.NONE_STRING:
            self.board_variant_field = ''
        self.board_variant_whitelist = list(
                dlg.extra.boardVariantWhitelist.GetCheckedStrings())
        self.board_variant_blacklist = list(
                dlg.extra.boardVariantBlacklist.GetCheckedStrings())
        self.dnp_field = dlg.extra.dnpFieldBox.Value
        if self.dnp_field == dlg.extra.NONE_STRING:
            self.dnp_field = ''

    def transfer_to_dialog(self, dlg):
        # type: (dialog.settings_dialog.SettingsDialogPanel) -> None
        # Html
        dlg.html.darkModeCheckbox.Value = self.dark_mode
        dlg.html.showPadsCheckbox.Value = self.show_pads
        dlg.html.showFabricationCheckbox.Value = self.show_fabrication
        dlg.html.showSilkscreenCheckbox.Value = self.show_silkscreen
        dlg.html.highlightPin1Checkbox.Value = self.highlight_pin1
        dlg.html.continuousRedrawCheckbox.value = self.redraw_on_drag
        dlg.html.boardRotationSlider.Value = self.board_rotation
        dlg.html.bomCheckboxesCtrl.Value = self.checkboxes
        dlg.html.bomDefaultView.Selection = self.bom_view_choices.index(
                self.bom_view)
        dlg.html.layerDefaultView.Selection = self.layer_view_choices.index(
                self.layer_view)
        dlg.html.openBrowserCheckbox.Value = self.open_browser

        # General
        import os.path
        if os.path.isabs(self.bom_dest_dir):
            dlg.general.bomDirPicker.Path = self.bom_dest_dir
        else:
            dlg.general.bomDirPicker.Path = os.path.join(
                    self.netlist_initial_directory, self.bom_dest_dir)
        dlg.general.fileNameFormatTextControl.Value = self.bom_name_format
        dlg.general.componentSortOrderBox.SetItems(self.component_sort_order)
        dlg.general.blacklistBox.SetItems(self.component_blacklist)
        dlg.general.blacklistVirtualCheckbox.Value = self.blacklist_virtual
        dlg.general.blacklistEmptyValCheckbox.Value = self.blacklist_empty_val
        dlg.general.includeTracksCheckbox.Value = self.include_tracks
        dlg.general.includeNetsCheckbox.Value = self.include_nets

        # Extra fields
        dlg.extra.netlistFilePicker.SetInitialDirectory(
                self.netlist_initial_directory)

        def safe_set_checked_strings(clb, strings):
            safe_strings = list(clb.GetStrings())
            if safe_strings:
                present_strings = [s for s in strings if s in safe_strings]
                not_present_strings = [s for s in safe_strings if s not in strings]
                clb.Clear()
                clb.InsertItems(present_strings + not_present_strings, 0)
                clb.SetCheckedStrings(present_strings)

        safe_set_checked_strings(dlg.extra.extraFieldsList, self.extra_fields)
        dlg.extra.normalizeCaseCheckbox.Value = self.normalize_field_case
        dlg.extra.boardVariantFieldBox.Value = self.board_variant_field
        dlg.extra.OnBoardVariantFieldChange(None)
        safe_set_checked_strings(dlg.extra.boardVariantWhitelist,
                                 self.board_variant_whitelist)
        safe_set_checked_strings(dlg.extra.boardVariantBlacklist,
                                 self.board_variant_blacklist)
        dlg.extra.dnpFieldBox.Value = self.dnp_field

        dlg.finish_init()

    # noinspection PyTypeChecker
    def add_options(self, parser, file_name_format_hint):
        # type: (argparse.ArgumentParser, str) -> None
        parser.add_argument('--show-dialog', action='store_true',
                            help='Shows config dialog. All other flags '
                                 'will be ignored.')
        # Html
        parser.add_argument('--dark-mode', help='Default to dark mode.',
                            action='store_true')
        parser.add_argument('--hide-pads',
                            help='Hide footprint pads by default.',
                            action='store_true')
        parser.add_argument('--show-fabrication',
                            help='Show fabrication layer by default.',
                            action='store_true')
        parser.add_argument('--hide-silkscreen',
                            help='Hide silkscreen by default.',
                            action='store_true')
        parser.add_argument('--highlight-pin1',
                            help='Highlight pin1 by default.',
                            action='store_true')
        parser.add_argument('--no-redraw-on-drag',
                            help='Do not redraw pcb on drag by default.',
                            action='store_true')
        parser.add_argument('--board-rotation', type=int,
                            default=self.board_rotation * 5,
                            help='Board rotation in degrees (-180 to 180). '
                                 'Will be rounded to multiple of 5.')
        parser.add_argument('--checkboxes',
                            default=self.checkboxes,
                            help='Comma separated list of checkbox columns.')
        parser.add_argument('--bom-view', default=self.bom_view,
                            choices=self.bom_view_choices,
                            help='Default BOM view.')
        parser.add_argument('--layer-view', default=self.layer_view,
                            choices=self.layer_view_choices,
                            help='Default layer view.')
        parser.add_argument('--no-browser', help='Do not launch browser.',
                            action='store_true')

        # General
        parser.add_argument('--dest-dir', default=self.bom_dest_dir,
                            help='Destination directory for bom file '
                                 'relative to pcb file directory.')
        parser.add_argument('--name-format', default=self.bom_name_format,
                            help=file_name_format_hint.replace('%', '%%'))
        parser.add_argument('--include-tracks', action='store_true',
                            help='Include track/zone information in output. '
                                 'F.Cu and B.Cu layers only.')
        parser.add_argument('--include-nets', action='store_true',
                            help='Include netlist information in output.')
        parser.add_argument('--sort-order',
                            help='Default sort order for components. '
                                 'Must contain "~" once.',
                            default=','.join(self.component_sort_order))
        parser.add_argument('--blacklist',
                            default=','.join(self.component_blacklist),
                            help='List of comma separated blacklisted '
                                 'components or prefixes with *. E.g. "X1,MH*"')
        parser.add_argument('--no-blacklist-virtual', action='store_true',
                            help='Do not blacklist virtual components.')
        parser.add_argument('--blacklist-empty-val', action='store_true',
                            help='Blacklist components with empty value.')

        # Extra fields section
        parser.add_argument('--netlist-file',
                            help='Path to netlist or xml file.')
        parser.add_argument('--extra-fields',
                            default=self._join(self.extra_fields),
                            help='Comma separated list of extra fields to '
                                 'pull from netlist or xml file.')
        parser.add_argument('--normalize-field-case',
                            help='Normalize extra field name case. E.g. "MPN" '
                                 'and "mpn" will be considered the same field.',
                            action='store_true')
        parser.add_argument('--variant-field',
                            help='Name of the extra field that stores board '
                                 'variant for component.')
        parser.add_argument('--variants-whitelist', default='',
                            help='List of board variants to '
                                 'include in the BOM.')
        parser.add_argument('--variants-blacklist', default='',
                            help='List of board variants to '
                                 'exclude from the BOM.')
        parser.add_argument('--dnp-field', default=self.dnp_field,
                            help='Name of the extra field that indicates '
                                 'do not populate status. Components with this '
                                 'field not empty will be blacklisted.')

    def set_from_args(self, args):
        # type: (argparse.Namespace) -> None
        import math

        # Html
        self.dark_mode = args.dark_mode
        self.show_pads = not args.hide_pads
        self.show_fabrication = args.show_fabrication
        self.show_silkscreen = not args.hide_silkscreen
        self.highlight_pin1 = args.highlight_pin1
        self.redraw_on_drag = not args.no_redraw_on_drag
        self.board_rotation = math.fmod(args.board_rotation // 5, 37)
        self.checkboxes = args.checkboxes
        self.bom_view = args.bom_view
        self.layer_view = args.layer_view
        self.open_browser = not args.no_browser

        # General
        self.bom_dest_dir = args.dest_dir
        self.bom_name_format = args.name_format
        self.component_sort_order = self._split(args.sort_order)
        self.component_blacklist = self._split(args.blacklist)
        self.blacklist_virtual = not args.no_blacklist_virtual
        self.blacklist_empty_val = args.blacklist_empty_val
        self.include_tracks = args.include_tracks
        self.include_nets = args.include_nets

        # Extra
        self.netlist_file = args.netlist_file
        self.extra_fields = self._split(args.extra_fields)
        self.normalize_field_case = args.normalize_field_case
        self.board_variant_field = args.variant_field
        self.board_variant_whitelist = self._split(args.variants_whitelist)
        self.board_variant_blacklist = self._split(args.variants_blacklist)
        self.dnp_field = args.dnp_field

    def get_html_config(self):
        import json
        d = {f: getattr(self, f) for f in self.html_config_fields}
        return json.dumps(d)