    program = require 'commander'
    fs = require 'fs-extra'
    path = require 'path'

    refresh = require '../lib/refresh'

    log = require '../lib/log'
    server = require '../server'
    utils = require '../lib/utils'
    notify = require '../lib/notify'
    generator = require '../lib/generator'
    utils = require '../lib/utils'

    bake = require "#{global.pkgBasePath}/tasks/bake"
   
    configPath = "#{process.env.HOME}/.pages"
    templatePath = "#{process.env.HOME}/.pages/templates"
    depsPath = "#{process.env.HOME}/.pages/deps"

    throwError = (error) ->

      notify 'Pages Error', error

      throw new Error error 

    setDev = () -> process.env.NODE_ENV = 'development'

    setProd = () -> process.env.NODE_ENV = 'production'

    setTest = () -> process.env.NODE_ENV = 'test'

    createTemplateDir = (cb) -> utils.runWithCb "mkdir #{configPath}", () -> utils.runWithCb "mkdir #{templatePath}", () -> cb()

    catchExceptions = () -> process.on 'uncaughtException', (error) -> log.err error

    setPort = (opts) -> 

      if !opts.port then opts.port = 80 else opts.port = parseInt opts.port 

      process.env.PORT = opts.port

      opts

    sanitize = (text) -> text.replace(/^[\s,]+/,"").replace(/[\s,]+$/,"").replace(/\s*,+\s*(,+\s*)*/g,",").split(' ').join('-').toLowerCase()

    camelCase = (text) -> text.replace(/^[\s,]+/,"").replace(/[\s,]+$/,"").replace(/\s*,+\s*(,+\s*)*/g,",").split(' ').join('-').toLowerCase()

    isTemplate = (cb) -> fs.exists templatePath, cb
    
_todo_ This returns extremely slow if lots of files, just look for adequate file.

    ifNotPagesApp = (cb) -> 

      try 

        require "#{process.cwd()}/pages"

        throwError 'Already inside a Pages application, aborting.'

      catch error 

        cb() 

    ifPagesApp = (cb) -> 

      try 

        require "#{process.cwd()}/pages"

        cb()

      catch error 

        throwError 'Invalid Pages application, aborting.'

    taskRunner = (taskName, opts, cb) ->

      try 

        task = require "#{process.cwd()}/tasks/#{taskName}"
        task opts, () -> if cb then cb() else done()

      catch error

Is task part of Pages?

        try 

          task = require "#{global.pkgBasePath}/tasks/#{taskName}"
          task opts, () -> if cb then cb() else done()

        catch error

          console.log error

          throwError "No tasks found which match tasks/#{taskName}."

    serverRunner = (opts) -> 

      opts = setPort opts

      if opts.cached then global.cached = true else global.cached = false

Use compiled assets.

      if opts.packaged

        server.run () ->

          refresh opts.browser

          done "Pages application running on port #{process.env.PORT}."

Build the assets based on the environment.

      else

        taskRunner 'bake', opts, () -> server.run () -> 

          refresh opts.browser

          done "Pages application running on port #{process.env.PORT}."

Generator validation.

    validate = (opts) ->

      throwError 'No type specified.'  if !opts.type
      throwError 'No name specified.'  if !opts.name

      types = [
        'app'
        'angular_module'
        'page'
        'server_module'
      ]

      throwError 'Invalid type specified`.'  if types.indexOf(opts.type) is -1

      switch opts.type 

        when 'page'

          throwError 'No route specified.'  if !opts.route

      return true

    done = (msg) -> if msg then log.ok msg else log.ok 'Alright, alright.' 

## Public API ##

    program
      .version JSON.parse(fs.readFileSync("#{global.pkgBasePath}/package.json")).version

### config ###

This command allows you to configure Pages.

    program.command('config')
      .description('Configure Pages.')
      .option('--skeleton [skeleton]', 'Configure the default application skeleton by providing a local folder path.')
      .option('--destroy-skeleton', 'Remove the configured application skeleton.')
      .action (opts) ->

        if opts.destroySkeleton

          utils.runWithCb "rm -rf #{templatePath}", () -> done 'Removed configured application skeleton.'

          return 

        isTemplate (template) ->

          setDev()

          throwError 'No skeleton specified.'  if !opts.skeleton

