/**
 * Title       : Schema for JSON Topic Maps 1.0
 * Author      : Robert Cerny
 * Created     : 2009-01-07
 * Last Change : 2010-10-15
 */

CERNY.require("JTM.v1_0",
              "CERNY.schema");

if (typeof JTM == "undefined") {
    JTM = {};
}

(function() {

    JTM.v1_0 = v1_0;

    var optional = CERNY.schema.optional;
    var arrayOf = CERNY.schema.arrayOf;

    /**
     * This function creates an object holding schemas for all constructs.
     */
    function v1_0(params) {

        params = params || {};
        if (!isBoolean(params.checkRules)) {
            params.checkRules = false;
        }

        function isNonEmptyString(x) {
            return !isNull(x) && isString(x) && x.trim().length > 0;
        }

        function isItemReference(x) {
            if (isNonEmptyString(x) && x.match(/^ii:/)) {
                return true;
            } else {
                return "is not an item reference: '" + x + "'";
            }
        }

        function isTopicReference(x) {
            if (isNonEmptyString(x) && x.match(/^(ii|si|sl):/)) {
                return true;
            } else {
                return "is not a topic reference: '" + x + "'";
            }
        }
        v1_0.isTopicReference = isTopicReference;

        var isUri = isNonEmptyString;
        var isLocator = isUri;

        function identifiable(obj) {
            obj.item_identifiers = optional(arrayOf(isLocator));
        }

        function reifiable(obj) {
            obj.reifier = optional(isTopicReference);
        }

        function scopeable(obj) {
            obj.scope = optional(arrayOf(isTopicReference));
        }

        function versionable(obj) {
            obj.version = function(x) {
                if (this._parent == null) {
                    if (x != "1.0") {
                        return "must be '1.0' on the root item";
                    }
                } else {
                    if (typeof x != "undefined") {
                        return "is only allowed on the root item";
                    }
                }
            };
        }

        var Variant= {
            value: isString,
            datatype: optional(isString),
            scope: arrayOf(isTopicReference, 1)
        };
        identifiable(Variant);
        reifiable(Variant);
        versionable(Variant);

        var Name = {
            value: isString,
            type: optional(isTopicReference),
            variants: optional(arrayOf(Variant))
        };
        identifiable(Name);
        reifiable(Name);
        scopeable(Name);
        versionable(Name);

        var Occurrence = {
            value: isString,
            type: isTopicReference,
            datatype: optional(isString)
        };
        identifiable(Occurrence);
        reifiable(Occurrence);
        scopeable(Occurrence);
        versionable(Occurrence);

        var Topic =  {
            names: optional(arrayOf(Name)),
            occurrences: optional(arrayOf(Occurrence)),
            subject_identifiers: optional(arrayOf(isLocator)),
            subject_locators: optional(arrayOf(isLocator))
        };
        identifiable(Topic);
        versionable(Topic);

        var Role = {
            type: isTopicReference,
            player: isTopicReference
        };
        identifiable(Role);
        reifiable(Role);
        versionable(Role);

        var Association = {
            type: isTopicReference,
            roles: arrayOf(Role, 1)
        };
        identifiable(Association);
        reifiable(Association);
        scopeable(Association);
        versionable(Association);

        var TopicMap = {
            topics: optional(arrayOf(Topic)),
            associations: optional(arrayOf(Association))
        };
        identifiable(TopicMap);
        reifiable(TopicMap);

        function makeDocumentSchema(schema, name) {
            var documentSchema = CERNY.clone(schema);

            // Add version
            documentSchema.version = "1.0";

            // Add item_type
            documentSchema.item_type = function(value) {
                if (typeof value == "string" && value.toLowerCase() == name.toLowerCase()) {
                    return true;
                }
                return "must be '" + name + "'";
            };

            // Maybe add parent
            switch (schema) {
            case Topic:
            case Association:
            case Role:
            case Variant:
                documentSchema.parent = optional(arrayOf(isItemReference, 1));
                break;

            case Occurrence:
            case Name:
                documentSchema.parent = optional(arrayOf(isTopicReference, 1));
                break;

            default:
            }
            return documentSchema;
        }

        function rules() {

            function reifierRule(obj) {
                var old = obj.reifier;
                obj.reifier = function(x) {
                    if (x === null) {
                        return "should be ommitted because it is null";
                    } else {
                        return old.call(this, x);
                    }
                }
            }
            reifierRule(Name);
            reifierRule(Occurrence);
            reifierRule(Role);
            reifierRule(Association);
            reifierRule(Variant);
            reifierRule(TopicMap);

            function datatypeStringRule(obj) {
                var old = obj.datatype;
                obj.datatype = function(x) {
                    if (x === "http://www.w3.org/2001/XMLSchema#string") {
                        return "should be ommitted because it is 'http://www.w3.org/2001/XMLSchema#string'";
                    } else {
                        return old.call(this, x);
                    }
                }
            }
            datatypeStringRule(Occurrence);
            datatypeStringRule(Variant);

            function emptyArrayRule(obj, memberName) {
                var old = obj[memberName];
                obj[memberName] = function(x) {
                    if (isArray(x) && x.length === 0) {
                        return "should be omitted because array is empty";
                    } else {
                        return old.call(this, x);
                    }
                }
            }

            function defaultNameTypeRule(obj) {
                var old = obj.type;
                obj.type = function(x) {
                    if (x === "si:http://psi.topicmaps.org/iso13250/model/topic-name") {
                        return "should be ommitted because it is the default name type";
                    } else {
                        return old.call(this, x);
                    }
                }
            }
            defaultNameTypeRule(Name);

            emptyArrayRule(TopicMap, "item_identifiers");
            emptyArrayRule(TopicMap, "topics");
            emptyArrayRule(TopicMap, "associations");

            emptyArrayRule(Topic, "item_identifiers");
            emptyArrayRule(Topic, "subject_identifiers");
            emptyArrayRule(Topic, "subject_locators");
            emptyArrayRule(Topic, "names");
            emptyArrayRule(Topic, "occurrences");

            emptyArrayRule(Name, "item_identifiers");
            emptyArrayRule(Name, "variants");
            emptyArrayRule(Name, "scope");

            emptyArrayRule(Variant, "item_identifiers");

            emptyArrayRule(Occurrence, "item_identifiers");
            emptyArrayRule(Occurrence, "scope");

            emptyArrayRule(Association, "item_identifiers");
            emptyArrayRule(Association, "scope");

            emptyArrayRule(Role, "item_identifiers");
        };

        if (params.checkRules) {
            rules();
        }

        return {
            TopicMap: makeDocumentSchema(TopicMap, "topicmap"),
            Topic: makeDocumentSchema(Topic, "topic"),
            Name: makeDocumentSchema(Name, "name"),
            Variant: makeDocumentSchema(Variant, "variant"),
            Occurrence: makeDocumentSchema(Occurrence, "occurrence"),
            Association: makeDocumentSchema(Association, "association"),
            Role: makeDocumentSchema(Role, "role")
        };
    };


})();

