import AnnotationAPI from '../../api/AnnotationAPI';

import Project from '../../model/Project';
import MediaObject from '../../model/MediaObject';
//import Events from '../../util/Events'; //TODO this must be part of STAN too!

export const AnnotationEvents = {
    ON_SAVE: 'on-save-annotation',

    ON_DELETE: 'on-delete-annotation',

    ON_EDIT: 'on-edit-annotation',

    ON_PLAY: 'on-play-annotation',

    ON_SET_ANNOTATION: 'on-set-annotation',

    ON_SET_SELECTION: 'on-set-selection',

    ON_CHANGE_TARGET: 'on-change-target'

    //TODO somehow add the resource IDs that can be triggered as well
}


export class ObjectUtil {

    static clone = object => {
        return object == null ? null : JSON.parse(JSON.stringify(object));
    }

    static filterNullValues = object => {
        return Object.keys(object).filter(key => object[key] != null).reduce(
            (obj, key) => {
                obj[key] = object[key];
                return obj;
            }, {}
        );
    }
}

/*

The activeTarget is central for generating annotations: it contains:
- nestedPID
- nonW3Cparams that need to be saved into the annotation as well (e.g. project & user IDs)
- additional semantic types related to the leaf (thus current target) of the nestedPID

*/

    /*
    {
        "templates" : {
            "mediaResource" : {
                "nestedPID" : [
                    {
                        "type": ["Collection"],
                        "property": "isPartOf"
                    },
                    {
                        "type": ["Resource"],
                        "property": "isPartOf"
                    },
                    {
                        "type": ["MediaObject", "Representation"],
                        "property": "isRepresentation",
                        "additionalType": ["Video", "Audio", "Image"]
                    }
                ],
                "selectionType": "Segment",
                "listTypes" : ["Resource"]
            }
        },
        "editOptions" : {
            "Resource" : {
                "motivations" : ["classification", "comment", "link", "metadata"]
            },
            "MediaObject" : {
                "motivations" : ["classification", "comment", "link", "metadata"]
            },
            "Segment" : {
                "motivations" : ["classification", "comment", "link", "metadata"]
            }
        },
        "motivationConfig" : {
            "classification" : {
                "vocabularies" : ["GTAA", "UNESCO"]
            },
            "link" : {
                "apis" : [
                    {"name" : "wikidata"},
                    {"name" : "europeana"},
                    {"name" : "custom"}
                ]
            },
            "metadata" : {
                "mediaObject" : {
                    "templates" : ["generic"],
                    "cardsPerUser" : 10
                },
                "mediaSegment" : {
                    "templates" : ["generic"],
                    "cardsPerUser" : 10
                },
                "templates" : {
                    "generic" : {
                        "id" : "generic",
                        "label" : "Generic",
                        "properties" : [
                            {"key" : "title", "type" : "string"},
                            {"key" : "description", "type" : "string"},
                            {"key" : "date", "type" : "date"}
                        ]
                    }
                }
            },
            "comment" : {}
        }
    }
    */

export class AnnotationClient {

	constructor(config, events) {
        if(!this.validateConfig(config)) {
            alert('error: Please provide a valid STAN-client config');
            return;
        }
        this.config = config;
        this.activeTemplate = config.templates[Object.keys(config.templates)[0]] //always just set the first template
        this.events = events;//new Events(); TODO fallback to new events object/function

        this.activeTarget = null; //set via setActiveTarget
		this.activeAnnotation = null; //only one annotation is active
        this.activeBody = null; //always null by default
        this.activeSelection = null; //only one selection is active

        this.annotations = []; //filled when setActiveTarget is called (or setActiveProject)
        AnnotationClient.instance = this;
	}

    static singleton(config, events) {
        if(AnnotationClient.instance) {
            AnnotationClient.instance.config = config;
            AnnotationClient.instance.events = events;
            return AnnotationClient.instance;
        }
        return new AnnotationClient(config, events);
    }

