# Copyright (c) 2012 Ecma International.  All rights reserved.
# This code is governed by the BSD license found in the LICENSE file.

#--Imports---------------------------------------------------------------------
import argparse
import os
import sys
import xml.dom.minidom
import base64
import datetime
import shutil
import re
import json
import stat

from common import convertDocString

#--Stubs-----------------------------------------------------------------------
def generateHarness(harnessType, jsonFile, description):
    pass


#------------------------------------------------------------------------------
from packagerConfig import *

#--Globals---------------------------------------------------------------------

__parser = argparse.ArgumentParser(description= \
                                   'Tool used to generate the test262 website')
__parser.add_argument('--version', action='store', required=True,
                      help='Version of the test suite.')
__parser.add_argument('--type', action='store', default=DEFAULT_TESTCASE_TEMPLATE,
                      help='Type of test case runner to generate.')
__parser.add_argument('--console', action='store_true', default=False,
                      help='Type of test case runner to generate.')
ARGS = __parser.parse_args()

if not os.path.exists(EXCLUDED_FILENAME):
    print "Cannot generate (JSON) test262 tests without a file," + \
        " %s, showing which tests have been disabled!" % EXCLUDED_FILENAME
    sys.exit(1)
EXCLUDE_LIST = xml.dom.minidom.parse(EXCLUDED_FILENAME)
EXCLUDE_LIST = EXCLUDE_LIST.getElementsByTagName("test")
EXCLUDE_LIST = [x.getAttribute("id") for x in EXCLUDE_LIST]

#a list of all ES5 test chapter directories
TEST_SUITE_SECTIONS = []

#total number of tests accross the entire set of tests.
TOTAL_TEST_COUNT = 0

#List of all *.json files containing encoded test cases
SECTIONS_LIST = []


#--Sanity checks--------------------------------------------------------------#
if not os.path.exists(TEST262_CASES_DIR):
    print "Cannot generate (JSON) test262 tests when the path containing said tests, %s, does not exist!" % TEST262_CASES_DIR
    sys.exit(1)

if not os.path.exists(TEST262_HARNESS_DIR):
    print "Cannot copy the test harness from a path, %s, that does not exist!" % TEST262_HARNESS_DIR
    sys.exit(1)

if not os.path.exists(TEST262_WEB_CASES_DIR):
    os.mkdir(TEST262_WEB_CASES_DIR)

if not os.path.exists(TEST262_WEB_HARNESS_DIR):
    os.mkdir(TEST262_WEB_HARNESS_DIR)

if not hasattr(ARGS, "version"):
    print "A test262 suite version must be specified from the command-line to run this script!"
    sys.exit(1)

#--Helpers--------------------------------------------------------------------#
def createDepDirs(dirName):
    #base case
    if dirName==os.path.dirname(dirName):
        if not os.path.exists(dirName):
            os.mkdir(dirName)
    else:
        if not os.path.exists(dirName):
            createDepDirs(os.path.dirname(dirName))
            os.mkdir(dirName)

def test262PathToConsoleFile(path):
    stuff = os.path.join(TEST262_CONSOLE_CASES_DIR,
                         path.replace("/", os.path.sep))
    createDepDirs(os.path.dirname(stuff))
    return stuff

def getJSCount(dirName):
    '''
    Returns the total number of *.js files (recursively) under a given
    directory, dirName.
    '''
    retVal = 0
    if os.path.isfile(dirName) and dirName.endswith(".js"):
        retVal = 1
    elif os.path.isdir(dirName):
        tempList = [os.path.join(dirName, x) for x in os.listdir(dirName)]
        for x in tempList:
            retVal += getJSCount(x)
    #else:
    #    raise Exception("getJSCount: encountered a non-file/non-dir!")
    return retVal

