#! /usr/bin/env python
"""This module implements test to check against the IMSCC Profile 1.0 specification defined by IMS GLC"""

import unittest
from types import StringTypes
import pyslet.imscpv1p2 as imscp
import os.path

CCOrganizationStructure = "rooted-hierarchy"
CartridgeWebContentType = "webcontent"
AssociatedContentType = "associatedcontent/imscc_xmlv1p0/learning-application-resource"
DiscussionTopicContentType = "imsdt_xmlv1p0"
WebLinkContentType = "imswl_xmlv1p0"
AssessmentContentType = "imsqti_xmlv1p2/imscc_xmlv1p0/assessment"
QuestionBankContentType = "imsqti_xmlv1p2/imscc_xmlv1p0/question-bank"


class CommonCartridge:

    ContentPackageClass = imscp.ContentPackage

    def __init__(self, src=None):
        if type(src) in StringTypes:
            self.cp = self.ContentPackageClass(src)
        elif isinstance(src, self.ContentPackageClass):
            self.cp = src
        elif src is None:
            self.cp = self.ContentPackageClass()
        else:
            raise ValueError
        self.ScanResources()

    def ScanResources(self):
        """Scans the content package and builds lists of resources.

        Resources in Common Cartridge are either Cartridge Web Content resources,
        Learning Application Object (LAO) resources or an LAO's Associated Content
        resources.  Any other resources are treated as passengers."""
        self.cwcList = []
        self.laoTable = {}
        resources = self.cp.manifest.root.Resources
        # First pass is a search for CWCs and LAOs
        for r in resources.Resource:
            rType = r.type
            if rType == AssociatedContentType:
                # Associated content is linked from the LAO later
                continue
            if rType == CartridgeWebContentType:
                self.cwcList.append(r)
                continue
            # All other resoure types are treated as LAOs
            if len(r.File) >= 1:
                laoDescriptor = r.File[0]
                fPath = laoDescriptor.PackagePath(self.cp)
                head, tail = fPath.split()
                if not head:
                    # The LAO must be in a directory, not at the top-level of
                    # the CP
                    head = None
            else:
                head = None
            # Is there associated content?
            acr = None
            depList = r.Dependency
            for dep in depList:
                rDep = self.cp.manifest.GetElementByID(dep.identifierref)
                if isinstance(rDep, imscp.Resource) and rDep.type == AssociatedContentType:
                    acr = rDep
                    break
            self.laoTable[r.id] = [head, acr]

    def Close(self):
        if self.cp:
            self.cp.Close()
        self.cp = None


class CCTestSuite(unittest.TestSuite):

    def __init__(self, cp):
        """This test suite requires an IMS Content Package to test"""
        unittest.TestSuite.__init__(self)
        loader = unittest.TestLoader()
        for tName in loader.getTestCaseNames(CCTestCase):
            self.addTest(CCTestCase(cp, tName))


