SandboxedModule = require('sandboxed-module')
sinon = require('sinon')
require('chai').should()
modulePath = require('path').join __dirname, '../../../app/js/CompileManager'
tk = require("timekeeper")
EventEmitter = require("events").EventEmitter
Path = require "path"

describe "CompileManager", ->
	beforeEach ->
		@CompileManager = SandboxedModule.require modulePath, requires:
			"./LatexRunner": @LatexRunner = {}
			"./ResourceWriter": @ResourceWriter = {}
			"./OutputFileFinder": @OutputFileFinder = {}
			"./OutputCacheManager": @OutputCacheManager = {}
			"settings-sharelatex": @Settings = 
				path:
					compilesDir: "/compiles/dir"
				synctexBaseDir: -> "/compile"
				clsi:
					docker:
						image: "SOMEIMAGE"

			"logger-sharelatex": @logger = { log: sinon.stub() , info:->}
			"child_process": @child_process = {}
			"./CommandRunner": @CommandRunner = {}
			"./DraftModeManager": @DraftModeManager = {}
			"./TikzManager": @TikzManager = {}
			"./LockManager": @LockManager = {}
			"fs": @fs = {}
			"fs-extra": @fse = { ensureDir: sinon.stub().callsArg(1) }
		@callback = sinon.stub()
		@project_id = "project-id-123"
		@user_id = "1234"
	describe "doCompileWithLock", ->
		beforeEach ->
			@request =
				resources: @resources = "mock-resources"
				project_id: @project_id
				user_id: @user_id
			@output_files = ["foo", "bar"]
			@Settings.compileDir = "compiles"
			@compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"
			@CompileManager.doCompile = sinon.stub().callsArgWith(1, null, @output_files)
			@LockManager.runWithLock = (lockFile, runner, callback) ->
				runner (err, result...) ->
					callback(err, result...)

		describe "when the project is not locked", ->
			beforeEach ->
				@CompileManager.doCompileWithLock @request, @callback

			it "should ensure that the compile directory exists", ->
				@fse.ensureDir.calledWith(@compileDir)
				.should.equal true

			it "should call doCompile with the request", ->
				@CompileManager.doCompile
				.calledWith(@request)
				.should.equal true

			it "should call the callback with the output files", ->
				@callback.calledWithExactly(null, @output_files)
				.should.equal true

		describe "when the project is locked", ->
			beforeEach ->
				@error = new Error("locked")
				@LockManager.runWithLock = (lockFile, runner, callback) =>
					callback(@error)
				@CompileManager.doCompileWithLock @request, @callback

			it "should ensure that the compile directory exists", ->
				@fse.ensureDir.calledWith(@compileDir)
				.should.equal true

			it "should not call doCompile with the request", ->
				@CompileManager.doCompile
				.called.should.equal false

			it "should call the callback with the error", ->
				@callback.calledWithExactly(@error)
				.should.equal true

	describe "doCompile", ->
		beforeEach ->
			@output_files = [{
				path: "output.log"
				type: "log"
			}, {
				path: "output.pdf"
				type: "pdf"
			}]
			@build_files = [{
				path: "output.log"
				type: "log"
				build: 1234
			}, {
				path: "output.pdf"
				type: "pdf"
				build: 1234
			}]
			@request =
				resources: @resources = "mock-resources"
				rootResourcePath: @rootResourcePath = "main.tex"
				project_id: @project_id
				user_id: @user_id
				compiler: @compiler = "pdflatex"
				timeout: @timeout = 42000
				imageName: @image = "example.com/image"
			@env = {}
			@Settings.compileDir = "compiles"
			@compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"
			@ResourceWriter.syncResourcesToDisk = sinon.stub().callsArgWith(2, null, @resources)
			@LatexRunner.runLatex = sinon.stub().callsArg(2)
			@OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files)
			@OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files)
			@DraftModeManager.injectDraftMode = sinon.stub().callsArg(1)
			@TikzManager.checkMainFile = sinon.stub().callsArg(3, false)
		
		describe "normally", ->
			beforeEach ->
				@CompileManager.doCompile @request, @callback

			it "should write the resources to disk", ->
				@ResourceWriter.syncResourcesToDisk
					.calledWith(@request, @compileDir)
					.should.equal true

			it "should run LaTeX", ->
				@LatexRunner.runLatex
					.calledWith("#{@project_id}-#{@user_id}", {
						directory: @compileDir
						mainFile:  @rootResourcePath
						compiler:  @compiler
						timeout:   @timeout
						image:     @image
						environment: @env
					})
					.should.equal true

			it "should find the output files", ->
				@OutputFileFinder.findOutputFiles
					.calledWith(@resources, @compileDir)
					.should.equal true

			it "should return the output files", ->
				@callback.calledWith(null, @build_files).should.equal true
			
			it "should not inject draft mode by default", ->
				@DraftModeManager.injectDraftMode.called.should.equal false
	
		describe "with draft mode", ->
			beforeEach ->
				@request.draft = true
				@CompileManager.doCompile @request, @callback
			
			it "should inject the draft mode header", ->
				@DraftModeManager.injectDraftMode
					.calledWith(@compileDir + "/" + @rootResourcePath)
					.should.equal true

		describe "with a check option", ->
			beforeEach ->
				@request.check = "error"
				@CompileManager.doCompile @request, @callback

			it "should run chktex", ->
				@LatexRunner.runLatex
					.calledWith("#{@project_id}-#{@user_id}", {
						directory: @compileDir
						mainFile:  @rootResourcePath
						compiler:  @compiler
						timeout:   @timeout
						image:     @image
						environment: {'CHKTEX_OPTIONS': '-nall -e9 -e10 -w15 -w16', 'CHKTEX_EXIT_ON_ERROR':1, 'CHKTEX_ULIMIT_OPTIONS': '-t 5 -v 64000'}
					})
					.should.equal true

		describe "with a knitr file and check options", ->
			beforeEach ->
				@request.rootResourcePath = "main.Rtex"
				@request.check = "error"
				@CompileManager.doCompile @request, @callback

			it "should not run chktex", ->
				@LatexRunner.runLatex
					.calledWith("#{@project_id}-#{@user_id}", {
						directory: @compileDir
						mainFile:  "main.Rtex"
						compiler:  @compiler
						timeout:   @timeout
						image:     @image
						environment: @env
					})
					.should.equal true

	describe "clearProject", ->
		describe "succesfully", ->
			beforeEach ->
				@Settings.compileDir = "compiles"
				@fs.lstat = sinon.stub().callsArgWith(1, null,{isDirectory: ()->true})
				@proc = new EventEmitter()
				@proc.stdout = new EventEmitter()
				@proc.stderr = new EventEmitter()
				@child_process.spawn = sinon.stub().returns(@proc)
				@CompileManager.clearProject @project_id, @user_id, @callback
				@proc.emit "close", 0

			it "should remove the project directory", ->
				@child_process.spawn
					.calledWith("rm", ["-r", "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"])
					.should.equal true

			it "should call the callback", ->
				@callback.called.should.equal true

		describe "with a non-success status code", ->
			beforeEach ->
				@Settings.compileDir = "compiles"
				@fs.lstat = sinon.stub().callsArgWith(1, null,{isDirectory: ()->true})
				@proc = new EventEmitter()
				@proc.stdout = new EventEmitter()
				@proc.stderr = new EventEmitter()
				@child_process.spawn = sinon.stub().returns(@proc)
				@CompileManager.clearProject @project_id, @user_id, @callback
				@proc.stderr.emit "data", @error = "oops"
				@proc.emit "close", 1

			it "should remove the project directory", ->
				@child_process.spawn
					.calledWith("rm", ["-r", "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"])
					.should.equal true

			it "should call the callback with an error from the stderr", ->
				@callback
					.calledWith(new Error())
					.should.equal true

				@callback.args[0][0].message.should.equal "rm -r #{@Settings.path.compilesDir}/#{@project_id}-#{@user_id} failed: #{@error}"

	describe "syncing", ->
		beforeEach ->
			@page = 1
			@h = 42.23
			@v = 87.56
			@width = 100.01
			@height = 234.56
			@line = 5
			@column = 3
			@file_name = "main.tex"
			@child_process.execFile = sinon.stub()
			@Settings.path.synctexBaseDir = (project_id) => "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"

		describe "syncFromCode", ->
			beforeEach ->
				@fs.stat = sinon.stub().callsArgWith(1, null,{isFile: ()->true})
				@stdout = "NODE\t#{@page}\t#{@h}\t#{@v}\t#{@width}\t#{@height}\n"
				@CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:@stdout})
				@CompileManager.syncFromCode @project_id, @user_id, @file_name, @line, @column, @callback

			# it "should execute the synctex binary", ->
			# 	bin_path = Path.resolve(__dirname + "/../../../bin/synctex")
			# 	synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf"
			# 	file_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}"
			# 	@child_process.execFile
			# 		.calledWith(bin_path, ["code", synctex_path, file_path, @line, @column], timeout: 10000)
			# 		.should.equal true

			it "should call the callback with the parsed output", ->
				@callback
					.calledWith(null, [{
						page: @page
						h: @h
						v: @v
						height: @height
						width: @width
					}])
					.should.equal true

		describe "syncFromPdf", ->
			beforeEach ->
				@fs.stat = sinon.stub().callsArgWith(1, null,{isFile: ()->true})
				@stdout = "NODE\t#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}\t#{@line}\t#{@column}\n"
				@CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:@stdout})
				@CompileManager.syncFromPdf @project_id, @user_id, @page, @h, @v, @callback

			# it "should execute the synctex binary", ->
			# 	bin_path = Path.resolve(__dirname + "/../../../bin/synctex")
			# 	synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf"
			# 	@CommandRunner.run
			# 		.calledWith(bin_path, ["pdf", synctex_path, @page, @h, @v], timeout: 10000)
			# 		.should.equal true

			it "should call the callback with the parsed output", ->
				@callback
					.calledWith(null, [{
						file: @file_name
						line: @line
						column: @column
					}])
					.should.equal true

	describe "wordcount", ->
		beforeEach ->
			@CommandRunner.run = sinon.stub().callsArg(6)
			@fs.readFile = sinon.stub().callsArgWith(2, null, @stdout = "Encoding: ascii\nWords in text: 2")
			@callback  = sinon.stub()

			@project_id
			@timeout = 10 * 1000
			@file_name = "main.tex"
			@Settings.path.compilesDir = "/local/compile/directory"
			@image = "example.com/image"

			@CompileManager.wordcount @project_id, @user_id, @file_name, @image, @callback

		it "should run the texcount command", ->
			@directory = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"
			@file_path = "$COMPILE_DIR/#{@file_name}"
			@command =[ "texcount", "-nocol", "-inc", @file_path, "-out=" + @file_path + ".wc"]
			
			@CommandRunner.run
				.calledWith("#{@project_id}-#{@user_id}", @command, @directory, @image, @timeout, {})
				.should.equal true

		it "should call the callback with the parsed output", ->
			@callback
				.calledWith(null, {
					encode: "ascii"
					textWords: 2
					headWords: 0
					outside: 0
					headers: 0
					elements: 0
					mathInline: 0
					mathDisplay: 0
					errors: 0
					messages: ""
				})
				.should.equal true
