import is from '@pilotlab/is';
import Debug from '@pilotlab/debug';
import { NodeFactoryBase } from '@pilotlab/nodes';
import Attribute from './attribute';
import AttributeCreateOptions from './attributeCreateOptions';
import DataTools from '../dataTools';
import IAttributesFactory from './interfaces/iAttributesFactory';
import IAttributeFactory from './interfaces/iAttributeFactory';
import IAttribute from './interfaces/iAttribute';
import { DataType } from './attributeEnums';
import AttributeChangeOptions from './attributeChangeOptions';
import AttributeBase from './attributeBase';
import AttributesBase from './attributesBase';


export abstract class AttributeFactoryBase<TAttribute extends IAttribute>
extends NodeFactoryBase<TAttribute>
implements IAttributeFactory {
    instance(options?:AttributeCreateOptions):TAttribute {
        if (is.notEmpty(options)) {
            let dataType:(DataType | string) = options.dataType;
            if (is.empty(dataType, true)) dataType = DataType.ANY;

            /// IMPORTANT: We need to initialize the new attribute with any value that was
            /// passed in. If the new attribute is to be a collection, it must be initialized
            /// with an object representing the proper schema, otherwise we won't be able
            /// to properly set child values later.
            let attribute:any;
            if (this.getBaseDataType(dataType) === DataType.COLLECTION) {
                let value:any = options.value;
                if (value instanceof AttributesBase) value = value.toObject(true, true, true);
                else if (value instanceof AttributeBase) value = value.attributes.toObject(true, true, true);
                attribute = this.p_factoryManager.instance(dataType, value);
            } else {
                attribute = this.p_factoryManager.instance(dataType);
            }

            if (is.empty(attribute, false)) return <TAttribute><IAttribute>new Attribute();

            if (is.notEmpty(options)) {
                if (is.notEmpty(options.key)) attribute.key = options.key;
                if (is.notEmpty(options.label)) attribute.label = options.label;
                if (is.notEmpty(options.isEditable)) attribute.isEditable = options.isEditable;
                if (is.notEmpty(options.isHidden)) attribute.isHidden = options.isHidden;
                attribute.set(options.value, options.changeOptions);
            }

            return attribute;
        } else return <TAttribute><IAttribute>new Attribute();
    }


    get collection():IAttributesFactory { return null; }


    fromAny(value:(TAttribute | Object | string), ...args:any[]):TAttribute {
        let node:TAttribute;

        if (value instanceof AttributeBase) {
            node = <TAttribute>value.copy;
        } else if (value instanceof AttributesBase) {
            node = this.fromObject((<AttributesBase<any, any>>value).toObject(true, true, true));
        } else if (typeof value === 'string') {
            node = this.fromString(value, ...args);
        } else if (Array.isArray(value)) {
            node = this.fromArray(value, ...args);
        } else node = this.fromObject(value, ...args);

        if (is.empty(node)) return this.instance();
        return node;
    }


    fromObject(obj:any, ...args:any[]):TAttribute {
        try {
            if (is.empty(obj)) return null;

            let key:string;
            let value:any;
            let dataType:(DataType | string);
            let label:string;
            let isEditable:boolean = false;
            let isHidden:boolean = false;
            let index:number;
            let hasValueProperty:boolean = true;

            if (obj.hasOwnProperty('key')) key = obj['key'];
            else if (obj.hasOwnProperty('id')) key = obj['id'];
            else if (obj.hasOwnProperty('_id')) key = obj['_id'];

            if (obj.hasOwnProperty('value')) {
                value = obj['value'];

                if (is.notEmpty(value) && value.hasOwnProperty('value')) {
                    if (is.notEmpty(key, true)) value['key'] = key;
                    return this.fromAny(value);
                }
            }
            else if (obj.hasOwnProperty('children')) value = obj['children'];
            else if (obj.hasOwnProperty('attributes')) value = obj['attributes'];
            else if (obj.hasOwnProperty('nodes')) value = obj['nodes'];
            else {
                hasValueProperty = false;
            }

            if (obj.hasOwnProperty('label')) label = obj['label'];

            if (obj.hasOwnProperty('isEditable')) {
                if (typeof obj['isEditable'] === 'string') {
                    isEditable = obj['isEditable'].toLowerCase() === 'true';
                } else {
                    isEditable = obj['isEditable'];
                }
            }

            if (obj.hasOwnProperty('isHidden')) {
                if (typeof obj['isHidden'] === 'string') {
                    isHidden = obj['isHidden'].toLowerCase() === 'true';
                } else {
                    isHidden = obj['isHidden'];
                }
            }

            if (obj.hasOwnProperty('index') && is.number(parseFloat('' + obj['index']))) {
                index = parseFloat('' + obj['index']);
            }

            if (hasValueProperty) {
                /// The object has a value property, so we'll assume it's
                /// describing an attribute.
                if (obj.hasOwnProperty('dataType') && is.notEmpty(obj['dataType'], true)) {
                    if (typeof obj['dataType'] === 'string') {
                        dataType = obj['dataType'].toLowerCase();
                    } else {
                        dataType = DataTools.getDataType(value);
                    }
                } else {
                    dataType = DataTools.getDataType(value);
                }
            } else if (is.notEmpty(key, true) || is.notEmpty(label, true) || (value.hasOwnProperty('dataType') && is.notEmpty(obj['dataType'], true))) {
                /// The object doesn't have a value property, but it has a label and/or
                /// a dataType property, so it appears to be describing an attribute.
                /// In this case, we'll give it a dataType of ANY.
                dataType = DataType.ANY;
                value = null;
            } else {
                /// The object doesn't have a value property, it must describe a collection,
                /// so pass the entire object as the value.
                dataType = DataType.COLLECTION;
                value = this.fromObject({ value: obj });
            }

            return this.instance(new AttributeCreateOptions(value, dataType, label, index, key, AttributeChangeOptions.zero, obj, isEditable, isHidden));
        } catch (e) {
            Debug.error(e, 'Attribute.create.fromObject(...)');
        }
    }


    fromArray(values:any[], ...args:any[]):TAttribute {
        let attributeNew:TAttribute = this.instance(new AttributeCreateOptions(null, DataType.COLLECTION));

        for (let i = 0, length = values.length; i < length; i++) {
            let value:any = values[i];
            let attributeChild:IAttribute = this.fromAny(value);

            if (is.notEmpty(attributeChild)) {
                attributeNew.attributes.add(attributeChild);
            }
        }

        return attributeNew;
    }


    getBaseDataType(dataType:string):string {
        const dataTypeDescription:any = this.p_factoryManager.getDescription(dataType);
        return is.notEmpty(dataTypeDescription) && is.notEmpty(dataTypeDescription.baseType) ? dataTypeDescription.baseType : 'any';
    }

} // End class


export default AttributeFactoryBase;
