import groovy.io.FileType
import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.tasks.Exec

buildscript {
    repositories {
        mavenCentral()
		jcenter()
    }
    dependencies {
        classpath 'com.eriwen:gradle-js-plugin:1.8.0'
		classpath 'com.moowork.gradle:gradle-grunt-plugin:0.2'
    }
}

apply plugin: 'js'
apply plugin: 'grunt'

repositories {
    mavenCentral()
}

configurations {
    rhino
}

dependencies {
    rhino 'org.mozilla:rhino:1.7R4'
}

project.ext {
    packageProps = new groovy.json.JsonSlurper().parseText(new File("package.json").toURL().text)
    failures = 0;
    rhinoTestSrc = "out/rhino-test-${packageProps.version}.js"
    testSrc = 'test/less'
    testOut = 'out/test'
}

task runGruntRhino(type: GruntTask) {
    gruntArgs = "rhino"
}

combineJs {
	dependsOn runGruntRhino
    source = ["dist/less-rhino-${packageProps.version}.js", "test/rhino/test-header.js","dist/lessc-rhino-${packageProps.version}.js"]
    dest = file(rhinoTestSrc)
}

task testRhino(type: AllRhinoTests) {
//      dependsOn 'testRhinoBase'
    dependsOn 'testRhinoBase', 'testRhinoErrors', 'testRhinoLegacy', 'testRhinoStaticUrls', 'testRhinoCompression', 'testRhinoDebugAll', 'testRhinoDebugComments', 'testRhinoDebugMediaquery', 'testRhinoNoJsError', 'testRhinoSourceMap'
}

task testRhinoBase(type: RhinoTest) {
    options = [ '--strict-math=true', '--relative-urls' ]
}

task testRhinoDebugAll(type: DebugRhinoTest) {
    options = [ '--strict-math=true', '--line-numbers=all' ]
    testDir = 'debug' + fs
    suffix = "-all"
}

task testRhinoDebugComments(type: DebugRhinoTest) {
    options = [ '--strict-math=true', '--line-numbers=comments' ]
    testDir = 'debug' + fs
    suffix = "-comments"
}

task testRhinoDebugMediaquery(type: DebugRhinoTest) {
    options = [ '--strict-math=true', '--line-numbers=mediaquery' ]
    testDir = 'debug' + fs
    suffix = "-mediaquery"
}

task testRhinoErrors(type: RhinoTest) {
    options = [ '--strict-math=true', '--strict-units=true' ]
    testDir = 'errors/'
    expectErrors = true
}

task testRhinoChyby(type: RhinoTest) {
    options = [ '--strict-math=true', '--strict-units=true' ]
    testDir = 'chyby/'
   // expectErrors = true
}

task testRhinoNoJsError(type: RhinoTest) {
    options = [ '--strict-math=true', '--strict-units=true', '--no-js' ]
    testDir = 'no-js-errors/'
    expectErrors = true
}

task testRhinoLegacy(type: RhinoTest) {
    testDir = 'legacy/'
}

task testRhinoStaticUrls(type: RhinoTest) {
    options = [ '--strict-math=true', '--rootpath=folder (1)/' ]
    testDir = 'static-urls/'
}

task testRhinoCompression(type: RhinoTest) {
    options = [ '--compress=true' ]
    testDir = 'compression/'
}

task testRhinoSourceMap(type: SourceMapRhinoTest) {
    options = [ '--strict-math=true', '--strict-units=true']
    testDir = 'sourcemaps/'
}

task setupTest {
	dependsOn combineJs
    doLast {
        file(testOut).deleteDir()
    }
}

task clean << {
    file(rhinoTestSrc).delete()
    file(testOut).deleteDir()
}

class SourceMapRhinoTest extends RhinoTest {
	
	// helper to get the output map file
	def getOutputMap(lessFile) {
		def outFile = project.file(lessFile.path.replace('test/less', project.testOut).replace('.less', '.css'))
		return project.file(outFile.path + ".map");
	}
	
	// callback to add SourceMap options to the options list
	def postProcessOptions(options, lessFile) {
		def outFile = getOutputMap(lessFile)
		project.file(outFile.parent).mkdirs()
		options << "--source-map=${testDir}${lessFile.name.replace('.less','.css')}"
		options << "--source-map-basepath=${lessRootDir}"
		options << "--source-map-rootpath=testweb/"
		options << "--source-map-output-map-file=${outFile}"

		options
	}
	
	// Callback to validate output
	def handleResult(exec, out, lessFile) {
		def actualFile = getOutputMap(lessFile)
		def expectedFile = project.file(projectDir + fs + "test" + fs + testDir + fs + lessFile.name.replace(".less", ".json"))		
		assert actualFile.text == expectedFile.text
	}
		
}

class DebugRhinoTest extends RhinoTest {

    def escapeIt(it) {
      return it.replaceAll("\\\\", "\\\\\\\\").replaceAll("/", "\\\\/").replaceAll(":", "\\\\:").replaceAll("\\.", "\\\\.");
    }

    def globalReplacements(input, directory) {
      def pDirectory = toPlatformFs(directory)
      def p = lessRootDir + fs + pDirectory 
      def pathimport = p + toPlatformFs("import/")
      def pathesc = escapeIt(p)
      def pathimportesc = escapeIt(pathimport)
      
      def result = input.replace("{path}", p).replace("{pathesc}", pathesc).replace("{pathimport}", pathimport)
      return result.replace("{pathimportesc}", pathimportesc).replace("\r\n", "\n")
    }
}