Strip.

          opts.skeleton = opts.skeleton.substring(0, opts.skeleton.length - 1)  if opts.skeleton.substring(opts.skeleton.length - 1, opts.skeleton.length) is '/'

          copyTemplate = () ->

            utils.copyDir 
              from: opts.skeleton
              to: templatePath
            , done

          if template

            utils.runWithCb "rm -rf #{templatePath}", () -> createTemplateDir () -> copyTemplate()

          else

            createTemplateDir () -> copyTemplate()

### generate ###

This command allows us to generate a Pages application and its core pieces (as AngularJS modules).

    program.command('generate')
      .description('Genererate AngularJS applications, angular_modules, pages, and server_modules.')
      .option('--type [type]', 'The component to generate, "app", "angular_module", "page", or "server_module".')
      .option('--name [name]', 'The name of the generated component.')
      .action (opts) ->

        ifNotPagesApp () ->

          isTemplate (template) ->

            opts.template = template

            setDev()

            validate opts

            switch opts.type 
            
              when 'app'

                opts.APPNAME = sanitize opts.name

                generator.app opts, done

              when 'server_module'

                opts.NAME = sanitize opts.name

                generator.server_module opts, done

### task ###

This command allows us to invoke Pages Tasks.

    program
      .command('task [taskName]')
      .description('Invoke Pages Tasks: "bake", "assets".')
      .option('--env [env]', 'The environment to invoke the task in, `development` or `production` (if the Task does not set it).')
      .action (taskName, opts) ->

        ifPagesApp () ->

          isTemplate (template) ->

            opts.template = template

            if opts.env and opts.env is 'production' then setProd() else setDev()

            throwError 'No task specified.'  if !taskName

            taskRunner taskName, opts

### run ###

This command allows us to run a standard web server in `development` mode while also utilizing Pages Modules at both core and app levels.

    program
      .command('run')
      .description('Run a Pages application in `development` mode.')
      .option('--port [port]', 'The port to run the HTTP server on, defaults to `80`.')
      .option('--browser [browser', 'The browser to reload, `chrome` or `safari`.')
      .option('--packaged', 'The app has been `baked`, and this starts the server with pre-compiled assets`.')
      .action (opts) ->

        ifPagesApp () ->

          setDev()

          utils.runWithCb "mkdir #{configPath}", () -> utils.runWithCb "mkdir #{depsPath}", () -> 

            opts.watch = true

            serverRunner opts 

### start ###

This command allows us to run a standard web server in `production` mode while also utilizing Pages Modules at both core and app levels.

    program
      .command('start')
      .description('Run a Pages application in `production` mode.')
      .option('--port [port]', 'The port to run the HTTP server on, defaults to `80`.')
      .option('--browser [browser', 'The browser to reload, `chrome` or `safari`.')
      .option('--packaged', 'The app has been `baked`, and this starts the server with pre-compiled assets`.')
      .action (opts) ->

        ifPagesApp () ->

          setProd()
          catchExceptions()

          opts.watch = false

          serverRunner opts 

### test ###

This command allows us to test both server and client code.

_todo_ Expose client tests.

    program.command('test [file]')
      .description('Run the tests inside the test/ or server_modules/test directory, or specify a file. ')
      .option('--die', 'On a test fail, stop running tests.')
      .option('--timeout [timeout]', 'How long each test should take, defaults to 10 seconds.')
      .action (file, opts) ->

        setTest()

        if file then file = "./test/#{file}.coffee" else file = './test'
        if opts.die then die = '-b' else die = ''
        if opts.timeout then timeout = parseInt(opts.timeout) * 1000 else timeout = 10 * 1000

        utils.runWithCb "#{global.pkgBasePath}/node_modules/mocha/bin/mocha --compilers coffee:coffee-script --require coffee-script #{file} --reporter spec --require should -t #{timeout} --colors -b", done

    program.parse process.argv
    program.help()  unless program.args.length