class CCTestCase(unittest.TestCase):

    def __init__(self, cc, method):
        """We initialise the test case with the Common Cartridge being
        tested and the name of the method to call."""
        unittest.TestCase.__init__(self, method)
        self.cc = cc

    def test1_4_AssociatedContent_1(self):
        """A resource of the type associatedcontent must ...
        1. ...contain a file element for each file that exists in the directory
        that contains the associated Learning Application Object's descriptor
        file or any of its subdirectories."""
        for lao in self.cc.laoTable.keys():
            laoResource = self.cc.cp.manifest.GetElementByID(lao)
            dPath, acr = self.cc.laoTable[lao]
            # acr must have a file element for all items in dPath
            fPaths = self.cc.cp.fileTable.keys()
            fPaths.sort()
            for fPath in fPaths:
                if imscp.PathInPath(fPath, dPath):
                    fList = self.cc.cp.fileTable[fPath]
                    foundResource = False
                    for f in fList:
                        if f.parent is acr:
                            foundResource = True
                            break
                        elif f.parent is laoResource:
                            foundResource = True
                            break
                    if not foundResource:
                        self.fail(fPath)

    def test1_4_AssociatedContent_2(self):
        """A resource of the type associatedcontent must ...
        2. It must not contain any references to files above the directory
        containing the associated Learning Application Object's descriptor file."""
        for lao in self.cc.laoTable.keys():
            dPath, acr = self.cc.laoTable[lao]
            if acr is None:
                continue
            for f in acr.File:
                fPath = f.PackagePath(self.cc.cp)
                self.assertTrue(imscp.PathInPath(fPath, dPath))

    def test1_4_AssociatedContent_3(self):
        """A resource of the type associatedcontent must ...
        3. It must not contain any dependency elements."""
        for lao in self.cc.laoTable.keys():
            dPath, acr = self.cc.laoTable[lao]
            if acr is None:
                continue
            self.assertFalse(len(acr.Dependency) > 0, acr.id)

    def test1_4_LAO_1(self):
        """A resource that represents a Learning Application Object has the
        following general restrictions:...
        1. It must contain a file element that points to the Learning
        Application Object's descriptor file."""
        for lao in self.cc.laoTable.keys():
            laoResource = self.cc.cp.manifest.GetElementByID(lao)
            self.assertFalse(len(laoResource.File) == 0)

    def test1_4_LAO_2(self):
        """A resource that represents a Learning Application Object has the
        following general restrictions:...
        2. It must not contain any other file elements."""
        for lao in self.cc.laoTable.keys():
            laoResource = self.cc.cp.manifest.GetElementByID(lao)
            self.assertFalse(len(laoResource.File) > 1)

    def GetACRListForDirectory(self, dPath):
        """Returns a list of associated content resources that have file's
        that reside in dPath.  (Used internally.)"""
        acrList = []
        fPaths = self.cc.cp.fileTable.keys()
        fPaths.sort()
        for fPath in fPaths:
            if imscp.PathInPath(fPath, dPath):
                fList = self.cc.cp.fileTable[fPath]
                foundResource = False
                for f in fList:
                    if f.parent.type != AssociatedContentType:
                        continue
                    if f.parent in acrList:
                        continue
                    acrList.append(f.parent)
        return acrList

    def test1_4_LAO_3(self):
        """A resource that represents a Learning Application Object has the
        following general restrictions:...
        3. If additional files exist in the directory containing the Learning
        Application Object's descriptor file, or any of its subdirectories, the
        resource must contain a dependency element that references the resource
        of type 'associatedcontent' which contains the references to these
        files."""
        for lao in self.cc.laoTable.keys():
            laoResource = self.cc.cp.manifest.GetElementByID(lao)
            dPath, acr = self.cc.laoTable[lao]
            if dPath is None:  # this is a fail of a different sort
                continue
            acrList = self.GetACRListForDirectory(dPath)
            # Now we must have a dependency for each element of acrList
            for d in laoResource.Dependency:
                acr = self.cc.cp.manifest.GetElementByID(d.identifierref)
                if acr in acrList:
                    del acrList[acrList.index(acr)]
            self.assertFalse(len(acrList))

    def test1_4_LAO_4(self):
        """A resource that represents a Learning Application Object has the
        following general restrictions:...
        4. It must not contain any other dependency elements of type
        'associatedcontent'."""
        for lao in self.cc.laoTable.keys():
            laoResource = self.cc.cp.manifest.GetElementByID(lao)
            dPath, acr = self.cc.laoTable[lao]
            if dPath is None:  # this is a fail of a different sort
                continue
            acrList = self.GetACRListForDirectory(dPath)
            # The use of 'the' suggests that there must be only one such acr
            self.assertFalse(len(acrList) > 1)
            if len(acrList):
                # And hence all associated content dependencies in lao must be to
                # the single acr in this list.
                for d in laoResource.Dependency:
                    acr = self.cc.cp.manifest.GetElementByID(d.identifierref)
                    if acr is None:
                        print d.identifierref
                        print self.cc.cp.manifest.root
                    if acr.type != AssociatedContentType:
                        continue
                    self.assertTrue(acr is acrList[0])

    def test1_4_WebContent_1(self):
        """A resource of the type "webcontent" must comply with the following
        restrictions...
        1. It may contain a file element for any file that exists in the package
        so long as the file is not in a Learning Application Object directory or
        a subdirectory of any Learning Application Object directory."""
        dPathList = []
        for lao in self.cc.laoTable.keys():
            dPath, acr = self.cc.laoTable[lao]
            if dPath is not None:
                dPathList.append(dPath)
        for wc in self.cc.cwcList:
            for f in wc.File:
                fPath = f.PackagePath(self.cc.cp)
                for dPath in dPathList:
                    self.assertFalse(imscp.PathInPath(fPath, dPath))
