fs = require 'fs'
path = require 'path'
cc = require 'coffee-script'
_ = require 'lodash'


pm2_app_defaults = ( obj ) ->
  _.defaultsDeep obj, {
    name: null
    pid: null
    pm_id: null
    pm2_env:
      status: null
      cwd: null
      pm_uptime: null
      pm_exec_path: null
      ENV: null
    versioning: {}
  }

class PM2Config
  constructor: ( @cwd ) ->
    @pm2_config = []
    @pm2_config_dir = null
    @pm2_config_file = null
  load: ( filename ) ->
    @pm2_config_file = filename
    if path.dirname(filename) != filename
      @pm2_config_dir = path.join @cwd, path.dirname filename
      if not fs.existsSync @pm2_config_dir
        throw new Error "Directory should exist: #{filename}"
    @pm2_config_file = path.join @cwd, filename
    if fs.existsSync @pm2_config_file
      @pm2_config = require(@pm2_config_file).apps
      for pm2_proc in @pm2_config
        pm2_app_defaults pm2_proc

class PM2Dump
  constructor: ( @cwd ) ->
    @pm2_dump = []
    @pm2_dump_dir = null
    @pm2_dump_file = null
  load: ( filename ) ->
    @pm2_dump_file = filename
    @pm2_dump_file = path.join @cwd, filename
    if not fs.existsSync @pm2_dump_file
      throw new Error "No such file #{filename}"
    @pm2_dump = require(@pm2_dump_file)

class PM2Manager
  constructor: ( @cwd ) ->
    @procs = []
  add: ( pm2_proc ) ->
    pm2_app_defaults pm2_proc
    @procs.push pm2_proc
  save: ->
  load: ( filename ) ->
    cfg = new PM2Config @cwd
    cfg.load filename
    @procs = cfg.pm2_config
  get_by_id: ( app_id) ->
    for app in @procs
      if app.pm_id == app_id
        return app
  get_by_name: ( app_name ) ->
    for app in @procs
      if app.name == app_name
        return app
  update_by_name: ( app ) ->
    for pm2_proc in @procs
      if app.name == pm2_proc.name
        _.merge pm2_proc, app
  name_exists: ( app_name ) ->
    for app in @procs
      if app.name == app_name
        return true
    return false


