# coding: windows-1252
#
# Name: Transfer list settings
# Classes: Entry
# CondExpression: None
# Selectedobjectsscript: Y
# Condexpression: self->size = 1
# ExtendedRights: N
#
# Description: Copies list settings in Vertec or to / from a file.
#
# 2025-03-06, CBR: Created.
# 2026-08-21, CBR: Updated for concept.
#
# Version = 101


import vtcapp
import traceback
import json
import re


# region constants

LANGUAGES = vtcapp.evalocl("Language.allInstances.languagename->asSet") + ["NV"]
TITLE = vtcapp.translate("Transfer list settings")

MEMBERS_SOURCE = ["tabsPerClass", "controllerClassName"]
MEMBERS_GRIDDEF = ["rowHeight", "ghostrow", "fontColor", "color", "editing", "class"]
MEMBERS_GRIDCOL = ["writeableExpression", "alignment", "orderIdx", "color", "fontColor", "controlName", "rendererName", "totals", "controlDefinition", "expression", "readOnly", "sortOrder", "tabstop", "width", "isFixed", "remarks", "isDynamic", "enableSpellCheck", "spellCheckLanguageExpression", "visibleExpression", "title"]
MEMBERS_ML = ["title"]


# region action functions

def transfer_from_list(target):
    "Copy another lists settings to this list."

    # Select source
    source = select_folder(target, False)
    if not source:
        return

    # Source to dict
    source_settings = to_dict(source)
    if not source_settings:
        return

    # Dict to target
    from_dict(target, source_settings)


def transfer_to_list(source):
    "Copy this lists settings to another list."

    # Select target
    target = select_folder(source, True)
    if not target:
        return

    # Source to dict
    source_settings = to_dict(source)
    if not source_settings:
        return

    # Dict to target
    from_dict(target, source_settings)


def load_from_file(target):
    "Updates the current list settings from a file."

    # Select file
    try:
        filename, file = vtcapp.requestfilefromclient(TITLE, "", "JSON|*.json")
    except RuntimeError:
        return

    # Source to dict
    source_settings = json.loads(file.decode('utf-8'))
    if not source_settings:
        return

    # Dict to target
    from_dict(target, source_settings)


def save_to_file(source):
    "Saves the current list setttings to a file."

    # Source to dict
    settings_dict = to_dict(source)

    # Save file
    filename = settings_dict.get('filename', '')
    file = json.dumps(settings_dict).encode('utf-8')

    vtcapp.sendfile(file, filename, True, False)


# region internal functions

def _save_getattr(vtc_object, member, settings):
    "Savely gets an attribute."

    normalized_member = member.lower()

    if hasattr(vtc_object, normalized_member):

        try:
            if normalized_member in MEMBERS_ML:
                settings[normalized_member] = {}
                for language in LANGUAGES:
                    settings[normalized_member][language] = vtc_object.getmlvalue(normalized_member, language)
            else:
                settings[normalized_member] = getattr(vtc_object, normalized_member)
        except Exception:
            traceback.print_exc()

    return settings


def _save_setattr(vtc_object, member, settings):
    "Savely sets an attribute."

    normalized_member = member.lower()

    if normalized_member not in settings.keys():
        return

    try:
        if normalized_member in MEMBERS_ML:
            for language in LANGUAGES:
                vtc_object.setmlvalue(normalized_member, settings[normalized_member][language], language)
        else:
            setattr(vtc_object, normalized_member, settings[normalized_member])
    except Exception:
        traceback.print_exc()


def _get_filename(source):
    "Returns the filename for the given source."

    date_string = vtcapp.currentdate().strftime('%Y%m%d')
    source_string = source.evalocl("asstring")
    source_reference = source.evalocl("objid")

    filename = "{}_{}_{}.json".format(date_string, source_string, source_reference)

    # Replace invalid characters with underscore and replace consecutive underscores.
    filename = re.sub(r'[\\/:*?"<>|]', '_', filename)
    filename = re.sub(r'_+', '_', filename)

    return filename


def _source_to_dict(source):
    "Translate the source to a settings dict."

    source_settings = {
        'griddefs': [],
        'filename': _get_filename(source),
    }

    for member in MEMBERS_SOURCE:
        source_settings = _save_getattr(source, member, source_settings)

    return source_settings


def _dict_to_target(target, target_setting):
    "Translate the settings dict to the target"

    for member in MEMBERS_SOURCE:
        _save_setattr(target, member, target_setting)


