import Component from './Component';
import FaceValue from './FaceValue';
import validate from '../Helpers/Validate';
import ConsoleMessages from '../Config/ConsoleMessages';
import { error, isNull, isUndefined, isObject, isArray, isFunction, callback } from '../Helpers/Functions';

export default class Face extends Component {

    /**
     * This class is meant to be provide an interface for all other faces to
     * extend.
     *
     * @class Face
     * @extends Component
     * @param {(FaceValue|object)} value - The `Face` value. If not an instance
     *     of FaceValue, this argument is assumed to be the instance attributes.
     * @param {(object|undefined)} [attributes] - The instance attributes.
     */
    constructor(value, attributes) {
        if(!(value instanceof FaceValue) && isObject(value)) {
            attributes = value;
            value = undefined;
        }

        super();

        this.setAttributes(Object.assign({
            autoStart: true,
            countdown: false,
            animationRate: 500
        }, this.defaultAttributes(), attributes || {}));

        if(isNull(value) || isUndefined(value)) {
            value = this.defaultValue();
        }

        if(value) {
            this.value = value;
        }
    }

    /**
     * The `dataType` attribute.
     *
     * @type {*}
     */
    get dataType() {
        return this.defaultDataType();
    }

    /**
     * The `value` attribute.
     *
     * @type {*}
     */
    get value() {
        return this.$value;
    }

    set value(value) {
        if(!(value instanceof FaceValue)) {
            value = this.createFaceValue(value);
        }

        this.$value = value;
    }

    /**
     * The `stopAt` attribute.
     *
     * @type {*}
     */
    get stopAt() {
        return this.$stopAt;
    }

    set stopAt(value) {
        this.$stopAt = value;
    }

    /**
     * The `originalValue` attribute.
     *
     * @type {*}
     */
    get originalValue() {
        return this.$originalValue;
    }

    set originalValue(value) {
        this.$originalValue = value;
    }

    /**
     * This method is called with every interval, or every time the clock
     * should change, and handles the actual incrementing and decrementing the
     * clock's `FaceValue`.
     *
     * @param  {FlipClock} instance - The `FlipClock` instance.
     * @param  {Function} fn - The interval callback.
     * @return {Face} - This `Face` instance.
     */
    interval(instance, fn) {
        if(this.countdown) {
            this.decrement(instance);
        }
        else {
            this.increment(instance);
        }

        callback.call(this, fn);

        if(this.shouldStop(instance)) {
            instance.stop();
        }

        return this.emit('interval');
    }

    /**
     * Determines if the clock should stop or not.
     *
     * @param  {FlipClock} instance - The `FlipClock` instance.
     * @return {boolean} - Returns `true` if the clock should stop.
     */
    shouldStop(instance) {
        return !isUndefined(this.stopAt) ? this.stopAt === instance.value.value : false;
    }

    /**
     * By default this just returns the value unformatted.
     *
     * @param  {FlipClock} instance - The `FlipClock` instance.
     * @param  {*} value - The value to format.
     * @return {*} - The formatted value.
     */
    format(instance, value) {
        return value;
    }

    /**
     * The default value for the `Face`.
     *
     * @return {*} - The default value.
     */
    defaultValue() {
        //
    }

    /**
     * The default attributes for the `Face`.
     *
     * @return {(Object|undefined)} - The default attributes.
     */
    defaultAttributes() {
        //
    }

    /**
     * The default data type for the `Face` value.
     *
     * @return {(Object|undefined)} - The default data type.
     */
    defaultDataType() {
        //
    }

    /**
     * Increment the clock.
     *
     * @param  {FlipClock} instance - The `FlipClock` instance.
     * @param  {Number} [amount] - The amount to increment. If the amount is not
     *     defined, it is left up to the `Face` to determine the default value.
     * @return {void}
     */
    increment(instance, amount) {
        //
    }

    /**
     * Decrement the clock.
     *
     * @param  {FlipClock} instance - The `FlipClock` instance.
     * @param  {Number} [amount] - The amount to decrement. If the amount is not
     *     defined, it is left up to the `Face` to determine the default value.
     * @return {void}
     */
    decrement(instance, amount) {
        //
    }

    /**
     * This method is called right after clock has started.
     *
     * @param  {FlipClock} instance - The `FlipClock` instance.
     * @return {void}
     */
    started(instance) {
        //
    }

    /**
     * This method is called right after clock has stopped.
     *
     * @param  {FlipClock} instance - The `FlipClock` instance.
     * @return {void}
     */
    stopped(instance) {
        //
    }

    /**
     * This method is called right after clock has reset.
     *
     * @param  {FlipClock} instance - The `FlipClock` instance.
     * @return {void}
     */
    reset(instance) {
        //
    }

    /**
     * This method is called right after `Face` has initialized.
     *
     * @param  {FlipClock} instance - The `FlipClock` instance.
     * @return {void}
     */
    initialized(instance) {
        //
    }

    /**
     * This method is called right after `Face` has rendered.
     *
     * @param  {FlipClock} instance - The `FlipClock` instance.
     * @return {void}
     */
    rendered(instance) {
        //
    }

    /**
     * This method is called right after `Face` has mounted.
     *
     * @param  {FlipClock} instance - The `FlipClock` instance.
     * @return {void}
     */
    mounted(instance) {
        if(this.autoStart && instance.timer.isStopped) {
            window.requestAnimationFrame(() => instance.start(instance));
        }
    }

    /**
     * Helper method to instantiate a new `FaceValue`.
     *
     * @param  {FlipClock} instance - The `FlipClock` instance.
     * @param  {object|undefined} [attributes] - The attributes passed to the
     *     `FaceValue` instance.
     * @return {Divider} - The instantiated `FaceValue`.
     */
    createFaceValue(instance, value) {
        return FaceValue.make(
            isFunction(value) && !value.name ? value() : value, {
                minimumDigits: this.minimumDigits,
                format: value => this.format(instance, value)
            }
        );
    }

}