-{Disposable}      = require('atom')
-child_process     = require('child_process')
-path              = require('path')
-
-# ------------------------------------------------------------------------------
+{Disposable}  = require 'atom'
+child_process = require 'child_process'
+path          = require 'path'
 
 module.exports =
 class ExecutionManager
-
-  ## Construction --------------------------------------------------------------
-
   constructor: (@levelCodeEditor) ->
     @executing = false
 
-  ## Level code execution ------------------------------------------------------
+  isExecuting: ->
+    return @executing
 
-  isExecuting: ->
-    @executing
+  startExecution: ({runExecArgs} = {}) ->
+    @terminal = @levelCodeEditor.getTerminal()
 
-  startExecution: ({runExecArgs}={}) ->
+    if @isExecuting() || @terminal.isExecuting()
+      return
+
     @textEditor = @levelCodeEditor.getTextEditor()
     @language = @levelCodeEditor.getLanguage()
     @level = @levelCodeEditor.getLevel()
-    @terminal = @levelCodeEditor.getTerminal()
-
-    return if @isExecuting() or @terminal.isExecuting()
 
     configKeyPath = 'levels.workspaceSettings.clearTerminalOnExecution'
-    @terminal.clear() if atom.config.get(configKeyPath)
-    @terminal.writeLn('Running level code...')
+    if atom.config.get configKeyPath
+      @terminal.clear()
+
+    @terminal.writeLn 'Running level code …'
 
     runExecPath = @language.getRunExecPath()
     configFilePath = @language.getConfigFilePath()
     levelNumber = @level.getNumber()
     filePath = @textEditor.getPath()
+
     cmd = ([
       "\"#{runExecPath}\""
-      '-l',"\"#{configFilePath}\""
+      '-l', "\"#{configFilePath}\""
     ].concat(runExecArgs).concat [
       "#{levelNumber}"
       "\"#{filePath}\""

    @processExited = false
    @processClosed = false

    # spawn child process and set up handlers
    @process = child_process.exec cmd,
      cwd: path.dirname(runExecPath)
      env: process.env
    @terminalSubscr = @terminal.onDidEnterInput (input) =>
      @process.stdin.write("#{input}\n")
    @process.stdout.on('data',@handleProcessData)
    @process.on('exit',@handleProcessExit)
    @process.on('close',@handleProcessClose)
    @executionStarted()

  stopExecution: ->
    return unless @isExecuting()
    @executionStoppedByUser = true
    @process.stdout.removeListener('data',@handleProcessData)
    @activeDataWriter?.dispose()
    unless @processExited
      @killProcess(@process.pid)
    unless @processClosed
      @process.stdout.read()
      @process.stdout.destroy()

  executionStarted: ->
    @executing = true
    @terminal.enterScope()
    @terminal.didStartExecution()
    @levelCodeEditor.didStartExecution()

  executionStopped: ->
    @executing = false

    @levelCodeEditor.didStopExecution()
    if @executionStoppedByUser
      @executionStoppedByUser = false
      @terminal.writeLn '…'
      @terminal.writeSubtle 'Execution stopped!'
    return

  handleProcessData: (data) =>
    @process.stdout.pause()
    lines = data.toString().split '\n'
    @activeDataWriter = @writeDataLines lines
    return

  handleProcessExit: (code, signal) =>
    @processExited = true
    return

  handleProcessClose: (code, signal) =>
    @terminalSubscription.dispose()
    @process.stdin.end()
    @processClosed = true
    if !@activeDataWriter
      @executionStopped()
    return

  writeDataLines: (lines) ->
    intervalId = setInterval =>
      if lines.length == 1
        lastLine = lines.shift()
        if lastLine
          @writeDataLine lastLine
        @activeDataWriter.dispose()
        @process.stdout.resume()
      else if lines.length > 1
        @writeDataLine lines.shift()
        @terminal.newLine()
    , 10
    new Disposable =>
      clearInterval intervalId
      @activeDataWriter = null
      if @processClosed
        @executionStopped()

  writeDataLine: (line) ->
    @terminal.write line
    return

  killProcess: (pid) ->
    switch process.platform
      when 'darwin' then @killProcessOnDarwinAndLinux pid
      when 'linux'  then @killProcessOnDarwinAndLinux pid
      when 'win32'  then @killProcessOnWin32 pid

  killProcessOnDarwinAndLinux: (parentPid) ->
    try
      out = child_process.execSync "pgrep -P #{parentPid}", {env: process.env}
      childPids = out.toString().split('\n')
                    .map (pid) -> parseInt pid
                    .filter (pid) -> not isNaN pid
    catch error
      childPids = []
    for childPid in childPids
      @killProcessOnDarwinAndLinux childPid
    try process.kill parentPid, 'SIGINT'

    return

  killProcessOnWin32: (parentPid) ->
    try
      wmicProcess = child_process.spawn 'wmic', [
        'process', 'where', "(ParentProcessId=#{parentPid})", 'get', 'processid'
      ]
      out = ''
      wmicProcess.stdout.on 'data', (data) -> out += data
      wmicProcess.stdout.on 'close', =>
        childPids = out.split(/\s+/)
                      .filter (pid) -> /^\d+$/.test pid
                      .map (pid) -> parseInt pid
                      .filter (pid) -> pid != parentPid && 0 < pid < Infinity
        for childPid in childPids
          @killProcessOnWin32 childPid
        try process.kill parentPid, 'SIGINT'
    return