# **page.coffee**
# Module for interacting with pages persisted on the server.
# Everything is stored using json flat files.

#### Requires ####
fs = require 'fs'
path = require 'path'
events = require 'events'
glob = require 'glob'

mkdirp = require 'mkdirp'
async = require 'async'

random_id = require './random_id'
synopsis = require 'wiki-client/lib/synopsis'

# Export a function that generates a page handler
# when called with options object.
module.exports = exports = (argv) ->
  mkdirp argv.db, (e) ->
    if e then throw e

  #### Private utility methods. ####
  load_parse = (loc, cb, annotations={}) ->
    fs.readFile(loc, (err, data) ->
      return cb(err) if err
      try 
        page = JSON.parse(data)
      catch e
        return cb(e)
      for key, val of annotations
        page[key] = val
      cb(null, page)
    )

  load_parse_copy = (defloc, file, cb) ->
    fs.readFile(defloc, (err, data) ->
      if err then cb(err)
      try
        page = JSON.parse(data)
      catch e
        return cb(e)
      cb(null, page)
      itself.put(file, page, (err) ->
        if err then cb(err)
      )
    )

  # Reads and writes are async, but serially queued to avoid race conditions.
  queue = []

  # Main file io function, when called without page it reads,
  # when called with page it writes.
  fileio = (file, page, cb) ->
    loc = path.join(argv.db, file)
    unless page?
      fs.exists(loc, (exists) =>
        if exists
          load_parse(loc, cb)
        else
          defloc = path.join(argv.root, 'default-data', 'pages', file)
          fs.exists(defloc, (exists) ->
            if exists
              load_parse(defloc, cb)
            else

              glob "wiki-plugin-*/pages", {cwd: argv.packageDir}, (e, plugins) ->
                if e then return cb(e)
                giveUp = do ->
                  count = plugins.length
                  return ->
                    count -= 1
                    if count is 0
                      cb(null, 'Page not found', 404)

                for plugin in plugins
                  do ->
                    pluginName = plugin.slice(12, -6)
                    pluginloc = path.join(argv.packageDir, plugin, file)
                    fs.exists(pluginloc, (exists) ->
                      if exists
                        load_parse(pluginloc, cb, {plugin})
                      else
                        giveUp()
                    )
          )
      )
    else
      page = JSON.stringify(page, null, 2)
      fs.exists(path.dirname(loc), (exists) ->
        if exists
          fs.writeFile(loc, page, (err) ->
            cb(err)
          )
        else
          mkdirp(path.dirname(loc), (err) ->
            if err then cb(err)
            fs.writeFile(loc, page, (err) ->
              cb(err)
            )
          )
      )

  # Control variable that tells if the serial queue is currently working.
  # Set back to false when all jobs are complete.
  working = false

  # Keep file io working on queued jobs, but don't block the main thread.
  serial = (item) ->
    if item
      itself.start()
      fileio(item.file, item.page, (err, data, status) ->
        process.nextTick( ->
          serial(queue.shift())
        )
        item.cb(err, data, status)
      )
    else
      itself.stop()

  #### Public stuff ####
  # Make the exported object an instance of EventEmitter
  # so other modules can tell if it is working or not.
  itself = new events.EventEmitter
  itself.start = ->
    working = true
    @emit 'working'
  itself.stop = ->
    working = false
    @emit 'finished'

  itself.isWorking = ->
    working

  # get method takes a slug and a callback, adding them to the queue,
  # starting serial if it isn't already working.
  itself.get = (file, cb) ->
    queue.push({file, page: null, cb})
    serial(queue.shift()) unless working

  # put takes a slugged name, the page as a json object, and a callback.
  # adds them to the queue, and starts it unless it is working.
  itself.put =  (file, page, cb) ->
      queue.push({file, page, cb})
      serial(queue.shift()) unless working

  itself.pages = (cb) ->
    fs.readdir argv.db, (e, files) ->
      return cb(e) if e
      # used to make sure all of the files are read 
      # and processesed in the site map before responding
      doSitemap = (file, cb) ->
        itself.get file, (e, page, status) ->
          return cb() if file.match /^\./
          if e 
            console.log 'Problem building sitemap:', file, 'e: ', e
            return cb() # Ignore errors in the pagehandler get.
          cb null, {
            slug     : file
            title    : page.title
            date     : page.journal and page.journal.length > 0 and page.journal.pop().date
            synopsis : synopsis(page)
          }

      async.map files, doSitemap, (e, sitemap) ->
        return cb(e) if e
        cb null, sitemap.filter (item) -> if item? then true 

  itself