    validateConfig = config => { //TODO extend this much more, for now it roughly checks if there is a template defined
        return config && config.templates && typeof config.templates === 'object';
    }

    setActiveTemplate = templateId => {
        this.activeTemplate = this.config.templates[templateId];
    }

    /* ------------------------- LOADING ANNOTATIONS OF ACTIVE TARGET (CHANGE TO STAN-SERVER) --------------------------------- */

    async refreshTargetAnnotations() {
        //TODO fetch all annotations based on an NPID
        //this.activeTarget.npid;
        if(!this.activeTarget) return [];
        let annotations = await this.__loadTargetAnnotations(this.activeTarget.npid, this.activeTarget.nonW3Cparams);
        if(this.activeTemplate.listTypes) {
            const additionalAnnotations = await this.__loadAnnotationsOfTypes(this.activeTemplate.listTypes);
            annotations = annotations.concat(additionalAnnotations);
        }
        return annotations;
    }

    async __loadAnnotationsOfTypes(types) {
        let annotations = [];
        for(let i=0;i<types.length;i++) { //NOTE: forEach is not Promise aware, therefore a simple for-loop
            const temp = await this.__loadAnnotationsOfType(
                this.activeTarget.npid,
                types[i], //semanticType
                this.activeTarget.nonW3Cparams
            );
            annotations = annotations.concat(temp);
        }
        return new Promise(resolve => {
            resolve(annotations);
        })
    }

    //loads the annotations directly related to the active target (leaf node)
    async __loadTargetAnnotations (npid, nonW3Cparams={}) {
        let annotations = null;
        if(npid) {
            const leaf = npid[npid.length -1];
            annotations = await this.__loadFromAPI({
                'target.source': leaf.id //leave out the type, since it can be e.g. Segment / MediaObject
            }, nonW3Cparams)
        }
        return new Promise(resolve => {
            resolve(annotations);
        });
    }

    //loads annotations directly related to a certain part (indicated by the semanticType) of the npid
    async __loadAnnotationsOfType(npid, semanticType, nonW3Cparams={}) {
        let annotations = null;
        if(npid) {
            const pidOfType = npid.find(pid => pid.type.indexOf(semanticType) != -1);
            annotations = pidOfType ? await this.__loadFromAPI({
                'target.source': pidOfType.id,
                'target.type' : semanticType
            }, nonW3Cparams) : null;
        }
        return new Promise(resolve => {
            resolve(annotations);
        });
    }

    __loadFromAPI = (filter, nonW3Cparams={}) => {
        return new Promise(resolve => {
            //add the non-null nonW3Cparams to the filter
            Object.assign(filter, ObjectUtil.filterNullValues(nonW3Cparams));

            //hack for now:
            filter['user.keyword'] = filter['user'];
            delete filter['user'];

            const notFilter = {
                motivation: 'bookmarking'
            };

            AnnotationAPI.getFilteredAnnotations(
                filter['user.keyword'], //user ID
                filter,
                notFilter,
                resolve
            );
        });
    };

    /* ------------------------- CRUD (REWIRE TO STAN) --------------------------------- */

	save = (setActive=true, notify=true) => {
        if(!this.activeAnnotation) {
            alert('no active annotation, cannot save');
            return;
        }
        return new Promise(resolve => {
            AnnotationAPI.saveAnnotation(this.activeAnnotation, data => {
                if(data == null) resolve(null);
                //assign the annotation data returned from the server (which now has annotation IDs)
                this.activeAnnotation = setActive ? data : null;

                //add or update the annotation in the list
                if(!this.annotations.find(a => a.id === data.id)) {
                    this.annotations.push(ObjectUtil.clone(data));
                } else {
                    this.annotations[
                        this.annotations.findIndex(a => a.id === data.id)
                    ] = ObjectUtil.clone(data);
                }

                if(notify) {
                    this.events.trigger(AnnotationEvents.ON_SAVE, ObjectUtil.clone(data)); // also trigger all listeners
                    //this.events.trigger(annotation.target.source, annotation); // TODO also trigger everyone listening to the media object
                }

                resolve(this.activeAnnotation); // callback for the async function
            });
        })
	};

