#!/usr/bin/env python
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Write a Mochitest manifest for WebGL conformance test files.

import os
import re

WRAPPER_TEMPLATE_FILEPATH = 'mochi-wrapper.html.template'
WRAPPERS_DIR = '_wrappers'
MANIFEST_TEMPLATE_FILEPATH = 'mochitest.ini.template'
MANIFEST_OUTPUT_FILEPATH = '../_webgl-conformance.ini'
ERRATA_FILEPATH = 'mochitest-errata.ini'
BASE_TEST_LIST_FILENAME = '00_test_list.txt'
FILE_PATH_PREFIX = os.path.basename(os.getcwd()) # 'webgl-conformance'

SUPPORT_DIRS = [
    'conformance',
    'resources',
]

EXTRA_SUPPORT_FILES = [
    'always-fail.html',
    'iframe-autoresize.js',
    'mochi-single.html',
    '../webgl-mochitest/driver-info.js',
]

ACCEPTABLE_ERRATA_KEYS = set([
  'fail-if',
  'skip-if',
  'subsuite',
])

GENERATED_HEADER = '''
# This is a GENERATED FILE. Do not edit it directly.
# Regenerated it by using `python generate-wrappers-and-manifest.py`.
# Mark skipped tests in mochitest-errata.ini.
# Mark failing tests in mochi-single.html.
'''.strip()

########################################################################
# GetTestList

def GetTestList():
    testList = []
    AccumTests('', BASE_TEST_LIST_FILENAME, testList)
    return testList

##############################
# Internals

def AccumTests(path, listFile, out_testList):
    listFilePath = os.path.join(path, listFile)
    assert os.path.exists(listFilePath), 'Bad `listFilePath`: ' + listFilePath

    with open(listFilePath, 'rb') as fIn:
        for line in fIn:
            line = line.rstrip()
            if not line:
                continue

            strippedLine = line.lstrip()
            if strippedLine.startswith('//'):
                continue
            if strippedLine.startswith('#'):
                continue
            if strippedLine.startswith('--'):
                continue

            split = line.rsplit('.', 1)
            assert len(split) == 2, 'Bad split for `line`: ' + line
            (name, ext) = split

            if ext == 'html':
                newTestFilePath = os.path.join(path, line)
                newTestFilePath = newTestFilePath.replace(os.sep, '/')
                out_testList.append(newTestFilePath)
                continue

            assert ext == 'txt', 'Bad `ext` on `line`: ' + line

            split = line.rsplit('/', 1)
            nextListFile = split[-1]
            nextPath = ''
            if len(split) != 1:
                nextPath = split[0]

            nextPath = os.path.join(path, nextPath)
            AccumTests(nextPath, nextListFile, out_testList)
            continue

    return

########################################################################
# Templates

def FillTemplate(inFilePath, templateDict, outFilePath):
    templateShell = ImportTemplate(inFilePath)
    OutputFilledTemplate(templateShell, templateDict, outFilePath)
    return


def ImportTemplate(inFilePath):
    with open(inFilePath, 'rb') as f:
        return TemplateShell(f)


def OutputFilledTemplate(templateShell, templateDict, outFilePath):
    spanStrList = templateShell.Fill(templateDict)

    with open(outFilePath, 'wb') as f:
        f.writelines(spanStrList)
    return

##############################
# Internals

def WrapWithIndent(lines, indentLen):
  split = lines.split('\n')
  if len(split) == 1:
      return lines

  ret = [split[0]]
  indentSpaces = ' ' * indentLen
  for line in split[1:]:
      ret.append(indentSpaces + line)

  return '\n'.join(ret)


templateRE = re.compile('(%%.*?%%)')
assert templateRE.split('  foo = %%BAR%%;') == ['  foo = ', '%%BAR%%', ';']


class TemplateShellSpan:
    def __init__(self, span):
        self.span = span

        self.isLiteralSpan = True
        if self.span.startswith('%%') and self.span.endswith('%%'):
            self.isLiteralSpan = False
            self.span = self.span[2:-2]

        return


    def Fill(self, templateDict, indentLen):
        if self.isLiteralSpan:
            return self.span

        assert (self.span in templateDict,
                '\'' + self.span + '\' not in dict!')

        filling = templateDict[self.span]

        return WrapWithIndent(filling, indentLen)


class TemplateShell:
    def __init__(self, iterableLines):
        spanList = []
        curLiteralSpan = []
        for line in iterableLines:
            split = templateRE.split(line)

            for cur in split:
                isTemplateSpan = cur.startswith('%%') and cur.endswith('%%')
                if not isTemplateSpan:
                    curLiteralSpan.append(cur)
                    continue

                if curLiteralSpan:
                    span = ''.join(curLiteralSpan)
                    span = TemplateShellSpan(span)
                    spanList.append(span)
                    curLiteralSpan = []

                assert len(cur) >= 4

                span = TemplateShellSpan(cur)
                spanList.append(span)
                continue
            continue

        if curLiteralSpan:
            span = ''.join(curLiteralSpan)
            span = TemplateShellSpan(span)
            spanList.append(span)

        self.spanList = spanList
        return


    # Returns spanStrList.
    def Fill(self, templateDict):
        indentLen = 0
        ret = []
        for span in self.spanList:
            span = span.Fill(templateDict, indentLen)
            ret.append(span)

            # Get next `indentLen`.
            try:
                lineStartPos = span.rindex('\n') + 1

                # let span = 'foo\nbar'
                # len(span) is 7
                # lineStartPos is 4
                indentLen = len(span) - lineStartPos
            except ValueError:
                indentLen += len(span)
            continue

        return ret

