// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
// SPDX-FileCopyrightText: 2020 Philip Chimento <philip.chimento@gmail.com>

/* exported _checkAccessors, _createBuilderConnectFunc, _createClosure,
_registerType */

// This is a helper module in which to put code that is common between the
// legacy GObject.Class system and the new GObject.registerClass system.

var _registerType = Symbol('GObject register type hook');

function _generateAccessors(pspec, propdesc, GObject) {
    const {name, flags} = pspec;
    const readable = flags & GObject.ParamFlags.READABLE;
    const writable = flags & GObject.ParamFlags.WRITABLE;

    if (!propdesc) {
        propdesc = {
            configurable: true,
            enumerable: true,
        };
    }

    if (readable && writable) {
        if (!propdesc.get && !propdesc.set) {
            const privateName = Symbol(`__autogeneratedAccessor__${name}`);
            const defaultValue = pspec.get_default_value();
            propdesc.get = function () {
                if (!(privateName in this))
                    this[privateName] = defaultValue;
                return this[privateName];
            };
            propdesc.set = function (value) {
                if (value !== this[privateName]) {
                    this[privateName] = value;
                    this.notify(name);
                }
            };
        } else if (!propdesc.get) {
            propdesc.get = function () {
                throw new Error(`setter defined without getter for property ${name}`);
            };
        } else if (!propdesc.set) {
            propdesc.set = function () {
                throw new Error(`getter defined without setter for property ${name}`);
            };
        }
    } else if (readable && !propdesc.get) {
        propdesc.get = function () {
            throw new Error(`missing getter for read-only property ${name}`);
        };
    } else if (writable && !propdesc.set) {
        propdesc.set = function () {
            throw new Error(`missing setter for write-only property ${name}`);
        };
    }

    return propdesc;
}

function _checkAccessors(proto, pspec, GObject) {
    const {name, flags} = pspec;
    if (flags & GObject.ParamFlags.CONSTRUCT_ONLY)
        return;

    const underscoreName = name.replace(/-/g, '_');
    const camelName = name.replace(/-([a-z])/g, match => match[1].toUpperCase());
    let propdesc = Object.getOwnPropertyDescriptor(proto, name);
    let dashPropdesc = propdesc, underscorePropdesc, camelPropdesc;
    const nameIsCompound = name.includes('-');
    if (nameIsCompound) {
        underscorePropdesc = Object.getOwnPropertyDescriptor(proto, underscoreName);
        camelPropdesc = Object.getOwnPropertyDescriptor(proto, camelName);
        if (!propdesc)
            propdesc = underscorePropdesc;
        if (!propdesc)
            propdesc = camelPropdesc;
    }

    const readable = flags & GObject.ParamFlags.READABLE;
    const writable = flags & GObject.ParamFlags.WRITABLE;
    if (!propdesc || (readable && !propdesc.get) || (writable && !propdesc.set))
        propdesc = _generateAccessors(pspec, propdesc, GObject);

    if (!dashPropdesc)
        Object.defineProperty(proto, name, propdesc);
    if (nameIsCompound) {
        if (!underscorePropdesc)
            Object.defineProperty(proto, underscoreName, propdesc);
        if (!camelPropdesc)
            Object.defineProperty(proto, camelName, propdesc);
    }
}

function _createClosure(builder, thisArg, handlerName, swapped, connectObject) {
    connectObject ??= thisArg;

    if (swapped) {
        throw new Error('Unsupported template signal flag "swapped"');
    } else if (typeof thisArg[handlerName] === 'undefined') {
        throw new Error(`A handler called ${handlerName} was not ` +
            `defined on ${thisArg}`);
    }

    return thisArg[handlerName].bind(connectObject);
}

function _createBuilderConnectFunc(klass) {
    const {GObject} = imports.gi;
    return function (builder, obj, signalName, handlerName, connectObj, flags) {
        const objects = builder.get_objects();
        const thisObj = objects.find(o => o instanceof klass);
        const swapped = flags & GObject.ConnectFlags.SWAPPED;
        const closure = _createClosure(builder, thisObj, handlerName, swapped,
            connectObj);

        if (flags & GObject.ConnectFlags.AFTER)
            obj.connect_after(signalName, closure);
        else
            obj.connect(signalName, closure);
    };
}
