import * as React from 'react';
import {BaseFrameworkAdaptor} from '../../adaptors/framework/BaseFrameworkAdaptor';
import {logger} from '../../adaptors/logger/LoggerAdaptor';
import {Provider} from './Provider';
import {SimpleXdmDefinitionBuilder} from '../../definitions/SimpleXdmDefinitionBuilder';

export module ModuleDefinitions {

    export const MIN_REGISTRATION_NAME_LENGTH : number = 1;
    export const MAX_REGISTRATION_NAME_LENGTH : number = 50;

    /**
     * All modules (classes that extend BaseModule) must be instantiated with props
     * that extend this interface. This facilitates the BaseModule's provision of
     * common module behaviour.
     */
    export interface Props {

        adaptor: BaseFrameworkAdaptor;

    }

    /**
     * All modules (classes that extend BaseModule) must have a state that extends this
     * interface. This facilitates the BaseModule's provision of common module behaviour.
     */
    export interface State {

        enabled: boolean;
        registered: boolean;

    }

}

export interface Module<Props extends ModuleDefinitions.Props, State extends ModuleDefinitions.State> {

    props?: Props;
    state?: State;

    /**
     * Enable or disable a module.
     * @param enabled indication of whether the module should be enabled or disabled.
     */
    setEnabled(enabled: boolean): void;

    /**
     * Determines if a module is enabled or disabled.
     */
    isEnabled(): boolean;

    /**
     * Get the name used to register this module with the underlying framework.
     */
    getModuleRegistrationName(): string;

    /**
     * Implementations of this method are responsible for returning an abject that can
     * be used to build a module that can be registered with Simple XDM.
     * @returns {SimpleXdmDefinitionBuilder}
     */
    getSimpleXdmDefinitionBuilder() : SimpleXdmDefinitionBuilder;

    /**
     * Get the provider for this module.
     */
    getProvider(): Provider;

}

/**
 * This class provides common module behaviour and characteristics. The term 'Module' refers to
 * a set of related functionality and provides the interface between a product that requires the
 * module functionality and the framework that implements it.
 */
export abstract class BaseModule<Props extends ModuleDefinitions.Props, State extends ModuleDefinitions.State>
extends React.Component<Props, State> implements Module<Props, State> {

    constructor(props: Props) {
        super(props);
    }

    componentDidMount(): void {
        const props = this.props;
        const registrationName = this.getModuleRegistrationName();
        logger.debug('Mounting module', registrationName, '...');
        this.validateRegistrationName(registrationName);
        props.adaptor.registerModule(this, props);

        const moduleState : State = this.state;
        moduleState.enabled = true;
        moduleState.registered = true;
        this.setState(moduleState);
    }

    setEnabled(enabled: boolean): void {
        const moduleState : State = this.state;
        moduleState.enabled = enabled;
        this.setState(moduleState);
    }

    isEnabled(): boolean {
        return this.state.enabled;
    }

    componentWillUnmount(): void {
        // Do nothing - we don't unregister modules.
    }

    shouldComponentUpdate(): boolean {
        // This module hierarchy never renders anything.
        return false;
    }

    /**
     * Get the name used to register this module with the underlying framework.
     */
    abstract getModuleRegistrationName(): string;

    /**
     * Implementations of this method are responsible for returning an abject that can
     * be used to build a module that can be registered with Simple XDM.
     * @returns {SimpleXdmDefinitionBuilder}
     */
    abstract getSimpleXdmDefinitionBuilder() : SimpleXdmDefinitionBuilder;

    /**
     * Get the provider for this module.
     */
    abstract getProvider(): Provider;

    validateRegistrationName(registrationName: string): void {
        if (!registrationName || registrationName.length < ModuleDefinitions.MIN_REGISTRATION_NAME_LENGTH) {
            throw new Error('The registration name is too short (< ' + ModuleDefinitions.MIN_REGISTRATION_NAME_LENGTH + ' characters).');
        }
        if (registrationName.length > ModuleDefinitions.MAX_REGISTRATION_NAME_LENGTH) {
            throw new Error('The registration name is too long (> ' + ModuleDefinitions.MAX_REGISTRATION_NAME_LENGTH + ' characters).');
        }
        if (!registrationName.match(/^[a-z]+$/i)) {
            throw new Error('The registration name must only contain alphabetic characters.');
        }
    }

    render(): React.ReactElement<any> | null {
        return null;
    }
}