def _griddef_to_dict(griddef):
    "Translate the grid def to a settings dict."

    griddef_settings = {
        'gridcols': []
    }

    for member in MEMBERS_GRIDDEF:
        griddef_settings = _save_getattr(griddef, member, griddef_settings)

    for gridcol in griddef.evalocl("gridcols->orderby(orderidx)"):
        griddef_settings['gridcols'].append(_gridcol_to_dict(gridcol))

    return griddef_settings


def _dict_to_griddef(griddef, griddef_setting):
    "Translate the settings dict to a grid def ."

    for member in MEMBERS_GRIDDEF:
        _save_setattr(griddef, member, griddef_setting)

    for gridcol_setting in griddef_setting.get('gridcols', []):

        gridcol = vtcapp.createobject('GridColDef')
        griddef.gridcols.append(gridcol)
        _dict_to_gridcol(gridcol, gridcol_setting)


def _gridcol_to_dict(gridcol):
    "Translate the grid col def to a dict."

    gridcol_settings = {}

    for member in MEMBERS_GRIDCOL:
        gridcol_settings = _save_getattr(gridcol, member, gridcol_settings)

    return gridcol_settings


def _dict_to_gridcol(gridcol, gridcol_setting):
    "Translate the grid col def to a settings dict."

    for member in MEMBERS_GRIDCOL:
        _save_setattr(gridcol, member, gridcol_setting)


# region helper functions

def to_dict(source):
    "Transfers the list settings to a dict."

    if not source:
        return None

    if source.evalocl("oclIsKindOf(LinkContainer)"):
        source = source.rolle

    # Save global settings
    source_settings = _source_to_dict(source)

    # Save griddefs
    for griddef in source.evalocl("griddefs"):
        source_settings['griddefs'].append(_griddef_to_dict(griddef))

    return source_settings


def from_dict(target, target_setting):
    "Transfers the list settings from a dict."

    if not target:
        return

    if target.evalocl("oclIsKindOf(LinkContainer)"):
        target = target.rolle

    # Restore global settings
    _dict_to_target(target, target_setting)

    # Remove old grids
    for griddef in list(target.evalocl("griddefs")):
        target.griddefs.remove(griddef)

    # Create new griddefs
    for griddef_setting in target_setting.get('griddefs', []):

        griddef = vtcapp.createobject("GridDef")
        target.griddefs.append(griddef)
        _dict_to_griddef(griddef, griddef_setting)


def select_folder(base, base_is_source=True):
    "Lets the user select a destination or target."

    title = "{} {}: '{}'".format(TITLE, vtcapp.translate("from" if base_is_source else "to"), base)
    return vtcapp.selectobjectintree(title, [], "", "AbstractFolder, LinkContainer, ViewType")


def select_action():
    "Lets the user select which action should be performed."
    dlgDefinition="""
    <Dialog Title="Transfer list settings" Width="500">
        <Group Label="Select action" ShowLabel="True">
            <ListBox Name="AuswahlBox" Lines="4"/>
        </Group>
        <Dialog.Buttons>
            <Button Text="OK" IsAccept="True" Command="{Binding OkCommand}" />
            <Button Text="Cancel" IsCancel="True" Command="{Binding CancelCommand}" />
        </Dialog.Buttons>
    </Dialog>
    """
    initValues = {}
    action = None
    initValues["AuswahlBox.Items"] = (vtcapp.translate("Transfer settings from another list"), vtcapp.translate("Transfer this list settings"), vtcapp.translate("Load settings from file"), vtcapp.translate("Save settings to file"))
    ok, values = vtcapp.showcustomdialog(dlgDefinition, initValues)
    if ok:
        actions = {0: 'transfer_from_list', 1:'transfer_to_list', 2: 'load_from_file', 3: 'save_to_file'}
        action = actions.get(values["AuswahlBox"])

    return action

# region main function

# We cannot use normal main entry, because we need locals()
source = argobject  # type: ignore # noqa

# Check conditions
if not source.eval("oclisKindOf(AbstractFolder) or oclisKindOf(LinkContainer) or oclisKindOf(ViewType)"):
    raise Exception(vtcapp.translate("This script only works for Folders, Link Containers and Ressource Planning views."))

# Select action
action = select_action()
if action:

    # Execute action
    try:
        locals()[action](source)

    # Catch error. Display short message and print long message.
    except Exception as e:
        traceback.print_exc()
        message = "{}\n\n{}\n\n({})".format(vtcapp.translate("An error occurred."),
                                            e,
                                            vtcapp.translate("See Python console for more information"))
        vtcapp.msgbox(message)