    delete = (annotation, notify=true) => {
        return new Promise(resolve => {
            AnnotationAPI.deleteAnnotation(annotation, (data, annotation) => {
                if(data == null) resolve(null, null);
                resolve(data, annotation);

                this.annotations = this.annotations.filter(a => a.id !== annotation.id);
                this.activeAnnotation = null;
                if(notify) {
                    this.events.trigger(AnnotationEvents.ON_DELETE, annotation);
                    //this.events.trigger(annotation.target.source, annotation);
                }
            });
        });
    };

    /* ------------------------ IMPORT CONVENIENCE METHODS FOR CRUD ON BODY ELEMENTS --------------------- */

    async saveBodyElement(bodyElement, setActive=true, notify=true, annotation=null) {
        if(annotation != null) this.activeAnnotation = annotation; //enables "stateless" calls

        if(!this.activeAnnotation || bodyElement == null) return;
        const body = this.activeAnnotation.body ? this.activeAnnotation.body : [];
        if(bodyElement.annotationId) { // update
            body[body.findIndex(b => b.annotationId === bodyElement.annotationId)] = ObjectUtil.clone(bodyElement);
        } else { // add
            bodyElement.annotationId = IDUtil.guid(); // generate on the client, so we know how to track it
            body.push(ObjectUtil.clone(bodyElement));
        }
        this.activeAnnotation.body = body;
        this.activeAnnotation = await this.save(true, notify);
        this.activeBody = setActive ? ObjectUtil.clone(bodyElement) : null; // the newly saved body element becomes the active one or null

        //return the promise and trigger listeners if needed TODO merge with save as well
        return new Promise(resolve => {
            resolve({annotation: this.activeAnnotation, bodyElement: this.activeBody});

            if(notify) {
                this.events.trigger(
                    AnnotationEvents.ON_SET_ANNOTATION, {annotation : this.activeAnnotation, bodyElement: this.activeBody}
                );
            }
        });
    };

    async removeBodyElement(motivation, index, setActive=true, notify=true, annotation=null) {
        if(annotation != null) this.activeAnnotation = annotation; //enables "stateless" calls

        if(!this.activeAnnotation) return;
        const bodyElementsOfMotivation = this.activeAnnotation.body ?
            this.activeAnnotation.body.filter(b => b.annotationType === motivation) :
            [];

        bodyElementsOfMotivation.splice(index, 1);
        this.activeAnnotation.body = this.activeAnnotation.body.filter(
            b => b.annotationType !== motivation
        ).concat(
            bodyElementsOfMotivation
        );
        this.activeAnnotation = await this.save(true, notify);

        const savedBodyElementsOfMotivation = this.activeAnnotation.body.filter(b => b.annotationType === motivation)

        if(setActive) {
            this.activeBody = savedBodyElementsOfMotivation.length > 0 ?
                ObjectUtil.clone(savedBodyElementsOfMotivation[0]) :
                null
            ;
        } else {
            this.activeBody = null;
        }

        //return the promise and trigger listeners if needed TODO merge with save as well
        return new Promise(resolve => {
            resolve({annotation: this.activeAnnotation, bodyElement: this.activeBody});

            if(notify) {
                this.events.trigger(AnnotationEvents.ON_SET_ANNOTATION, {
                    annotation : this.activeAnnotation, bodyElement: this.activeBody
                });
            }
        });

    }

    /* ------------------------- SELECTING PARTS OF THE SCREEN TO UPDATE ANNOTATION TARGETS ---------- */

    /*
    temporal segment looks like:

        {start: start, end: end}

    spatial segment looks like:

        {
            rect : {
                x : rect.x,
                y : rect.y,
                w : rect.width,
                h : rect.height
            },
            rotation : rect.rotation
        }
    */

