var coding = require('./coding');
var util = require('../util');
var _ = require('lodash');

var assert = {
    isResourceType: function (type) {
        if (!type) {
            throw new Error('type undefined')
        }

        return function (entry) {
            if (!entry) {
                return false;
            }

            if (!entry.content) {
                return false;
            }

            return entry.content.resourceType === type;
        };
    },
    hasCode: function (codesToMatch, matchFunc) {
        var codes = codesToMatch || [];

        var matchesCode;
        if (matchFunc && _.isFunction(matchFunc)) {
            matchesCode = matchFunc(codes);
        }
        else {
            matchesCode = coding.includesCodeFromSet(codes);
        }

        return function (entry) {
            if (_.has(entry, 'content.modifierExtension[0]')) {
                throw new Error('Resource contains modifierExtension');
            }

            try {
                var candidates = [];

                //Codes from body of resource
                var codingFromResource = util.entry.coding(entry);
                if (codingFromResource) {
                    candidates.push(codingFromResource);
                }

                //Codes in extensions
                if (_.has(entry, 'content.extension')) {
                    var codeableConcepts = entry.content.extension
                        .filter(function (extension) {
                            return extension.valueCodeableConcept !== undefined;
                        })
                        .map(function (extension) {
                            return extension.valueCodeableConcept.coding;
                        });

                    var coding = entry.content.extension
                        .filter(function (extension) {
                            return extension.valueCoding !== undefined;
                        })
                        .map(function (extension) {
                            return extension.valueCoding;
                        });

                    var code = entry.content.extension
                        .filter(function (extension) {
                            return extension.valueCode !== undefined;
                        })
                        .map(function (extension) {
                            return [extension.valueCode];
                        });

                    candidates = candidates.concat(codeableConcepts, coding, code);
                }

                return candidates.some(matchesCode);
            }
            catch (err) {
                return false;
            }
        };
    },
    isCodeable: function(){
        return function (entry) {
            return util.entry.isCodeable(entry);
        };
    },
    isCompositionOfType: function (type) {
        var isComposition = assert.isResourceType('Composition');

        return function (entry) {
            if (!isComposition(entry)) {
                return false;
            }
            return _.has(entry, 'content.type.coding[0].code') ? entry.content.type.coding[0].code === type : false;
        };
    },
    isDocumentOfType: function (type) {
        var isDocument = function (entry) {
            return _.has(entry, 'content.type') && entry.content.type === 'document';
        };

        return function (entry) {
            if (!isDocument(entry) || !_.has(entry, 'content.entry[0]')) {
                return false;
            }
            var isDocumentOfType = false;
            var isCompositionOfType = assert.isCompositionOfType(type);
            _.forEach(entry.content.entry, function (subEntry) {
                if (isCompositionOfType(subEntry)) {
                    isDocumentOfType = true;
                    return false;
                }
            });
            return isDocumentOfType;
        };
    },
    hasEffectiveDate: function (date, options) {
        if (!date) {
            throw new Error('date undefined');
        }

        options = options || {};
        options.tolerance = options.tolerance || {};
        options.tolerance.ticksBefore = options.tolerance.ticksBefore || 0;
        options.tolerance.ticksAfter = options.tolerance.ticksAfter || 0;

        function testMatch(actual, date) {
            return actual.toISOString().indexOf(date) === 0;
        }

        return function (entry) {
            if (!entry) {
                return false;
            }

            if (!entry.content) {
                return false;
            }

            var actual = util.entry.effectiveDate(entry);
            if (!actual){
                return false;
            }

            var match = testMatch(actual, date);

            if (!match) {
                //check for matches within tolerance range before day
                if (options.tolerance.ticksBefore > 0) {
                    var actualLow = new Date(actual.getTime() + options.tolerance.ticksBefore);
                    match = testMatch(actualLow, date);
                }
            }

            return match;
        };
    },
    isWithin: function (days, refDate) {
        days = days || 60;

        return function (entry) {
            refDate = refDate || new Date();
            var cutoffDate = new Date(refDate - (days * 24 * 60 * 60 * 1000));

            if (!entry) {
                return false;
            }

            if (!entry.content) {
                return false;
            }

            var actual = util.entry.effectiveDate(entry);

            if (!actual) {
                return false;
            }

            return actual > cutoffDate;
        }
    }
};

module.exports = assert;