########################################################################
# Output

def WriteWrappers(testWebPathList):
    templateShell = ImportTemplate(WRAPPER_TEMPLATE_FILEPATH)

    if not os.path.exists(WRAPPERS_DIR):
        os.mkdir(WRAPPERS_DIR)
    assert os.path.isdir(WRAPPERS_DIR)

    wrapperManifestPathList = []
    for testWebPath in testWebPathList:
        # Mochitests must start with 'test_' or similar, or the test
        # runner will ignore our tests.
        # The error text is "is not a valid test".
        wrapperFilePath = 'test_' + testWebPath.replace('/', '__')
        wrapperFilePath = os.path.join(WRAPPERS_DIR, wrapperFilePath)

        templateDict = {
            'TEST_PATH': testWebPath,
        }

        print('Writing \'' + wrapperFilePath + '\'')
        OutputFilledTemplate(templateShell, templateDict,
                             wrapperFilePath)

        wrapperManifestPath = wrapperFilePath.replace(os.sep, '/')
        wrapperManifestPathList.append(wrapperManifestPath)
        continue

    return wrapperManifestPathList


def PathFromManifestDir(path):
    print('path: ' + path)
    ret = os.path.join(FILE_PATH_PREFIX, path)
    return ret.replace(os.sep, '/')


def WriteManifest(wrapperManifestPathList, supportFilePathList):
    errataMap = LoadErrata()

    # DEFAULT_ERRATA
    defaultHeader = '[DEFAULT]'
    defaultErrataStr = ''
    if defaultHeader in errataMap:
        defaultErrataStr = '\n'.join(errataMap[defaultHeader])
        del errataMap[defaultHeader]

    # SUPPORT_FILES
    supportFilePathList = sorted(supportFilePathList)
    supportFilePathList = [PathFromManifestDir(x) for x in supportFilePathList]
    supportFilesStr = '\n'.join(supportFilePathList)

    # MANIFEST_TESTS
    manifestTestLineList = []
    for wrapperManifestPath in wrapperManifestPathList:
        header = '[' + wrapperManifestPath + ']'
        transformedHeader = '[' + PathFromManifestDir(wrapperManifestPath) + ']'
        # header: '[foo.html]'
        # transformedHeader: '[webgl-conformance/foo.html]'

        manifestTestLineList.append(transformedHeader)

        if not header in errataMap:
            continue

        errataLineList = errataMap[header]
        del errataMap[header]
        manifestTestLineList += errataLineList
        continue

    assert not errataMap, 'Errata left in map: {}'.format(str(errataMap))

    manifestTestsStr = '\n'.join(manifestTestLineList)

    # Fill the template.
    templateDict = {
        'HEADER': GENERATED_HEADER,
        'DEFAULT_ERRATA': defaultErrataStr,
        'SUPPORT_FILES': supportFilesStr,
        'MANIFEST_TESTS': manifestTestsStr,
    }

    FillTemplate(MANIFEST_TEMPLATE_FILEPATH, templateDict,
                 MANIFEST_OUTPUT_FILEPATH)
    return

##############################
# Internals

kManifestHeaderRegex = re.compile(r'\[[^\]]*?\]')


def LoadErrata():
    nodeMap = {}

    nodeHeader = None
    nodeLineList = []
    lineNum = 0
    with open(ERRATA_FILEPATH, 'rb') as f:
        for line in f:
            lineNum += 1
            line = line.rstrip()
            cur = line.lstrip()
            if cur.startswith('#'):
                continue

            if not cur:
                continue

            if not cur.startswith('['):
                split = cur.split('=')
                key = split[0].strip()
                if not key in ACCEPTABLE_ERRATA_KEYS:
                    text = 'Unacceptable errata key on line {}: {}'
                    text = text.format(str(lineNum), key)
                    raise Exception(text)
                nodeLineList.append(line)
                continue

            match = kManifestHeaderRegex.search(cur)
            assert match, line

            nodeHeader = match.group()
            assert not nodeHeader in nodeMap, 'Duplicate header: ' + nodeHeader
            nodeLineList = []
            nodeMap[nodeHeader] = nodeLineList
            continue

    return nodeMap

########################################################################

def GetSupportFileList():
    ret = []
    for supportDir in SUPPORT_DIRS:
        ret += GetFilePathListForDir(supportDir)

    ret += EXTRA_SUPPORT_FILES

    return ret


def GetFilePathListForDir(baseDir):
    ret = []
    for root, folders, files in os.walk(baseDir):
        for f in files:
            filePath = os.path.join(root, f)
            filePath = filePath.replace(os.sep, '/')
            ret.append(filePath)

    return ret


if __name__ == '__main__':
    fileDir = os.path.dirname(__file__)
    assert not fileDir, 'Run this file from its directory, not ' + fileDir

    testWebPathList = GetTestList()

    wrapperFilePathList = WriteWrappers(testWebPathList)

    supportFilePathList = GetSupportFileList()
    WriteManifest(wrapperFilePathList, supportFilePathList)

    print('Done!')
