###
# Omelette Simple Auto Completion for Node
###
{EventEmitter} = require "events"
path           = require "path"
fs             = require "fs"
os             = require "os"

depthOf = (object) ->
  level = 1
  for own key of object
    if typeof object[key] is 'object'
      depth = depthOf(object[key]) + 1
      level = Math.max(depth, level)
  level

class Omelette extends EventEmitter

  {log} = console

  constructor: ->
    @compgen  = process.argv.indexOf "--compgen"
    @install  = process.argv.indexOf("--completion") > -1
    @installFish  = process.argv.indexOf("--completion-fish") > -1
    isZsh     = process.argv.indexOf("--compzsh") > -1
    isFish    = process.argv.indexOf("--compfish") > -1
    @isDebug  = process.argv.indexOf("--debug") > -1

    @fragment = parseInt(process.argv[@compgen+1])-(if isZsh then 1 else 0)
    @line     = process.argv[@compgen+3]
    @word     = @line?.trim().split(/\s+/).pop()

    {@HOME, @SHELL} = process.env

  setProgram: (programs)->
    programs = programs.split '|'
    [@program] = programs
    @programs = programs.map (program)-> program.replace ///
      [
       ^     # Do not allow except:
        A-Z  # .. uppercase
        a-z  # .. lowercase
        0-9  # .. numbers
        \.   # .. dots
        \_   # .. underscores
        \-   # .. dashes
      ]
    ///g, ''

  setFragments: (@fragments...)->

  generate: ->
    data = {before: @word, @fragment, @line, @reply}
    @emit "complete", @fragments[@fragment-1], data
    @emit @fragments[@fragment-1], data
    @emit "$#{@fragment}", data
    process.exit()

  reply: (words=[])->
    console.log words.join? os.EOL
    process.exit()

  tree: (objectTree={})->
    depth = depthOf objectTree
    for level in [1..depth]
      @on "$#{level}", ({ fragment, reply, line })->
        accessor = new Function '_', """
          return _['#{line.split(/\s+/).slice(1).filter(Boolean).join("']['")}']
        """
        replies = if fragment is 1 then Object.keys(objectTree) else accessor(objectTree)
        reply do (replies = replies)->
          return replies() if replies instanceof Function
          return replies if replies instanceof Array
          return Object.keys(replies) if replies instanceof Object
    this

  generateCompletionCode: ->
    completions = @programs.map (program)=>
      completion = "_#{program}_completion"
      """
      ### #{program} completion - begin. generated by omelette.js ###
      if type compdef &>/dev/null; then
        #{completion}() {
          compadd -- `#{@program} --compzsh --compgen "${CURRENT}" "${words[CURRENT-1]}" "${BUFFER}"`
        }
        compdef #{completion} #{program}
      elif type complete &>/dev/null; then
        #{completion}() {
          local cur prev nb_colon
          _get_comp_words_by_ref -n : cur prev
          nb_colon=$(grep -o ":" <<< "$COMP_LINE" | wc -l)

          COMPREPLY=( $(compgen -W '$(#{@program} --compbash --compgen "$((COMP_CWORD - (nb_colon * 2)))" "$prev" "${COMP_LINE}")' -- "$cur") )

          __ltrim_colon_completions "$cur"
        }
        complete -F #{completion} #{program}
      fi
      ### #{program} completion - end ###
      """

    # Adding aliases for testing purposes
    completions.push @generateTestAliases() if @isDebug
    completions.join os.EOL

  generateCompletionCodeFish: ->
    completions = @programs.map (program)=>
      completion = "_#{program}_completion"
      """
      ### #{program} completion - begin. generated by omelette.js ###
      function #{completion}
        #{@program} --compfish --compgen (count (commandline -poc)) (commandline -pt) (commandline -pb)
      end
      complete -f -c #{program} -a '(#{completion})'
      ### #{program} completion - end ###
      """

    # Adding aliases for testing purposes
    completions.push @generateTestAliases() if @isDebug
    completions.join os.EOL

  generateTestAliases: ->
    fullPath = path.join process.cwd(), @program
    debugAliases   = @programs.map((program)-> "  alias #{program}=#{fullPath}").join os.EOL
    debugUnaliases = @programs.map((program)-> "  unalias #{program}").join os.EOL

    """
    ### test method ###
    omelette-debug-#{@program}() {
    #{debugAliases}
    }
    omelette-nodebug-#{@program}() {
    #{debugUnaliases}
    }
    ### tests ###
    """

  checkInstall: ->
    if @install
      log @generateCompletionCode()
      process.exit()
    if @installFish
      log @generateCompletionCodeFish()
      process.exit()

  getActiveShell: ->
    {SHELL} = process.env
    if SHELL.match /bash/      then 'bash'
    else if SHELL.match /zsh/  then 'zsh'
    else if SHELL.match /fish/ then 'fish'

  getDefaultShellInitFile: ->

    fileAt = (root)->
      (file)-> path.join root, file

    fileAtHome = fileAt @HOME

    switch @shell = @getActiveShell()
      when 'bash'  then fileAtHome '.bash_profile'
      when 'zsh'   then fileAtHome '.zshrc'
      when 'fish'  then fileAtHome '.config/fish/config.fish'

  setupShellInitFile: (initFile=@getDefaultShellInitFile())->

    template = (command)=>
      """

      # begin #{@program} completion
      #{command}
      # end #{@program} completion

      """

    switch @shell
      when 'bash'
        programFolder = path.join @HOME, ".#{@program}"
        completionPath = path.join programFolder, 'completion.sh'

        fs.mkdirSync programFolder unless fs.existsSync programFolder
        fs.writeFileSync completionPath, @generateCompletionCode()
        fs.appendFileSync initFile, template "source #{completionPath}"

      when 'zsh'
        fs.appendFileSync initFile, template ". <(#{@program} --completion)"

      when 'fish'
        fs.appendFileSync initFile, template "#{@program} --completion-fish | source"

    process.exit();

  init: ->
    do @generate if @compgen > -1

module.exports = (template, args...)->
  if template instanceof Array and args.length > 0
    [program, callbacks] = [template[0].trim(), args]
    fragments = callbacks.map (callback, index) -> "arg#{index}"
  else
    [program, fragments...] = template.split /\s+/
    callbacks = []

  fragments = fragments.map (fragment)-> fragment.replace /^\<+|\>+$/g, ''
  _omelette = new Omelette
  _omelette.setProgram program
  _omelette.setFragments fragments...
  _omelette.checkInstall()
  for callback, index in callbacks
    fragment = "arg#{index}"
    do (callback = callback)->
      _omelette.on fragment, (args...)->
        @reply if callback instanceof Array
          callback
        else
          callback args...
  _omelette