    //TODO rework so it works for text, spatial, temporal selections
    //use it to create an active refinedBy/selection object, that can be visualised by whoever can (see saveSelection())
    //TODO create a new event for notifying: ON_SET_SELECTION
    setActiveSelection = (selection, notify=true) => { //TODO merge with setActiveTarget, called by the TierView only
        return new Promise(resolve => {
            this.activeSelection = selection ? ObjectUtil.clone(selection) : null;
            if(selection) {
                this.activeSelection.semanticType = this.activeTemplate.selectionType; //always add the selectionType
            }
            resolve(this.activeSelection);

            if(notify) {
                this.events.trigger(AnnotationEvents.ON_SET_SELECTION, this.activeSelection);
            }
        })
    }

    // Saves an annotation based on the current selection.
    // if there is an active annotation, that means the selection updates the target of that annotation
    // always make sure to SET the selection before saving it, since this gives other components the chance to
    // respond to the ON_SET_SELECTION event triggered (see setActiveSelection)
    async saveSelection(selection=null, setActive=true, notify=true) { //merge with save()
        if(selection != null) {
            this.activeSelection = await this.setActiveSelection(selection); //enables "stateless" calls
        }

        if(!this.activeSelection) {
            alert('no active selection, cannot save');
            return;
        }

        if(this.activeAnnotation && this.activeAnnotation.target.selector.refinedBy) {//update the selection
            this.activeAnnotation.target.selector.refinedBy = MSAnnotationUtil.__generateRefinedBy(this.activeSelection);
        } else if(this.activeTarget) { //create new annotation with the selection
            this.activeAnnotation = this.newAnnotation();

        } else {
            alert('error: trying to save selection without an active target');
            return new Promise(resolve => {
                resolve(null);
            });
        }

        await this.save(setActive, notify);
        //return the promise and trigger listeners if needed TODO merge with save as well
        return new Promise(resolve => {
            const data = {annotation: this.activeAnnotation, segment: this.activeSelection}
            resolve(data);
        });
    }

    /* ------------------------- EDIT, PLAY, SET/ACTIVATE ANNOTATION --------------------------------- */

    edit = (annotation, bodyElement=null, notify=true) => {
        this.__activateAndTrigger(AnnotationEvents.ON_EDIT, annotation, bodyElement, notify);
    }

    play = (annotation, bodyElement=null, notify=true) => {
        this.__activateAndTrigger(AnnotationEvents.ON_PLAY, annotation, bodyElement, notify);
    }

    setActiveAnnotation = (annotation, bodyElement=null, notify=true) => { // (currently) the can only be one active annotation
        this.__activateAndTrigger(AnnotationEvents.ON_SET_ANNOTATION, annotation, bodyElement, notify);
    };

    setActiveBodyElement = (bodyElement, notify=true) => { // activate a bodyElement within the active annotation
        this.__activateAndTrigger(AnnotationEvents.ON_SET_ANNOTATION, this.activeAnnotation, bodyElement, notify);
    };

    //all called by edit, play & setActiveAnnotation. The only difference is the event triggered
    __activateAndTrigger = (eventType, annotation, bodyElement=null, notify=true) => {
        this.activeAnnotation = ObjectUtil.clone(annotation);
        this.activeBody = ObjectUtil.clone(bodyElement);
        if (annotation && annotation.target) { //TODO simplify these functions as well
            //TODO this should not perse trigger a new ON_SET_SELECTION
            this.setActiveSelection(MSAnnotationUtil.extractSelectionFromTarget(annotation.target));
        }
        if(notify) {
            if(eventType != AnnotationEvents.ON_SET_ANNOTATION) {
                this.events.trigger(eventType, {
                    annotation : this.activeAnnotation, bodyElement: this.activeBody
                });
            }

            //for "edit" & "play" events also trigger the "set" listeners
            this.events.trigger(AnnotationEvents.ON_SET_ANNOTATION, {
                annotation : this.activeAnnotation, bodyElement: this.activeBody
            });
        }
    };

