growl         = require 'growl'
gaze          = require 'gaze'
child_process = require 'child_process'
fs            = require 'fs'
path          = require 'path'
mkdirp        = require 'mkdirp'
assets        = require 'coffee-assets'

_t = {}; i = 0; colors = [ '\u001b[33m', '\u001b[34m', '\u001b[35m', '\u001b[36m', '\u001b[31m', '\u001b[32m', '\u001b[1m\u001b[33m', '\u001b[1m\u001b[34m', '\u001b[1m\u001b[35m', '\u001b[1m\u001b[36m', '\u001b[1m\u001b[31m', '\u001b[1m\u001b[32m' ]
notify = (title, msg, image, err, show) ->
  _t[title] = i++ if typeof _t[title] is 'undefined'
  msg = msg.stack if err and typeof msg is 'object' and typeof msg.stack isnt 'undefined'
  console.log "#{colors[_t[title]]}#{title}:\u001b[0m #{msg.toString().replace(/[\r\n]+$/, '')}"
  growl msg, image: process.cwd()+'/node_modules/coffee-assets/images/'+image+'.png', title: title if show

watch = ->
  a = arguments
  cb = a[a.length-1]
  if a.length is 2
    globs = [ in: [''], out: '' ]
    suffix = a[0]
  else if a.length is  3
    globs = a[0]
    suffix = a[1]
  for k, glob of globs
    glob.in = [ glob.in ] if typeof glob.in is 'string'
    for kk of glob.in
      ((glob)->
        notify 'gaze', "watching #{glob.in+glob.suffix}", 'working', false, false
        gaze glob.in+glob.suffix, (err, watcher) ->
          notify 'gaze', err, 'failure', true, false if err
          @on 'changed', (file) ->
            relout = path.join(glob.out, path.relative(path.join(process.cwd(), glob.in), file))
            cb file, relout, glob.in, glob.out
      )(in: glob.in[kk], out: glob.out, suffix: glob.suffix or suffix)

write = (who, file, data, cb) ->
  mkdirp.sync path.dirname file
  fs.writeFile file, data, 'utf8', (err) ->
    if err
      notify who, "unable to write #{file}. #{err}", 'failure', true, true
    else
      notify who, "wrote #{file}", 'success', false, true
      cb() if typeof cb is 'function'

node_child = null
restart_node = -> node_child.kill() if node_child?
start_node = ->
  last_start = new Date()
  node_child = child_process.spawn 'node', ['server.js']
  node_child.stdout.on 'data', (stdout) ->
    notify 'node', ''+stdout, 'working', false, false
  node_child.stderr.on 'data', (stderr) ->
    notify 'node', ''+stderr, 'failure', true, true
  node_child.on 'exit', (code) ->
    uptime = (new Date()-last_start)
    notify 'node', "node server died (uptime: #{uptime/1000}sec)", 'working', false, false
    if uptime < 2*1000
      notify 'node', 'due to short uptime, 15sec to restart...', 'working', false, false
      setTimeout start_node, 15*1000 # wait 5 sec before trying again
    else
      start_node()
  notify 'node', 'spawned new server instance', 'working', false, false


# tasks
task 'start', 'start main process loop', ->
  watch 'Cakefile', ->
    notify 'cake', 'Cakefile changed.', 'working', false, true
    restart_node()
    process.exit 0

  # CoffeeScripts
  watch [
      in: 'precompile'
      suffix: '/server.js.coffee'
      out: ''
    ,
      in: 'precompile/controllers/server'
      out: 'static/app/controllers'
    ,
      in: 'precompile/controllers/shared'
      out:  'static/public/assets/controllers'
    ,
      in: 'precompile/models/server'
      out: 'static/app/models'
    ,
      in: 'precompile/models/shared'
      out: 'static/public/assets/models'
    ,
      in: 'precompile/helpers/server'
      out: 'static/app/helpers'
    ,
      in: 'precompile/helpers/shared'
      out: 'static/public/assets/helpers'
    ,
      in: [
        'precompile/assets/behaviors'
        'precompile/vendor/assets'
      ]
      out: 'static/public/assets'
  # filenames with underscore prefix are only compiled via #= require directive
  ], '/**/!(@(_))*.{js,js.coffee}', (infile, outfile) ->
    assets.precompile infile, assets.compiler(), (err, compiled) ->
      return notify 'coffeescript', err, 'failure', true, true if err
      write 'coffeescript', outfile.replace(/\.coffee$/,''), compiled

  # CoffeeStylesheets
  watch [
    in: [
      'precompile/assets/stylesheets'
      'precompile/vendor/assets'
    ],
    out: 'static/public/assets'
  # filenames with underscore prefix are only compiled via #= require directive
  ], '/**/!(@(_))*.{css,css.coffee}', (infile, outfile) ->
    assets.precompile infile, assets.compiler(
      render_options:
        format: true
      sprite_options:
        image_path: 'precompile/assets/sprites/'
        sprite_path: 'static/public/assets/'
        sprite_url: '/assets/'
    ), (err, compiled) ->
      return notify 'coffeestylesheets', err, 'failure', true, true if err
      write 'coffeestylesheets', outfile.replace(/\.coffee/,''), compiled

  # CoffeeTemplates
  watch [
      in: 'precompile/views/server'
      out: 'static/app/views'
    ,
      in: 'precompile/views/shared'
      out: 'static/public/assets'
  ], '/**/*.html.coffee', (infile, outfile, inpath, outpath) ->
    # if ANY template file changes, ALL must be recompiled
    # because they are aggregated into a single templates.js file and function()
    assets.precompile_all inpath, {
      render_options:
        format: true
        globals:
          content_for: (s,f) ->
            @block 'content_for '+JSON.stringify(s), f
          yields: (s) ->
            @block 'yields '+JSON.stringify(s), ->
      compile_options:
        common_helpers: true
    }, (err, compiled) ->
      return notify 'coffeetemplates', err, 'failure', true, true if err
      write 'coffeetemplates', outpath+'/templates.js', compiled

  # JavaScripts
  watch 'static/app/**/*.js', ->
    notify 'gaze', 'static .js changed. restarting node', 'working', false, false
    restart_node()

  start_node()