#------------------------------------------------------------------------------
def dirWalker(dirName):
    '''
    Populates TEST_SUITE_SECTIONS with ES5 test directories based
    upon the number of test files per directory.
    '''
    global TEST_SUITE_SECTIONS
    #First check to see if it has test files directly inside it
    temp = [os.path.join(dirName, x) for x in os.listdir(dirName) \
                if not os.path.isdir(os.path.join(dirName, x))]
    if len(temp)!=0:
        TEST_SUITE_SECTIONS.append(dirName)
        return

    #Next check to see if all *.js files under this directory exceed our max
    #for a JSON file
    temp = getJSCount(dirName)
    if temp==0:
        print "ERROR:  expected there to be JavaScript tests under dirName!"
        sys.exit(1)
    #TODO - commenting out this elif/else clause seems to be causing *.json
    #naming conflicts WRT Sputnik test dirs.
    # elif temp < MAX_CASES_PER_JSON:
    TEST_SUITE_SECTIONS.append(dirName)
    return
    #TODO else:
    #    #Max has been exceeded.  We need to look at each subdir individually
    #    temp = os.listdir(dirName)
    #    for tempSubdir in temp:
    #        dirWalker(os.path.join(dirName, tempSubdir))

#------------------------------------------------------------------------------
def isTestStarted(line):
    '''
    Used to detect if we've gone past extraneous test comments in a test case.

    Note this is a naive approach on the sense that "/*abc*/" could be on one
    line.  However, we know for a fact this is not the case in IE Test Center
    or Sputnik tests.
    '''
    if re.search("^\s*//", line)!=None:     #//blah
        return False
    elif ("//" in line) and ("Copyright " in line):
        #BOM hack
        return False
    elif re.match("^\s*$", line)!=None: #newlines
        return False
    return True

#------------------------------------------------------------------------------
def getAllJSFiles(dirName):
    retVal = []
    for fullPath,dontCare,files in os.walk(dirName):
        retVal += [os.path.join(fullPath,b) for b in files if b.endswith(".js")]
    return retVal

#--MAIN------------------------------------------------------------------------
for temp in os.listdir(TEST262_CASES_DIR):
    temp = os.path.join(TEST262_CASES_DIR, temp)
    if not os.path.exists(temp):
        print "The expected ES5 test directory,", temp, "did not exist!"
        sys.exit(1)

    if temp.find("/.") != -1:
        # skip hidden files on Unix, such as ".DS_Store" on Mac
        continue

    if not ONE_JSON_PER_CHAPTER:
        dirWalker(temp)
    else:
        TEST_SUITE_SECTIONS.append(temp)

