#!/usr/bin/env python

"""
TODO: Add tests for...
  - validate_constituency_parse()
  - validate_dependency_parss()
"""

import logging
import sys
import unittest

from testfixtures import LogCapture, StringComparison
from thrift import TSerialization

import concrete
from concrete.validate import *


class TestCommunication(unittest.TestCase):
    def test_entity_mention_ids(self):
        comm = read_test_comm()
        self.assertTrue(validate_communication(comm))
        self.assertTrue(validate_entity_mention_ids(comm))

        comm.entitySets[0].entityList[0].mentionIdList[0] = concrete.UUID(uuidString='BAD_ENTITY_MENTION_UUID')
        
        with LogCapture() as log_capture:
            self.assertFalse(validate_entity_mention_ids(comm))
        log_capture.check(('root', 'ERROR', StringComparison(r'.*invalid entityMentionId \(BAD_ENTITY_MENTION_UUID\)')))

    def test_entity_mention_tokenization(self):
        comm = read_test_comm()
        self.assertTrue(validate_communication(comm))
        self.assertTrue(validate_entity_mention_ids(comm))

        comm.entityMentionSets[0].mentionSet[0].tokens.tokenizationId = concrete.UUID(uuidString='BAD_TOKENIZATION_UUID')
        
        with LogCapture() as log_capture:
            self.assertFalse(validate_entity_mention_tokenization_ids(comm))
        log_capture.check(('root', 'ERROR', StringComparison(r'.*invalid tokenizationId \(BAD_TOKENIZATION_UUID\)')))


class TestRequiredThriftFields(unittest.TestCase):
    def test_check_required_fields(self):
        # When a field is marked as required in a .thrift file, the
        # Python code generated by the Thrift compiler only seems to
        # capture this requirement in the validate() function for the
        # generated class.  While the ThriftGeneratedClass.thrift_spec
        # structure captures the names and types of the fields,
        # thrift_spec does not seem to store any flags indicating
        # whether or not a field is required.
        #
        # Here is the validate() function for the Communication class:
        #
        #    def validate(self):
        #        if self.id is None:
        #            raise TProtocol.TProtocolException(message='Required field id is unset!')
        #        if self.uuid is None:
        #            raise TProtocol.TProtocolException(message='Required field uuid is unset!')
        #        if self.type is None:
        #            raise TProtocol.TProtocolException(message='Required field type is unset!')
        #        return
        # 
        # The validate() function raises an exception when it can't
        # find a required field.  There doesn't seem to be any way to
        # determine whether multiple required fields are missing,
        # aside from assigning a value to the required field and
        # running validate() again.

        comm = concrete.Communication()

        with LogCapture() as log_capture:
            self.assertFalse(validate_thrift_object_required_fields_recursively(comm))
        log_capture.check(('root', 'ERROR', "Communication: Required Field 'id' is unset!"))

        comm.id = "ID"
        with LogCapture() as log_capture:
            self.assertFalse(validate_thrift_object_required_fields_recursively(comm))
        log_capture.check(('root', 'ERROR', "Communication: Required Field 'uuid' is unset!"))

        comm.uuid = concrete.UUID(uuidString="TEST_UUID")
        with LogCapture() as log_capture:
            self.assertFalse(validate_thrift_object_required_fields_recursively(comm))
        log_capture.check(('root', 'ERROR', "Communication 'TEST_UUID': Required Field 'type' is unset!"))

        comm.type = "OTHER"
        self.assertTrue(validate_thrift_object_required_fields_recursively(comm))


class TestTextspanOffsets(unittest.TestCase):
    def test_validate_token_offsets_for_good_sentence(self):
        sentence = self.create_sentence_with_token(0, 30, 0, 10)
        self.assertTrue(validate_token_offsets_for_sentence(sentence))

    def test_validate_token_offsets_for_sentence_with_reversed_sentence_offsets(self):
        sentence = self.create_sentence_with_token(30, 0, 0, 10)
        with LogCapture() as log_capture:
            self.assertFalse(validate_token_offsets_for_sentence(sentence))

    def test_validate_token_offsets_for_sentence_with_reversed_token_offsets(self):
        sentence = self.create_sentence_with_token(0, 30, 10, 0)
        with LogCapture() as log_capture:
            self.assertFalse(validate_token_offsets_for_sentence(sentence))

    def test_validate_token_offsets_for_sentence_with_token_not_fully_in_sentence(self):
        sentence = self.create_sentence_with_token(0, 30, 25, 35)
        with LogCapture() as log_capture:
            self.assertFalse(validate_token_offsets_for_sentence(sentence))


    def test_validate_token_offsets_for_good_section(self):
        section = self.create_section_with_sentence(0, 30, 0, 10)
        self.assertTrue(validate_token_offsets_for_section(section))

    def test_validate_token_offsets_for_section_with_reversed_section_offsets(self):
        section = self.create_section_with_sentence(30, 0, 0, 10)
        with LogCapture() as log_capture:
            self.assertFalse(validate_token_offsets_for_section(section))

    def test_validate_token_offsets_for_section_with_reversed_token_offsets(self):
        section = self.create_section_with_sentence(0, 30, 10, 0)
        with LogCapture() as log_capture:
            self.assertFalse(validate_token_offsets_for_section(section))

    def test_validate_token_offsets_for_section_with_sentence_not_fully_in_section(self):
        section = self.create_section_with_sentence(0, 30, 25, 35)
        with LogCapture() as log_capture:
            self.assertFalse(validate_token_offsets_for_section(section))

    def test_validate_token_offsets_for_real_example_section_data(self):
        section = self.create_section_with_sentence(55, 296, 0, 118)
        with LogCapture() as log_capture:
            self.assertFalse(validate_token_offsets_for_section(section))


    def create_section_with_sentence(self, section_start, section_ending, sentence_start, sentence_ending):
        sentence_textspan = concrete.spans.ttypes.TextSpan(start=sentence_start, ending=sentence_ending)
        sentence = concrete.structure.ttypes.Sentence(textSpan=sentence_textspan, uuid='TEST_SENTENCE')
        sentence_segmentation = concrete.structure.ttypes.SentenceSegmentation(sentenceList=[sentence])
        section_textspan = concrete.spans.ttypes.TextSpan(start=section_start, ending=section_ending)
        section = concrete.structure.ttypes.Section(
            sentenceSegmentation=[sentence_segmentation],
            textSpan=section_textspan,
            uuid='TEST_SECTION')
        return section

    def create_sentence_with_token(self, sentence_start, sentence_ending, token_start, token_ending):
        token_textspan = concrete.spans.ttypes.TextSpan(start=token_start, ending=token_ending)
        token = concrete.structure.ttypes.Token(textSpan=token_textspan)
        tokenization = concrete.structure.ttypes.Tokenization(tokenList=concrete.TokenList(tokens=[token]))
        sentence_textspan = concrete.spans.ttypes.TextSpan(start=sentence_start, ending=sentence_ending)
        sentence = concrete.structure.ttypes.Sentence(
            tokenizationList=[tokenization],
            textSpan=sentence_textspan,
            uuid='TEST')
        return sentence


def read_test_comm():
    communication_filename = "tests/testdata/serif_dog-bites-man.concrete"
    comm = concrete.Communication()
    comm_bytestring = open(communication_filename).read()
    TSerialization.deserialize(comm, comm_bytestring)
    return comm


if __name__ == '__main__':
    unittest.main(buffer=True)