    /* ------------------------- CHANGE ANNOTATION TARGET --------------------------------- */

    /*
        TODO This function needs to be called whenever something is selected on the screen. This function
        then takes care of finding or creating the corresponding annotation.

        Concerning the parameters, either an annotation or a list of consecutive PID elements is supplied:
        [collectionId, resourceId, mediaObject, segment] TODO better think about passing this

        Most importantly: setting an active target ALWAYS results in (re)setting the active annotation.
        This way it is clear that changing/making a target on the screen OR selecting an existing annotation,
        results in the same thing: a new active annotation.

        The only difference (maybe) is that an existing annotation is already saved on the server.

    */
    //only called by the resourceviewer.jsx, so update there
    //TODO rename to loadAnnotationsForTarget()
    //TODO add the NPID template, ids & nonW3Cparams as params
    async setActiveTarget(ids, nonW3Cparams={}, additionalTypes=[], notify=true) {
        this.activeTarget = {
            ids : ids,
            nonW3Cparams : nonW3Cparams,
            additionalTypes : additionalTypes, //remember these too (see saveSelection())
            npid : MSAnnotationUtil.constructNPID(this.activeTemplate, ids, false, additionalTypes)
        }
        this.activeAnnotation = null; //no active annotation when this is changed
        this.activeSelection = null; //nothing is selected when changing the active (annotatable) target
        this.annotations = await this.refreshTargetAnnotations(); //always uses this.activeTarget to refresh the annotations

        return new Promise(resolve => {
            resolve(this.activeTarget);
            if(notify) {
                this.events.trigger(AnnotationEvents.ON_CHANGE_TARGET, this.activeTarget);
            }
        });
    };

    //constructs a new annotation based on the active target, selection & a desired semantic type (if any)
    newAnnotation = (semanticType=null, useSelection=true) => {
        const level = MSAnnotationUtil.getLevelOfSemanticType(this.activeTemplate.nestedPID, semanticType)
        if(level === -1 && semanticType) {
            alert('error: please supply a valid semantic type; ' + semanticType + ' is not part of the template');
            return null;
        }

        const selection = useSelection === true ? this.activeSelection : null;
        const idsToUse = semanticType ? this.activeTarget.ids.slice(0, level + 1) : this.activeTarget.ids;
        const additionalTypesToUse = semanticType ?
            level === (this.activeTarget.ids.length -1) ?
                this.activeTarget.additionalTypes :
                []
            : this.activeTarget.additionalTypes

        const annotation = MSAnnotationUtil.newAnnotationFromNPID(
            MSAnnotationUtil.constructNPID(
                this.activeTemplate,
                idsToUse,
                useSelection, //whether to use add the selectionType to the NPID or not
                additionalTypesToUse //only use the additional type if it matches the level
            ),
            selection, //FIXME: what if there is no selection, but useSelection is true?
            this.activeTarget.nonW3Cparams
        );
        return annotation;
    }

    /* --------------------------- TEMPORAL SEGMENTATION HELPERS -------------------- */

    setTemporalSegment = (start, end) => {
        this.setActiveSelection({type: 'temporal', start: start, end: end});
    }

    newTemporalSegment = () => {
        this.setActiveAnnotation(null);
        this.setTemporalSegment(-1, -1);
    };

    // TODO add callback function
    newTemporalSegmentFromLast = seekFunc => {
        if(this.activeSelection.end > 0) {
            this.setActiveAnnotation(null);
            this.setTemporalSegment(this.activeSelection.end, -1);
            if(seekFunc && typeof seekFunc === 'function') {
                seekFunc(this.activeSelection.end);
            }
        } else {
            this.newTemporalSegment();
        }
    };

    setStart = pos => this.setTemporalSegment(pos, this.activeSelection ? this.activeSelection.end : -1);

