var entry = require('../../lib/assert/entry');

var should = require('chai').should();

describe('assert.entry', function () {


    describe('isResourceType', function () {
        it('should throw exception if type undefined', function () {
            should.Throw(function () {
                entry.isResourceType();
            });
        });

        it('should return a function', function () {
            var result = entry.isResourceType('Foo');

            should.exist(result);
            result.should.be.a('Function');
        });

        it('should return false when entry undefined', function () {
            var fn = entry.isResourceType('Foo');
            var result = fn();

            should.exist(result);
            result.should.equal(false);
        });

        it('should return false when entry.content undefined', function () {
            var candidate = {
                resourceType: 'Foo'
            };

            var fn = entry.isResourceType('Foo');
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });

        it('should return false when entry does not match resource type', function () {
            var candidate = {
                content: {
                    resourceType: 'Foo'
                }
            };

            var fn = entry.isResourceType('Bar');
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });

        it('should return true when entry matches resource type', function () {
            var candidate = {
                content: {
                    resourceType: 'Foo'
                }
            };

            var fn = entry.isResourceType('Foo');
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(true);
        });
    });

    describe('isCodeable', function () {
        var fn = entry.isCodeable;

        it('should return true when resource could be coded', function () {
            var candidate = {
                content: {
                    resourceType: 'AllergyIntolerance'
                }
            };

            var result = fn()(candidate);

            should.exist(result);
            result.should.equal(true);
        });

        it('should return false when resource cannot be coded', function () {
            var candidate = {
                content: {
                    resourceType: 'Location'
                }
            };

            var result = fn()(candidate);

            should.exist(result);
            result.should.equal(false);
        });
    });

    describe('hasCode', function () {
        var fn = entry.hasCode;

        function testCase(description, fn, codes, candidate, expected) {
            var message = 'when ' + description;
            it(message, function () {
                var result = fn(codes)(candidate);

                should.exist(result);
                result.should.equal(expected);
            });
        }

        it('should return a function', function () {
            var result = fn();

            should.exist(result);
            result.should.be.a('Function');
        });

        var candidateNoContent = {};
        var candidateNoCodeableConcept = {
            content: {
                resourceType: 'Observation'
            }
        };
        var candidateAllergyIntolerance = {
            content: {
                resourceType: 'AllergyIntolerance',
                substance: {
                    coding: [
                        {
                            system: 'READ2',
                            code: 'XYZ..',
                            display: 'Matched concept from target system'
                        },
                        {
                            system: 'SNOMED',
                            code: '169461009',
                            display: 'Concept from alternative system'
                        }
                    ]
                }
            }
        };
        var candidateCondition = {
            content: {
                resourceType: 'Condition',
                code: {
                    coding: [
                        {
                            system: 'READ2',
                            code: 'XYZ..',
                            display: 'Matched concept from target system'
                        },
                        {
                            system: 'SNOMED',
                            code: '169461009',
                            display: 'Concept from alternative system'
                        }
                    ]
                }
            }
        };
        var candidateEncounter = {
            content: {
                resourceType: 'Encounter',
                reason: {
                    coding: [
                        {
                            system: 'READ2',
                            code: 'XYZ..',
                            display: 'Matched concept from target system'
                        },
                        {
                            system: 'SNOMED',
                            code: '169461009',
                            display: 'Concept from alternative system'
                        }
                    ]
                }
            }
        };
        var candidateImmunization = {
            content: {
                resourceType: 'Immunization',
                vaccineType: {
                    coding: [
                        {
                            system: 'READ2',
                            code: 'XYZ..',
                            display: 'Matched concept from target system'
                        },
                        {
                            system: 'SNOMED',
                            code: '169461009',
                            display: 'Concept from alternative system'
                        }
                    ]
                }
            }
        };
        var candidateMedication = {
            content: {
                resourceType: 'Medication',
                code: {
                    coding: [
                        {
                            system: 'READ2',
                            code: 'XYZ..',
                            display: 'Matched concept from target system'
                        },
                        {
                            system: 'SNOMED',
                            code: '169461009',
                            display: 'Concept from alternative system'
                        }
                    ]
                }
            }
        };
        var candidateMedicationUncoded = {
            content: {
                resourceType: 'Medication'
            }
        };
        var candidateObservation = {
            content: {
                resourceType: 'Observation',
                name: {
                    coding: [
                        {
                            system: 'READ2',
                            code: 'XYZ..',
                            display: 'Matched concept from target system'
                        },
                        {
                            system: 'SNOMED',
                            code: '169461009',
                            display: 'Concept from alternative system'
                        }
                    ]
                }
            }
        };
        var candidateAllergyIntoleranceWithExtensions = {
            content: {
                resourceType: 'AllergyIntolerance',
                extension: [
                    {
                        valueCodeableConcept: {
                            coding: [
                                {
                                    system: 'READ2',
                                    code: 'EX_CC',
                                    display: 'Matched CodeableConcept extension from target system'
                                }
                            ]
                        }
                    },
                    {
                        valueCoding: [
                            {
                                system: 'READ2',
                                code: 'EX_CD',
                                display: 'Matched Coding extension from target system'
                            }
                        ]
                    },
                    {
                        valueCode:
                        {
                            system: 'READ2',
                            code: 'EX_C',
                            display: 'Matched code extension from target system'
                        }
                    },
                    {
                        valueString: 'Uncoded extension'
                    }
                ],
                substance: {
                    coding: [
                        {
                            system: 'READ2',
                            code: 'XYZ..',
                            display: 'Matched concept from target system'
                        },
                        {
                            system: 'SNOMED',
                            code: '169461009',
                            display: 'Concept from alternative system'
                        }
                    ]
                }
            }
        };
        var candidates = [
            candidateAllergyIntolerance,
            candidateCondition,
            candidateEncounter,
            candidateMedication,
            candidateImmunization,
            candidateObservation
        ];

        testCase('entry undefined', fn, [], undefined, false);
        testCase('codes undefined', fn, undefined, candidateObservation, false);
        testCase('entry.content undefined', fn, [], candidateNoContent, false);
        testCase('entry CodeableConcept undefined', fn, [], candidateNoCodeableConcept, false);

        describe('should return false', function () {
            var unmatchedCodes = [
                {system: 'READ2', code: 'ABCD.'},
                {system: 'READ2', code: 'EFGH.'}
            ];

            var unmatchedCodesByRegex = [
                {system: 'READ2', code: /^ABCD/},
                {system: 'READ2', code: /^EFG/}
            ];

            candidates.forEach(function (candidate) {
                testCase(candidate.content.resourceType + ' does not match by exact code', fn, unmatchedCodes, candidate, false);
                testCase(candidate.content.resourceType + ' does not match by regex', fn, unmatchedCodesByRegex, candidate, false);
            });

            testCase('resource is uncoded', fn, unmatchedCodes, candidateMedicationUncoded, false);
        });

        describe('should return true', function () {
            candidates.forEach(function (candidate) {
                var matchedCodes = [
                    {system: 'READ2', code: 'ABCD.'},
                    {system: 'READ2', code: 'XYZ..'}
                ];

                var matchedCodesByRegex = [
                    {system: 'READ2', code: /^ABCD/},
                    {system: 'READ2', code: /^XYZ/}
                ];

                testCase(candidate.content.resourceType + ' matches exact code', fn, matchedCodes, candidate, true);
                testCase(candidate.content.resourceType + ' matches by regex', fn, matchedCodesByRegex, candidate, true);
            });

            var matchedCodesInCodeableConceptExtension = [
                {system: 'READ2', code: 'EX_CC'}
            ];

            var matchedCodesInCodingExtension = [
                {system: 'READ2', code: 'EX_CD'}
            ];

            var matchedCodesInCodeExtension = [
                {system: 'READ2', code: 'EX_C'}
            ];

            testCase('matches code in CodeableConcept extension', fn, matchedCodesInCodeableConceptExtension, candidateAllergyIntoleranceWithExtensions, true);
            testCase('matches code in Coding extension', fn, matchedCodesInCodingExtension, candidateAllergyIntoleranceWithExtensions, true);
            testCase('matches code in Code extension', fn, matchedCodesInCodeExtension, candidateAllergyIntoleranceWithExtensions, true);

            var matchAny = [
                {code: /^.*$/}
            ];

            testCase('matches any code', fn, matchAny, candidateAllergyIntoleranceWithExtensions, true);
        });

        it('should support alternative code search function', function () {
            var matchedCodes = [
                {system: 'READ2', code: 'ABCD.'},
                {system: 'READ2', code: 'XYZ..'}
            ];

            var searchFunc = function(codesToTest) {
                return function(coding) {
                    var codeFound = false;
                    coding.forEach(function(candidate) {
                        for(var ind=0; ind < codesToTest.length; ind++)
                        {
                            if ((candidate.system === codesToTest[ind].system) &&
                                (candidate.code === codesToTest[ind].code))
                                codeFound = true;
                        }
                    });
                    return codeFound;
                };
            };

            var result = fn(matchedCodes, searchFunc)(candidateCondition);

            should.exist(result);
            result.should.equal(true);
        });
    });

    describe('isCompositionOfType', function () {
        it('should return a function', function () {
            var result = entry.isCompositionOfType();

            should.exist(result);
            result.should.be.a('Function');
        });
        it('should return false when entry undefined', function () {
            var fn = entry.isCompositionOfType();
            var result = fn();

            should.exist(result);
            result.should.equal(false);
        });
        it('should return false when entry.content undefined', function () {
            var candidate = {
                content: {
                    resourceType: 'Composition'
                }
            };

            var fn = entry.isCompositionOfType();
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });
        it('should return false when entry is not a composition', function () {
            var candidate = {
                content: {
                    resourceType: 'Wrong thing',
                    type: {
                        coding: [
                            {
                                code: 'test_code'
                            }
                        ]
                    }
                }
            };
            var fn = entry.isCompositionOfType('test_code');
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });
        it('should return true when the entry contains a matching composition', function () {
            var candidate = {
                content: {
                    resourceType: 'Composition',
                    type: {
                        coding: [
                            {
                                code: 'test_code'
                            }
                        ]
                    }
                }
            };
            var fn = entry.isCompositionOfType('test_code');
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(true);
        });
        it('should return false when the entry does not contain a matching composition', function () {
            var candidate = {
                content: {
                    resourceType: 'Composition',
                    type: {
                        coding: [
                            {
                                code: 'test_code'
                            }
                        ]
                    }
                }
            };
            var fn = entry.isCompositionOfType('wrong_code');
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });
    });

    describe('isDocumentOfType', function () {
        it('should return a function', function () {
            var result = entry.isDocumentOfType();

            should.exist(result);
            result.should.be.a('Function');
        });

        it('should return false when entry undefined', function () {
            var fn = entry.isDocumentOfType();
            var result = fn();

            should.exist(result);
            result.should.equal(false);
        });

        it('should return false when entry.content undefined', function () {
            var candidate = {
                resourceType: 'Document'
            };

            var fn = entry.isDocumentOfType();
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });
        it('should return false when entry is not a document', function () {
            var candidate = {
                content: {
                    type: 'something else',
                    entry: [
                        {
                            content: {
                                resourceType: 'Composition',
                                type: {
                                    coding: [
                                        {
                                            code: 'test_code'
                                        }
                                    ]
                                }
                            }
                        }
                    ]
                }
            };

            var fn = entry.isDocumentOfType('test_code');
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });
        it('should return true when entry is a document with a matching composition type', function () {
            var candidate = {
                content: {
                    type: 'document',
                    entry: [
                        {
                            content: {
                                data: 'some data'
                            }
                        },
                        {
                            content: {
                                resourceType: 'Composition',
                                type: {
                                    coding: [
                                        {
                                            code: 'test_code'
                                        }
                                    ]
                                }
                            }
                        }
                    ]
                }
            };

            var fn = entry.isDocumentOfType('test_code');
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(true);
        });
        it('should return false when entry is a document with a non-matching composition type', function () {
            var candidate = {
                content: {
                    type: 'document',
                    entry: [
                        {
                            content: {
                                data: 'some data'
                            }
                        },
                        {
                            content: {
                                resourceType: 'Composition',
                                type: {
                                    coding: [
                                        {
                                            code: 'test_code'
                                        }
                                    ]
                                }
                            }
                        }
                    ]
                }
            };

            var fn = entry.isDocumentOfType('another_code');
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });

    });

    describe('hasEffectiveDate', function () {
        it('should throw exception if date undefined', function () {
            should.Throw(function () {
                entry.hasEffectiveDate();
            });
        });

        it('should return a function', function () {
            var result = entry.hasEffectiveDate('2008-04-10');

            should.exist(result);
            result.should.be.a('Function');
        });

        it('should return false when entry undefined', function () {
            var fn = entry.hasEffectiveDate('2008-04-10');
            var result = fn();

            should.exist(result);
            result.should.equal(false);
        });

        it('should return false when entry.content undefined', function () {
            var candidate = {
                resourceType: 'Foo',
                date: '2008-04-10'
            };

            var fn = entry.hasEffectiveDate('2008-04-10');
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });

        it('should return false when entry.content has no date', function () {
            var candidate = {
                content: {
                    resourceType: 'Medication'
                }
            };

            var fn = entry.hasEffectiveDate('2008-04-10');
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });

        it('should return false when entry date does not match effective date', function () {
            var candidate = {
                content: {
                    resourceType: 'Immunization',
                    date: '2008-04-11'
                }
            };

            var fn = entry.hasEffectiveDate('2008-04-10');
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });

        it('should return true when entry date exactly matches effective date', function () {
            var candidate = {
                content: {
                    resourceType: 'Immunization',
                    date: '2008-04-10'
                }
            };

            var fn = entry.hasEffectiveDate('2008-04-10');
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(true);
        });

        it('should return true when entry date partially matches effective date', function () {
            var candidate = {
                content: {
                    resourceType: 'Immunization',
                    date: '2008-04-10'
                }
            };

            var fn = entry.hasEffectiveDate('2008-04');
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(true);
        });

        describe('with tolerance adjustment', function () {
            it('should return true when entry date is within tolerance before effective date', function () {
                var candidate = {
                    content: {
                        resourceType: 'Immunization',
                        date: '2008-04-01T23:00:00.000Z'
                    }
                };

                var options = {
                    tolerance: {
                        ticksBefore: 3600000,
                        ticksAfter: 0
                    }
                };

                var fn = entry.hasEffectiveDate('2008-04-02', options);
                var result = fn(candidate);

                should.exist(result);
                result.should.equal(true);
            });
        });
    });

    describe('isWithin', function () {
        var refDate = new Date('2015-03-03');

        it('should return a function', function () {
            var result = entry.isWithin(60);

            should.exist(result);
            result.should.be.a('Function');
        });

        it('should return false when entry undefined', function () {
            var candidate = undefined;

            var fn = entry.isWithin(60, refDate);
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });

        it('should return false when entry.content undefined', function () {
            var candidate = {};

            var fn = entry.isWithin(60, refDate);
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });

        it('should return false when entry.content has no effective date', function () {
            var candidate = {
                content: {
                    resourceType: 'Immunization'
                }
            };

            var fn = entry.isWithin(60, refDate);
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });

        it('should return true when entry date is within date range', function () {
            var candidate = {
                content: {
                    resourceType: 'Immunization',
                    date: '2015-01-10'
                }
            };

            var fn = entry.isWithin(60, refDate);
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(true);
        });

        it('should return false when entry date is outside date range', function () {
            var candidate = {
                content: {
                    resourceType: 'Immunization',
                    date: '2008-04-10'
                }
            };

            var fn = entry.isWithin(60, refDate);
            var result = fn(candidate);

            should.exist(result);
            result.should.equal(false);
        });
    });
});