for chapter in TEST_SUITE_SECTIONS:
    chapterName = chapter.rsplit(os.path.sep, 1)[1]
    print "Generating test cases for ES5 chapter:", chapterName
    #create dictionaries for all our tests and a section
    testsList = {}
    sect = {}
    sect["name"] = "Chapter - " + chapterName

    #create an array for tests in a chapter
    tests = []
    sourceFiles = getAllJSFiles(chapter)

    if len(sourceFiles)!=0:
        excluded = 0
        testCount = 0
        for test in sourceFiles:
            #TODO - use something other than the hard-coded 'TestCases' below
            testPath =  "TestCases" + \
                test.split(TEST262_CASES_DIR, 1)[1].replace("\\", "/")
            testName=test.rsplit(".", 1)[0]
            testName=testName.rsplit(os.path.sep, 1)[1]
            if EXCLUDE_LIST.count(testName)==0:
                # dictionary for each test
                testDict = {}
                testDict["path"] = testPath

                tempFile = open(test, "rb")
                scriptCode = tempFile.readlines()
                tempFile.close()
                scriptCodeContent=""
                #Rip out license headers that add unnecessary bytes to
                #the JSON'ized test cases
                inBeginning = True

                #Hack to preserve the BOM
                if "Copyright " in scriptCode[0]:
                    scriptCodeContent += scriptCode[0]
                for line in scriptCode:
                    if inBeginning:
                        isStarted = isTestStarted(line)
                        if not isStarted:
                            continue
                        inBeginning = False
                    scriptCodeContent += line

                if scriptCodeContent==scriptCode[0]:
                    print "WARNING (" + test + \
                        "): unable to strip comments/license header/etc."
                    scriptCodeContent = "".join(scriptCode)
                scriptCodeContentB64 = base64.b64encode(scriptCodeContent)

                #add the test encoded code node to our test dictionary
                testDict["code"] = scriptCodeContentB64
                #now close the dictionary for the test

                #now get the metadata added.
                tempDict = convertDocString("".join(scriptCode))
                for tempKey in tempDict.keys():
                    #path is set from the file path above; the "@path" property
                    #in comments is redundant
                    if not (tempKey in ["path"]):
                        testDict[tempKey] = tempDict[tempKey]

                #this adds the test to our tests array
                tests.append(testDict)

                if ARGS.console:
                    with open(test262PathToConsoleFile(testDict["path"]),
                                  "w") as fConsole:
                        fConsole.write(scriptCodeContent)
                    with open(test262PathToConsoleFile(testDict["path"][:-3] + \
                                                       "_metadata.js"),
                              "w") as fConsoleMeta:
                        metaDict = testDict.copy()
                        del metaDict["code"]
                        fConsoleMeta.write("testDescrip = " + str(metaDict))
                testCount += 1
            else:
                print "Excluded:", testName
                excluded = excluded + 1

        #we have completed our tests
        # add section node, number of tests and the tests themselves.
        sect["numTests"] = str(len(sourceFiles)-excluded)
        sect["tests"] = tests

        #create a node for the tests and add it to our testsLists
        testsList["testsCollection"] = sect
        with open(os.path.join(TEST262_WEB_CASES_DIR, chapterName + ".json"),
                  "w") as f:
            json.dump(testsList, f, separators=(',',':'), sort_keys=True,
                      indent=0)


        if TESTCASELIST_PER_JSON:
            CHAPTER_TEST_CASES_JSON = {}
            CHAPTER_TEST_CASES_JSON["numTests"] = int(sect["numTests"])
            CHAPTER_TEST_CASES_JSON["testSuite"] = \
                [WEBSITE_CASES_PATH + chapterName + ".json"]
            with open(os.path.join(TEST262_WEB_CASES_DIR,
                                   "testcases_%s.json" % chapterName),
                      "w") as f:
                json.dump(CHAPTER_TEST_CASES_JSON, f, separators=(',',':'),
                          sort_keys=True, indent=0)
            generateHarness(ARGS.type, "testcases_%s.json" % chapterName,
                            chapterName.replace("ch", "Chapter "))

        #add the name of the chapter test to our complete list
        tempBool = True
        for tempRe in WEBSITE_EXCLUDE_RE_LIST:
            if tempRe.search(chapterName)!=None:
                tempBool = False
        if tempBool:
            SECTIONS_LIST.append(WEBSITE_CASES_PATH + chapterName + ".json")
            TOTAL_TEST_COUNT += int(sect["numTests"])


#we now have the list of files for each chapter
#create a root node for our suite
TEST_CASES_JSON = {}
TEST_CASES_JSON["numTests"] = TOTAL_TEST_COUNT
TEST_CASES_JSON["testSuite"] = SECTIONS_LIST
with open(os.path.join(TEST262_WEB_CASES_DIR, "default.json"), "w") as f:
    json.dump(TEST_CASES_JSON, f, separators=(',',':'), sort_keys=True, indent=0)
generateHarness(ARGS.type, "default.json", "Chapters 1-16")

#Overall description of this version of the test suite
SUITE_DESCRIP_JSON = {}
SUITE_DESCRIP_JSON["version"] = ARGS.version
SUITE_DESCRIP_JSON["date"] = str(datetime.datetime.now().date())
with open(os.path.join(TEST262_WEB_CASES_DIR, "suiteDescrip.json"), "w") as f:
    json.dump(SUITE_DESCRIP_JSON, f, separators=(',',':'), sort_keys=True)

#Deploy test harness to website as well
print ""
print "Deploying test harness files to 'TEST262_WEB_HARNESS_DIR'..."
if TEST262_HARNESS_DIR!=TEST262_WEB_HARNESS_DIR:
    for filename in [x for x in os.listdir(TEST262_HARNESS_DIR) \
                         if x.endswith(".js")]:
        toFilenameList = [ os.path.join(TEST262_WEB_HARNESS_DIR, filename)]
        if ARGS.console:
            toFilenameList.append(os.path.join(TEST262_CONSOLE_HARNESS_DIR,
                                               filename))

        for toFilename in toFilenameList:
            if not os.path.exists(os.path.dirname(toFilename)):
                os.mkdir(os.path.dirname(toFilename))
            fileExists = os.path.exists(toFilename)
            if fileExists:
                SC_HELPER.edit(toFilename)
            shutil.copy(os.path.join(TEST262_HARNESS_DIR, filename),
                        toFilename)
            if not fileExists:
                SC_HELPER.add(toFilename)

print "Done."
