import { System } from "./System.js";

/**
 * Utility method for making sure that the system is properly constructed, used internally by the engine itself
 * @param {System} system
 * @throws {Error} if the system fails validation
 */
export function system_validate_class(system) {
    if (system === undefined) {
        throw new Error("System is undefined");
    }

    if (system === null) {
        throw new Error("System is null");
    }

    if (!(system instanceof System)) {
        throw new TypeError("System does not inherit from \"System\" class");
    }

    if (typeof system.add === "function") {
        throw new Error(`uses deprecated 'add' method, should use 'link' instead`);
    }

    if (typeof system.remove === "function") {
        throw new Error(`uses deprecated 'remove' method, should use 'unlink' instead`);
    }

    // validate 'components_used' section
    const components_used = system.components_used;
    const components_used_count = components_used.length;

    for (let i = 0; i < components_used_count; i++) {
        const spec = components_used[i];

        if (spec === undefined || spec === null || spec.isResourceAccessSpecification !== true) {
            throw new Error(`Invalid access specification[${i}], expected an instance of ResourceAccessSpecification, but got something else`);
        }

        if (spec.resource === null) {
            throw new Error(`No component specified for element [${i}]`);
        }

        if (spec.access === 0) {
            throw new Error('No access modifiers specified, must have at least one (such as Read, Write, Create)');
        }

        // backtrace to make sure there are no duplicates
        for (let j = 0; j < i - 1; j++) {
            const spec_other = components_used[j];

            if (spec_other.resource === spec.resource) {
                throw new Error(`Duplicate specification of component [${j}] and [${i}]`);
            }
        }
    }

    //validate dependencies
    const dependencies = system.dependencies;

    const numDependencies = dependencies.length;

    if (numDependencies < 1) {
        throw new Error(`A system must declare at least one dependency`);
    }

    if (numDependencies > 1) {
        //check for duplicates
        for (let i = 0; i < numDependencies; i++) {
            const dependencyA = dependencies[i];
            for (let j = i + 1; j < numDependencies; j++) {
                if (dependencyA === dependencies[j]) {
                    throw new Error(`Detected duplicate dependency: ${dependencyA.constructor.typeName}`);
                }
            }
        }
    }

    // deprecation check
    if(system.startup !== System.prototype.startup && system.startup.length > 1) {
        throw new Error(`'startup' method declares ${system.startup.length} arguments, callbacks have been deprecated in meep 2.128.0, use promises instead`);
    }

    // deprecation check
    if(system.shutdown !== System.prototype.shutdown && system.shutdown.length > 1) {
        throw new Error(`'shutdown' method declares ${system.shutdown.length} arguments, callbacks have been deprecated in meep 2.128.0, use promises instead`);
    }

    const linkArgumentCount = numDependencies + 1;

    if (system.link !== System.prototype.link && system.link.length !== linkArgumentCount && system.__validation_ignore_link_argument_count !== true) {
        throw new Error(`'link' method declares ${system.link.length} instead of expected ${linkArgumentCount} based on it's ${numDependencies} dependencies`);
    }

    if (system.unlink !== System.prototype.unlink && system.unlink.length !== linkArgumentCount && system.__validation_ignore_link_argument_count !== true) {
        throw new Error(`'unlink' method declares ${system.unlink.length} instead of expected ${linkArgumentCount} based on it's ${numDependencies} dependencies`);
    }
}