    setEnd = pos => this.setTemporalSegment(this.activeSelection ? this.activeSelection.start : -1, pos);

}

export class MSAnnotationUtil {

    static constructNPID = (npidTemplate, ids, useSelection=false, additionalTypes=[]) => {
        if(!npidTemplate) {
            alert('Error: you have to supply an NPID template before a NPID can be constructed');
            return null;
        }
        const template = ObjectUtil.clone(npidTemplate); //make sure to always create a new object to avoid weird behaviour

        //then grab the relevant part of the template
        const nestedPID = template.nestedPID.slice(0, ids.length).map((e,i) => {
            return {
                id: ids[i], //assign the supplied id
                type: e.type,
                property: e.property
            }
        });

        //add the selection type (e.g. Segment)
        if(useSelection && template.selectionType) {
            nestedPID[nestedPID.length -1].type.push(template.selectionType);
        }
        //add the additional types
        nestedPID[nestedPID.length -1].type.push(...additionalTypes);

        return nestedPID;
    }

    static newAnnotationFromNPID = (npid, selection, nonW3Cparams={}) => {
        let target = {
            type : npid[npid.length -1].type[0], // always use the first type for the target.source
            source : npid[npid.length -1].id,
            selector : {
                type: 'NestedPIDSelector',
                value: npid
            }
        }

        //Then assign the selection bit as a refinedBy element
        const refinedBy = selection ? MSAnnotationUtil.__generateRefinedBy(selection) : null;
        if(refinedBy) {
            target.selector.refinedBy = refinedBy;
            target.type = selection.semanticType;
        }

        //finally return the generated annotation
        return Object.assign({
            id : null, //generated on the server
            body : null,
            target : target
        }, nonW3Cparams); //merge the custom params
    }

    /*
    temporal segment looks like:

        {start: start, end: end}

    spatial segment looks like:

        {
            rect : {
                x : rect.x,
                y : rect.y,
                w : rect.width,
                h : rect.height
            },
            rotation : rect.rotation
        }
    */

    static __generateRefinedBy = selection => {
        if(!selection) return null;

        if(selection.rect && typeof(selection.rect) == 'object') { //spatial
            return {
                type: "FragmentSelector",
                conformsTo: "http://www.w3.org/TR/media-frags/",
                value: '#xywh=' + selection.rect.x + ',' + selection.rect.y + ',' + selection.rect.w + ',' + selection.rect.h,
                rect : selection.rect //TODO remove custom MS prop
            }
        } else if(selection.start >= 0 && selection.end >= 0) { //temporal
            return {
                type: "FragmentSelector",
                conformsTo: "http://www.w3.org/TR/media-frags/",
                value: '#t=' + selection.start + ',' + selection.end,
                start: selection.start, //TODO remove custom MS prop
                end: selection.end //TODO remove custom MS prop
            }
        }
        return null;
    };

    //almost as important as being able to extract & construct an NPID.
    //TODO model selections in a class
    //TODO think about multi-target annotations; is it likely there will be multiple targets with selections?
    static extractSelectionFromTarget = target => {
        //selections are ALWAYS inside the refinedBy part of the target's selector
        if(!target || !target.selector || !target.selector.refinedBy) return null;

        if(target.selector.refinedBy.start !== undefined) {
            return {
                type : 'temporal',
                start : target.selector.refinedBy.start,
                end : target.selector.refinedBy.end
            }
        } else if(target.selector.refinedBy.rect) {
            return {
                type : 'spatial',
                rect : target.selector.refinedBy.rect
            }
        } //TODO add text selection bit
    };

    /* --------------------------- MISC HELPER FUNCTIONS --------------------------- */

    //this is so components can request part of the currently active NPID, e.g.
    //"give me the ID for the currently selected Image / MediaObject / etc"
    //NB not used yet!
    static getIdFromTarget = (activeTarget, semanticType) => {
        if(!activeTarget || !semanticType) return null;
        //first find how deep the semanticType is in the pid template
        const level = activeTemplate.findIndex(ot => ot.type.indexOf(semanticType) != -1);
        //then just return the id stored in that level
        return activeTarget.ids[level];
    };