module.exports = ( ctx ) ->

  try
    pug = require 'pug'
    pm2 = require 'pm2'
  catch
    return

  httprouter = require('./http') ctx
  pugrouter = require('./pug') ctx
  
  detailPugFn = path.join( __dirname, 'pm2/view/detail.pug' )

  listPugFn = path.join( __dirname, 'pm2/view/list.pug' )
  listCoffeeFn = path.join( __dirname, 'pm2/view/list.coffee' )

  ### TODO: PM2Manager dump/config loader
  m = new PM2Manager ctx.cwd
  # If given, use spec for JSON config file
  if rctx.route.spec and rctx.route.spec != '#'
    m.load rctx.route.spec

  pm2.list (err, ps_list) ->
    for app in ps_list
      if m.name_exists app.name
        m.update_by_name app
      else
        m.add app
  ###

  generators =
    default: ( rctx ) ->
      # FIXME: allow string descriptions;
      # auto-export routes based on route.default mapping
      # XXX: console.log 'PM2', ctx.site.base, rctx.name, rctx.res, rctx.route

      route =
        '.json': get: generators.list
        '-data.json': get: generators.data
        '.js': get: generators.script
        '.html': get: generators.view
        '/:pm_id.html': get: generators['view/app']
        '/:pm_id.json': get: generators['data/app']
        '/:pm_id/start': post: generators.start
        '/:pm_id/reload': post: generators.reload
        '/:pm_id/restart': post: generators.reload
        '/:pm_id/stop': post: generators.stop
        '': get: (rctx) ->
          (req, res) ->
            res.redirect ctx.site.base+rctx.name+'.html'
        '/': get: (rctx) ->
          (req, res) ->
            res.redirect ctx.site.base+rctx.name+'.html'

      # TODO: see r0.0.6 for module generate export scheme
      for name of route
        for method of route[name]
          ref = ctx.site.base+rctx.name+name
          unless "function" is typeof route[name][method]
            throw Error \
              "Expected callback #{name}:#{method}:#{route[name][method]}"
          r = route[name][method] rctx
          if "object" is typeof r
            throw Error "Expected callback #{name}:#{method}:#{r}"
          ctx.app[method] ref, r

      null

    # List all PM2 procs in JSON
    data: ( rctx ) ->
      (req, res) ->
        res.type 'json'
        res.write JSON.stringify m.procs
        res.end()

    list: ( rctx ) ->
      (req, res) ->
        pm2.list (err, ps_list) ->
          res.type 'json'
          if err
            res.status 500
          res.write JSON.stringify ps_list
          res.end()

    # Describe single PM2 proc in JSON
    'data/app': ( rctx ) ->
      (req, res) ->
        pm2.describe req.params.pm_id, (err, ps_list) ->
          res.type 'json'
          if err
            res.status 500
          res.write JSON.stringify ps_list[0]
          res.end()

    start: ( rctx ) ->
      (req, res) ->
        pm2.gracefulReload req.params.pm_id, ( err ) ->
          res.type 'txt'
          if err
            res.status 500
            res.write err
          res.end()

    stop: ( rctx ) ->
      (req, res) ->
        pm2.stop req.params.pm_id, ( err ) ->
          res.type 'txt'
          if err
            res.status 500
            res.write err
          res.end()

    reload: ( rctx ) ->
      (req, res) ->
        pm2.gracefulReload req.params.pm_id, ( err ) ->
          res.type 'txt'
          if err
            res.status 500
            res.write err
          res.end()

    # Serve JS for view(s?)
    script: ( rctx ) ->
      (req, res) ->
        res.type 'js'
        res.write cc._compileFile listCoffeeFn
        res.end()

    #  XXX: pm2_proc = m.get_by_id parseInt req.params.pm_id, 10

    # Serve HTML list view
    view: ( rctx ) ->
      (req, res) ->

        httprouter.promise.resource(
          hostname: 'localhost'
          port: ctx.app.get('port')
          path: ctx.site.base + rctx.name + '.json'
        ).then ( apps ) ->

          res.type 'html'
          res.write pugrouter.compile listPugFn, {
            compile: rctx.route.options.compile
            merge:
              pid: process.pid
              pm2_base: ctx.site.base+rctx.name
              script: ctx.site.base+rctx.name+'.js'
              options: rctx.options
              query: req.query
              context: rctx
              apps: apps
          }
          res.end()

    # Serve PM2 proc HTML details
    'view/app': ( rctx ) ->
      (req, res) ->

        console.log req.path.substring(0, req.path.length - 5) + '.json'
        httprouter.promise.json(
          hostname: 'localhost'
          port: ctx.app.get('port')
          path: req.path.substring(0, req.path.length - 5) + '.json'
        ).then ( app ) ->

          res.type 'html'
          res.write pugrouter.compile detailPugFn, {
            compile: rctx.route.options.compile
            merge:
              pid: process.pid
              pm2_base: ctx.site.base+rctx.name
              script: ctx.site.base+rctx.name+'.js'
              options: rctx.route.options
              query: req.query
              context: rctx
              app: app
          }
          res.end()



  ### # TODO: see r0.0.6
  route:
    default:
      '/': 'redir:pm2.html'
      '.json': '.data'
      '.html': '.view'
      '.js': '.script'
      '/:pm_id.json': '.data/app'
      '/:pm_id.html': '.view/app'
      '/:pm_id/start': '.start'
      '/:pm_id/reload': '.reload'
      '/:pm_id/stop': '.stop'

  ###
  name: 'pm2'

  generate: generators