class RhinoTest extends DefaultTask {

    RhinoTest() {
        dependsOn 'setupTest'
    }

    def suffix = ""
    def testDir = ''
    def options = []
    def expectErrors = false
    def fs = File.separator;
    def projectDir = toUpperCaseDriveLetter(System.getProperty("user.dir"));
    def lessRootDir = projectDir + fs + "test" + fs + "less"
 
    def toUpperCaseDriveLetter(path) {
      if (path.charAt(1)==':' && path.charAt(2)=='\\') {
          return path.substring(0,1).toUpperCase() + path.substring(1);
      } 
      return path;
    }

    def toPlatformFs(path) {
      return path.replace('\\', fs).replace('/', fs); 
    }

    def expectedCssPath(lessFilePath) {
	  lessFilePath.replace('.less', "${suffix}.css").replace("${fs}less${fs}", "${fs}css${fs}");
    }

    def globalReplacements(input, directory) {
      return input;
    }

    def stylize(str, style) {
        def styles = [
            reset     : [0,   0],
            bold      : [1,  22],
            inverse   : [7,  27],
            underline : [4,  24],
            yellow    : [33, 39],
            green     : [32, 39],
            red       : [31, 39],
            grey      : [90, 39]
        ];
        return '\033[' + styles[style][0] + 'm' + str +
                '\033[' + styles[style][1] + 'm';
    }
	
	// Callback for subclasses to make any changes to the options
	def postProcessOptions(options, lessFile) {
		options
	}
	
	// Callback to validate output
	def handleResult(exec, out, lessFile) {
		def actual = out.toString().trim()
		def actualResult = project.file(lessFile.path.replace('test/less', project.testOut).replace('.less', '.css'))
		project.file(actualResult.parent).mkdirs()
		actualResult << actual
		def expected
		if (expectErrors) {
			assert exec.exitValue != 0
			expected = project.file(lessFile.path.replace('.less', '.txt')).text.trim().
					replace('{path}', lessFile.parent + '/').
					replace('{pathhref}', '').
					replace('{404status}', '')
		} else {
			assert exec.exitValue == 0
			def expectedFile = expectedCssPath(lessFile.path)
			expected = project.file(expectedFile).text.trim()
			expected = globalReplacements(expected, testDir)
		}
		actual=actual.trim()
		actual = actual.replace('\r\n', '\n')
		expected = expected.replace('\r\n', '\n')
		actual = actual.replace("/","\\")
		expected = expected.replace("/","\\")
//                        println "* actual *"
//                        println actual
//                        new File("actual.txt").write(actual)
//                        println "* expected *"
//                        println expected
//                        new File("expected.txt").write(expected)
		assert actual == expected
		actualResult.delete()

	}

    @TaskAction
    def runTest() {
        int testSuccesses = 0, testFailures = 0, testErrors = 0
        project.file('test/less/' + testDir).eachFileMatch(FileType.FILES, ~/.*\.less/) { lessFile ->
            println "lessfile: $lessFile"
            if (!project.hasProperty('test') || lessFile.name.startsWith(project.test)) {
                def out = new java.io.ByteArrayOutputStream()
                def processedOptions = postProcessOptions([project.rhinoTestSrc, lessFile] + options, lessFile)
                def execOptions = {
                    main = 'org.mozilla.javascript.tools.shell.Main'
//                    main = 'org.mozilla.javascript.tools.debugger.Main'
                    classpath = project.configurations.rhino
                    args = processedOptions
                    standardOutput = out
                    ignoreExitValue = true
                }
                println "rhinoTestSrc: ${project.rhinoTestSrc}" 
                try {
                    def exec = project.javaexec(execOptions)
					handleResult(exec, out, lessFile)
					testSuccesses++
					println stylize('   ok', 'green')
                }
                catch (ex) {
                    println ex
                    println()
                    testErrors++;
                }
                catch (AssertionError ae) {
                    println stylize('   failed', 'red')
                    println ae
                    testFailures++
                }
            } else {
                println stylize('   skipped', 'yellow')
            }
        }
        println stylize(testSuccesses + ' ok', 'green')
        println stylize(testFailures + ' assertion failed', testFailures == 0 ? 'green' : 'red')
        println stylize(testErrors + ' errors', testErrors == 0 ? 'green' : 'red')
        if (testFailures != 0 || testErrors != 0) {
            project.failures++;
        }
    }
}

class AllRhinoTests extends DefaultTask {

    AllRhinoTests() {
    }

    @TaskAction
    def runTest() {
       println stylize(project.failures + ' test suites failed', project.failures == 0 ? 'green' : 'red')
    }

    def stylize(str, style) {
        def styles = [
            reset     : [0,   0],
            bold      : [1,  22],
            inverse   : [7,  27],
            underline : [4,  24],
            yellow    : [33, 39],
            green     : [32, 39],
            red       : [31, 39],
            grey      : [90, 39]
        ];
        return '\033[' + styles[style][0] + 'm' + str +
                '\033[' + styles[style][1] + 'm';
    }
}

class GruntTask extends Exec {
    private String gruntExecutable = Os.isFamily(Os.FAMILY_WINDOWS) ? "grunt.cmd" : "grunt"
    private String switches = "--no-color"

    String gruntArgs = "" 

    public GruntTask() {
        super()
        this.setExecutable(gruntExecutable)
    }

    public void setGruntArgs(String gruntArgs) {
        this.args = "$switches $gruntArgs".trim().split(" ") as List
    }
}