    static getLevelOfSemanticType = (npidTemplate, semanticType) => {
        return npidTemplate.findIndex(pid => pid.type.indexOf(semanticType) !== -1);
    }

    static getNPIDLength = annotation => {
        return MSAnnotationUtil.hasNPID(annotation) ? annotation.target.selector.value.length : -1;
    }

    static hasNPID = annotation => annotation && annotation.target &&
        annotation.target.selector && annotation.target.selector.type === 'NestedPIDSelector';

}

export class InformationCardUtil {

    /*
        {
            "mediaObject" : {
                "templates" : ["generic"],
                "cardsPerUser" : 10
            },
            "mediaSegment" : {
                "templates" : ["generic"],
                "cardsPerUser" : 10
            },
            "templates" : {
                "generic" : {
                    "id" : "generic",
                    "label" : "Generic",
                    "properties" : [
                        {"key" : "title", "type" : "string"},
                        {"key" : "description", "type" : "string"},
                        {"key" : "date", "type" : "date"}
                    ]
                }
            }
        }
    */

    static determinePossibleTemplates = (metadataConfig, annotation) => {
        if(!annotation.target) return null;

        if(annotation.target.selector.refinedBy && metadataConfig.mediaSegment) { //return the first template defined for media segments
            return metadataConfig.mediaSegment.templates ? metadataConfig.mediaSegment.templates.map(key => {
                return metadataConfig.templates[key];
            }) : null;

        } else if(!annotation.target.selector.refinedBy && metadataConfig.mediaObject) {
            //return the first template defined for media objects
            return metadataConfig.mediaObject.templates ? metadataConfig.mediaObject.templates.map(key => {
                return metadataConfig.templates[key];
            }) : null;
        }
        return null;
    };

    //this is for determining whether the user only able to edit a single card. If so the card list won't be dispalyed
    static determineSingleCardMode(metadataConfig, annotation) {
        if(!annotation.target) return false;
        //if there is more than one template, always return false, otherwise check whether cardsPerUser is set to 1
        if(annotation.target.selector.refinedBy && metadataConfig.mediaSegment) {
            //do this check for the media segment config
            return metadataConfig.mediaSegment.templates && metadataConfig.mediaSegment.cardsPerUser === 1;
        } else if(!annotation.target.selector.refinedBy && metadataConfig.mediaObject) {
            //do this check for the media object config
            return metadataConfig.mediaObject.templates && metadataConfig.mediaObject.cardsPerUser === 1;
        }
        return false
    }
}


export class IDUtil {

    //used to generate a more compact form for unique strings (e.g. collection names) to be used as guid
    static hashCode = s => {
      let hash = 0, i, chr, len;
      if (s.length === 0) return hash;
      for (i = 0, len = s.length; i < len; i++) {
        chr   = s.charCodeAt(i);
        hash  = ((hash << 5) - hash) + chr;
        hash |= 0; // Convert to 32bit integer
      }
      return hash;
    };

    //generates a guid from nothing
    static guid = () => {
        return IDUtil.__s4() + IDUtil.__s4() + '-' + IDUtil.__s4() + '-' +
        IDUtil.__s4() + '-' + IDUtil.__s4() + '-' + IDUtil.__s4() +
        IDUtil.__s4() + IDUtil.__s4();
    };

    //only used by the guid function
    static __s4 = () => {
        return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
    };

    //all component specific class names should be generated with this function
    //class names consist of: bg__[component-prefix]__[semantically-intelligble-component-attribute]
    static cssClassName = (componentAttribute, componentPrefix) => {
        if(componentPrefix) {
            return 'bg__' + componentPrefix + '__' + componentAttribute;
        }
        return 'bg__' + componentAttribute;
    };
}
