##
##  ====
##
##  Anatomy of a build:
##
##    * The build's filesystem layout
##        * Source directory:
##            * src/
##
##        * Distribution artifacts' destination directory:
##            * dist/
##
##        * Assembly directories:
##            * dist/app/   - collects the app's build results
##            * dist/doc/   - collects the app's code documentation
##
##        * Source directory for per target-environment settings
##            * settings/
##
##        * Tests and reports directories:
##            * test/
##            * test-report/
##
##    * The build's distribution artifacts
##
##        * The application
##        * The application's code documentation
##
##    * The build's target-environment:
##        * local
##        * testing
##        * acceptance
##        * production
##
##    * The build parts:
##        * app<@ if ( i18n ) { @>
##            * i18n<@ } @>
##            * style
##            * target-environment settings
##            * brief
##            * bootstrap
##        * documentation
##
##    * The build's debugging mode:
##        * debugging,      - alias debug
##        * non-debugging,  - alias dist - note the overloading of the 'dist' term.
##
##    * The build packing:
##        * as-is
##        * minified        - alias uglified
##
##    * The build's tests
##
##    * The build tools. These almost map 1-to-1 on the npm-loaded grunt tasks:
##
##        * browserify      - for the app build part
##        * clean
##        * compass         - for the style build part
##        * compress        - for the application and documentation build artifacts
##        * copy
##        * template        - for the bootstrap build part
##        * uglify
##        * yuidoc          - for the documentation build part
##
##      The above all have to do with the actual assembly of the build.
##      Apart from these, there are also:
##
##        * Verification and testing:
##            * coffeelint
##            * coffee_jshint
##            * karma & jasmine
##
##        * Development support tools:
##            * watch
##
##
##  Note that the above factors are not entirely clear-cut:
##
##    * The build's packing and build debugging-type are somewhat intertwined:
##
##        * A debugging build implies as-is packing.
##        * Minified packing implies a non-debugging build.
##
##    * The build environment directly determines the default target-environment settings's build part's source.
##
##    * The build's artifacts are an all-or-nothing deal, currently.
##
##    * The build parts can be processed seperately, but some depend on others:
##          * The bootstrap build part needs a brief.
##          * The app build part will also trigger builds of the <@ if ( i18n ) { @>i18n, <@ } @>style, target-environment settings, brief, and bootstrap
##            build parts.
##
##
##  Mapping to grunt tasks and targets:
##
##  As briefly indicated, the build tools map 1-to-1 onto the npm-loaded grunt tasks.
##  Where applicable, for these tools, build parts and debugging mode map to their task's targets.
##
##  For instance, the `browserify` grunt task is one of the tools needed to build the app part, either in
##  debugging (app_debug), or non-debugging (app_dist) mode.
##
##      browserify:
##          app_dist:
##              <specific config>
##
##          app_debug:
##              <specific config>
##
##
##  Build parts may map to more than one tool. In fact, generally, part builds have three phases:
##
##      * clean phase           - maps to clean task, one target per build part
##      * copy phase            - maps to copy task, one target per build part
##      * construction phase    - maps to part specific tasks, one target per build part and possibly, debugging mode
##
##
##  Some part builds have more phases than these; verification and testing phases for instance.
##
##  Grunt tasks per build part exist, controlling these phases.
##
##
##  Finally, this is how the main grunt commandline tasks are mapped to all of the above:
##
##      * grunt [default]   - shortcut for `grunt dist` unless the `GRUNT_TASKS` environment variable specifies a space separated list of alternative tasks to
##                            run instead;
##
##      * grunt dist        - does a for-production, non-debugging, all-parts, tested, minified build plus artifacts;
##      * grunt debug       - does a for-testing, debugging, all-parts except documentation, tested, as-is build;
##      * grunt dev         - does a for-local, debugging, all-parts except documentation, as-is build;
##                            (Note that this variant doesn't exit. Instead, it'll keep a close watch on
##                            filesystem changes, selectively re-triggering part builds as needed)
##
##
##  The `--target` command line option sets the build target environment.
##  So, for an for-acceptance, non-debugging, all-parts, tested, minified build, do:
##
##      * grunt --target=acceptance
##
##  ====
##

'use strict'

child_process   = require( 'child_process' )
glob            = require( 'glob' )
path            = require( 'path' )
_               = require( 'underscore' )

module.exports = ( grunt ) ->

    grunt.initConfig(

        ##  ------------------------------------------------
        ##  Build configuration
        ##  ------------------------------------------------

        ##
        ##  Contents of npm's 'package.json' file as `<%= npm.pkg.* %>`
        ##  Installed dependencies of npm's 'package.json' file as `<%= npm.installed.* %>`
        ##

        npm:
            pkg:                        grunt.file.readJSON( 'package.json' )
            installed:                  JSON.parse( child_process.execSync( 'npm ls --json --prod --depth 0 --silent' )).dependencies


        ##
        ##  Local data as `<%= build.* %>`
        ##

        build:

            ##
            ##  Filesystem:
            ##

            ##  Included for configurations that need an absolute path.
            ##
            base:                       '<%= process.cwd() %>/'

            source:                     'src/'
            dist:                       'dist/'
            assembly:
                app:                    '<%= build.dist %>app/'
                doc:                    '<%= build.dist %>doc/'

            settings:                   'settings/'
            test:
                src:                    'test/'
                report:                 'test-report/'

            artifactBase:               '<%= build.dist %><%= npm.pkg.name %>-<%= npm.pkg.version %>'

            ##
            ##  This is the default build environment but may be overridden by the 'environment' task
            ##

            environment:                'production'

            ##
            ##  Parts:
            ##

            part:
                app:
                    src:
                        browserify:     '<%= build.source %>app.coffee'

                        ##  To have certain code included only in debug builds, prefix the filename with `debug.`
                        ##
                        debug:          '<%= build.source %>**/debug.*.coffee'

                        lint:           '<%= build.source %>**/*.coffee'

                    ##                  NOTE:   `<%= npm.pkg.main %>` should have `<%= build.dist %>` as its prefix:
                    ##
                    tgt:                '<%= npm.pkg.main %>'

                brief:
                    tgt:                '<%= build.assembly.app %>build.json'

                bootstrap:
                    src:                '<%= build.source %>index.template.html'
                    tgt:                '<%= build.assembly.app %>index.html'

                doc:
                    ##                  NOTE:   Directories to include and to exclude cannot be expressed in a single expression.
                    ##
                    src:                [ '<%= build.source %>', 'vendor' ]
                    srcExclude:         []

                    ##                  NOTE:   `tgt` - must - be a directory.
                    ##
                    tgt:                '<%= build.assembly.doc %>'<@ if ( i18n ) { @>

                i18n:
                    src:                '<%= build.source %>i18n/'
                    tgt:                '<%= build.assembly.app %>i18n/'<@ } @>

                settings:
                    src:                '<%= build.settings %><%= build.environment %>.json'
                    tgt:                '<%= build.assembly.app %>settings.json'

                style:
                    src:
                        copy:           '<%= build.source %>style/'
                        compass:        '<%= build.source %>sass/'
                    tgtDir:             '<%= build.assembly.app %>style/'

                    ##                  NOTE:   This file will be created because the `style.src.compass` dir contains a file 'app.sass'
                    ##                          This will be true for any '*.sass' file, except when its filename contains a leading underscore ('_') character.
                    ##
                    tgt:                '<%= build.part.style.tgtDir %>app.css'


        ##  ------------------------------------------------
        ##  Configuration for each npm-loaded task:target
        ##  ------------------------------------------------
        ##
        ##  Where applicable these task have a target per build part and sometimes, debugging mode.
        ##

        ##
        ##  Compile and bundle your code.
        ##
        ##  https://github.com/jmreidy/grunt-browserify#readme
        ##
        ##  https://github.com/substack/node-browserify#readme
        ##  https://github.com/substack/node-browserify#browserifyfiles--opts
        ##
        ##  https://github.com/substack/browserify-handbook#packagejson
        ##
        ##      file:./package.json
        ##
        ##        - browser : https://github.com/substack/browserify-handbook#browser-field
        ##
        ##          You can define a "browser" field in the package.json of any package that will tell browserify to override lookups for the main field and
        ##          for individual modules.
        ##
        ##          The browser field only applies to the current package. Any mappings you put will not propagate down to its dependencies or up to its
        ##          dependents. This isolation is designed to protect modules from each other so that when you require a module you won't need to worry about
        ##          any system-wide effects it might have. Likewise, you shouldn't need to wory about how your local configuration might adversely affect
        ##          modules far away deep into your dependency graph.
        ##
        ##          See also:
        ##            - https://github.com/substack/node-browserify#browser-field
        ##            - https://gist.github.com/defunctzombie/4339901
        ##
        ##
        ##        - browserify.transform : https://github.com/substack/browserify-handbook#browserifytransform-field
        ##
        ##          You can configure transforms to be automatically applied when a module is loaded in a package's browserify.transform field.
        ##
        ##          Like the "browser" field, transforms configured in package.json will only apply to the local package for the same reasons.
        ##
        ##          See also:
        ##            - https://github.com/substack/node-browserify#browserifytransform
        ##
        ##        - browserify-shim : https://github.com/substack/browserify-handbook#browserify-shim
        ##
        ##          See also:
        ##            - https://github.com/thlorenz/browserify-shim#readme
        ##

        browserify:

            options:

                ##  Transforms are ideally set in 'package.json' as 'browserify.transform'.
                ##
                ##  Shadowed here - commented out - but documented, for easy reference.
                ##
                ##  Browserify transforms are run in order and may modify your source code along the way.
                ##
                ##  You'll typically want to include 'browserify-shim' last.
                ##
                ###
                transform: [
                                        ##  https://github.com/jnordberg/coffeeify#readme
                                        ##
                                        'coffeeify'

                                        ##  https://github.com/epeli/node-hbsfy#readme
                                        ##
                                        'hbsfy'

                                        ##  https://github.com/thlorenz/browserify-shim#readme
                                        ##
                                        'browserify-shim'
                ]
                ###

                ##  Caveat: Using the extra variable `browserifyOptions` to share a common set between the different targets below. Afaict this can't be done
                ##  any other way. (Duplicating doesn't count).
                ##
                browserifyOptions: ( browserifyOptions =

                    ##  Scan all files for process, global, __filename, and __dirname, defining as necessary.
                    ##  With this option npm modules are more likely to work but bundling takes longer.
                    ##
                    ##  When you find yourself using 'browserify-shim', you're likely to want to leave this set to `true`.
                    ##  If not, have a try at setting this to `false` for extra build speed.
                    ##
                    detectGlobals:      true

                    extensions: [
                                        '.coffee'
                                        '.hbs'
                    ]

                    ##  Skip all require() and global parsing for each file in this array.
                    ##  For giant libs like jquery or threejs that don't have any requires or node-style globals but
                    ##  take forever to parse.
                    ##
                    noParse: [
                                        'jquery'
                    ]
                )<@ if ( jqueryCdn ) { @>

                ##  Do not include `jquery` in the output bundle. It is an `npm install`ed dependency, and `require()`d, chiefly, by `Backbone`.
                ##  Instead, a `<script>` tag in the main entry point will load `jquery` from a CDN.
                ##  A 'browserify-shim' will take care of exposing that jquery to the app. (see 'package.json')
                ##  The app will take care of exposing it to Backbone.
                ##
                exclude: [
                                        'jquery'
                ]<@ } @>

            ##  Non-debugging build
            ##
            app_dist:
                files: [
                    src:                '<%= build.part.app.src.browserify %>'
                    dest:               '<%= build.part.app.tgt %>'
                ]

            ##  Debugging build
            ##
            app_debug:
                options:
                    watch:              true
                    browserifyOptions:  _.extend(
                        {}
                    ,
                        browserifyOptions
                    ,
                        debug:          true
                    )

                files: [
                    src:                [ '<%= build.part.app.src.browserify %>', '<%= build.part.app.src.debug %>' ]
                    dest:               '<%= build.part.app.tgt %>'
                ]


        ##
        ##  Remove your previously built build results.
        ##
        ##  https://github.com/gruntjs/grunt-contrib-clean#readme
        ##

        clean:

            ##
            ##  Distribution artifact destination directory:
            ##

            dist:
                files: [
                    src:                '<%= build.dist %>'
                ]

            ##
            ##  Per build part cleaning within the above destination directory:
            ##

            app:
                files: [
                    src:                '<%= build.part.app.tgt %>'
                ]

            brief:
                files: [
                    src:                '<%= build.part.brief.tgt %>'
                ]

            bootstrap:
                files: [
                    src:                '<%= build.part.bootstrap.tgt %>'
                ]

            doc:
                files: [
                    src:                '<%= build.part.doc.tgt %>'
                ]<@ if ( i18n ) { @>

            i18n:
                files: [
                    src:                '<%= build.part.i18n.tgt %>'
                ]<@ } @>

            settings:
                files: [
                    src:                '<%= build.part.settings.tgt %>'
                ]

            style:
                files: [
                    src:                '<%= build.part.style.tgtDir %>'
                ]


        ##
        ##  Delint your coffeescript - before transpilation to javascript.
        ##
        ##  https://github.com/vojtajina/grunt-coffeelint#readme
        ##
        ##  http://www.coffeelint.org/
        ##  file:./coffeelint.json
        ##

        coffeelint:

            options:
                configFile:             'coffeelint.json'

            app:
                files: [
                    src:                '<%= build.part.app.src.lint %>'
                ]

            gruntfile:
                files: [
                    src:                'Gruntfile.coffee'
                ]

            test:
                files: [
                    src:                '<%= build.test.src %>**/*.coffee'
                ]


        ##
        ##  Delint your coffeescript - after transpilation to javascript.
        ##
        ##  https://github.com/bmac/grunt-coffee-jshint#readme
        ##
        ##  https://github.com/Clever/coffee-jshint#readme
        ##  http://www.jshint.com/docs/options/
        ##  http://www.jshint.com/
        ##

        coffee_jshint:

            options:

                ##  NOTE:   The use of browserify and the UMD (Universal Module Definition) pattern implies the legimate use of the globals below.
                ##
                ##  I would have liked to specify these globals and other jshint options through a '.jshintrc' file instead but have been unsuccessful so far.
                ##
                ##  Look at the supplied 'file:./.jshintrc' for further inspiration.
                ##
                globals: [
                                        'define'
                ]

                ##  Caveat: Using the extra variable `jshintOptions` to share a common set between the different targets below. Afaict this can't be done any
                ##  other way. (Duplicating doesn't count).
                ##
                jshintOptions: ( jshintOptions = [

                    ##                  Enforcing options:
                                        'eqeqeq'
                                        'forin'
                                        'noarg'
                                        'nonew'
                                        'undef'
                                        'unused'

                    ##                  Relaxing options:
                                        'debug'
                                        'loopfunc'
                                        'validthis'
                ])

            app:
                options:
                    jshintOptions:      jshintOptions.concat( [
                        ##              Environment options:
                                        'browserify'
                                        'browser'
                                        'devel'
                    ] )

                files:                  '<%= coffeelint.app.files %>'

            gruntfile:
                options:
                    jshintOptions:      jshintOptions.concat( [
                        ##              Environment options:
                                        'node'
                    ] )

                files:                  '<%= coffeelint.gruntfile.files %>'

            test:
                options:
                    jshintOptions:      jshintOptions.concat( [
                        ##              Environment options:
                                        'jasmine'
                                        'node'
                    ] )

                files:                  '<%= coffeelint.test.files %>'


        ##
        ##  Compile your sass to bundled css.
        ##
        ##  https://github.com/gruntjs/grunt-contrib-compass#readme
        ##
        ##  http://compass-style.org/help/documentation/configuration-reference/
        ##
        ##  http://sass-lang.com/documentation/file.SASS_REFERENCE.html#options
        ##  http://sass-lang.com/documentation/file.SASS_REFERENCE.html#output_style
        ##

        compass:

            options:

                ##  This is not a "rails" app `project_type`
                ##
                app:                    'stand_alone'

                ##  Source
                sassDir:                '<%= build.part.style.src.compass %>'

                ##  Destination
                cssDir:                 '<%= build.part.style.tgtDir %>'

                ##  Images and fonts will have been copied here first by means of the `copy:style` task.
                ##
                imagesDir:              '<%= build.part.style.tgtDir %>images/'
                fontsDir:               '<%= build.part.style.tgtDir %>fonts/'

                ##  Compass's asset helper functions should produce urls relative to the stylesheet.
                ##
                relativeAssets:         true

                raw:                    'sass_options = { :property_syntax => :new }\n'

            style_dist:
                options:
                    environment:        'production'
                    outputStyle:        'compressed'

            style_debug:
                options:
                    environment:        'development'
                    outputStyle:        'nested'
                    sourcemap:          true


        ##
        ##  Create your distribution artifacts.
        ##
        ##  https://github.com/gruntjs/grunt-contrib-compress#readme
        ##

        compress:

            app_dist:
                options:
                    archive:            '<%= build.artifactBase %>.zip'

                files: [
                    expand:             true
                    cwd:                '<%= build.assembly.app %>'
                    src:                '**/*'
                    dest:               '.'
                ]

            app_debug:
                options:
                    archive:            '<%= build.artifactBase %>-debug.zip'

                files:                  '<%= compress.app_dist.files %>'

            doc:
                options:
                    archive:            '<%= build.artifactBase %>-doc.zip'

                files: [
                    expand:             true
                    cwd:                '<%= build.assembly.doc %>'
                    src:                '**/*'
                    dest:               '.'
                ]


        ##
        ##  Copy your build bits that needs no transformation.
        ##
        ##  https://github.com/gruntjs/grunt-contrib-copy#readme
        ##

        copy:

            options:
                mode:                   true
                timestamp:              true<@ if ( i18n ) { @>

            i18n:
                files: [
                    filter:             'isFile'
                    expand:             true
                    cwd:                '<%= build.part.i18n.src %>'
                    src:                '**/*'
                    dest:               '<%= build.part.i18n.tgt %>'
                ]<@ } @>

            settings:
                files: [
                    filter:             'isFile'
                    src:                '<%= build.part.settings.src %>'
                    dest:               '<%= build.part.settings.tgt %>'
                ]

            style:
                files: [
                    filter:             'isFile'
                    expand:             true
                    cwd:                '<%= build.part.style.src.copy %>'
                    src:                '**/*'
                    dest:               '<%= build.part.style.tgtDir %>'
                ]


        ##
        ##  Test your code.
        ##
        ##  https://github.com/karma-runner/grunt-karma#readme
        ##
        ##  Karma:
        ##      https://github.com/karma-runner/karma#readme
        ##      http://karma-runner.github.io/1.0/
        ##
        ##  Browserify:
        ##      https://github.com/nikku/karma-browserify#readme
        ##
        ##      See also the `browserify:` section in this config for more info on browserify and **its** preprocessors:
        ##
        ##          coffeeify
        ##          hbsfy
        ##          browserify-shim
        ##
        ##  Jasmine:
        ##      https://github.com/karma-runner/karma-jasmine#readme
        ##      https://github.com/jasmine/jasmine#readme
        ##      http://jasmine.github.io/
        ##      http://tryjasmine.com/
        ##
        ##  PhantomJS:
        ##      https://github.com/karma-runner/karma-phantomjs-launcher#readme
        ##      https://github.com/Medium/phantomjs#readme
        ##      http://phantomjs.org/
        ##
        ##
        ##  The following combo of posts has been instrumental in getting this to work:
        ##
        ##      http://nick.perfectedz.com/browserify-unit-testing-p1/
        ##      http://nick.perfectedz.com/browserify-unit-testing-p2/
        ##

        karma:

            ##  https://karma-runner.github.io/1.0/config/configuration-file.html
            ##
            options:
                basePath:               '<%= build.test.src %>'

                ##  https://karma-runner.github.io/1.0/config/browsers.html
                ##
                browsers: [
                                        'PhantomJS'
                ]

                ##  https://karma-runner.github.io/1.0/config/files.html
                ##
                exclude:                []
                files:                  []

                frameworks: [
                                        ##  https://github.com/nikku/karma-browserify#usage
                                        ##
                                        ##      "Add browserify as a framework to your Karma configuration file."
                                        ##
                                        'browserify'
                                        'jasmine'
                ]

                hostname:               'localhost'

                httpServerOptions:      {}

                logLevel:               'INFO'

                loggers: [

                    ##  https://github.com/nomiddlename/log4js-node#readme
                    ##
                    type:               'console'
                ]

                ##  https://karma-runner.github.io/1.0/config/plugins.html
                ##
                ##  By default, Karma loads all sibling NPM modules which have a name starting with karma-*.
                ##  We like to be explicit, so:
                ##
                plugins: [
                                        'karma-browserify'
                                        'karma-jasmine'
                                        'karma-phantomjs-launcher'
                ]

                port:                   9876

                ##  https://karma-runner.github.io/1.0/config/preprocessors.html
                ##
                ##  Note that there's no need for a `karma-coffee-preprocessor` because that's taken care of by browserify.
                ##
                preprocessors:

                    'unit/init.coffee': [
                                        'browserify'
                    ]

                    '**/spec/**/*': [
                                        'browserify'
                    ]

                protocol:               'http:'

                ##  https://karma-runner.github.io/1.0/config/files.html
                ##
                ##      Section: Loading Assets
                ##
                proxies:                {}

                ##  Not related to `karma.options.proxies` setting above.
                ##
                ##  Whether or not Karma or any browsers should raise an error when an inavlid SSL certificate is found.
                ##
                proxyValidateSSL:       true

                reporters: [
                                        'progress'
                ]

                urlRoot:                '/'


                ##  Continuous integration mode:
                ##
                autoWatch:              false
                background:             false
                colors:                 false
                singleRun:              true


                ##
                ##  Plugin specific config:
                ##

                ##  Browserify:
                ##
                ##  Reuse `browserify.options.browserifyOptions`.
                ##
                browserify:
                    _.extend(
                        {}
                    ,
                        browserifyOptions
                    ,
                        debug:          true<@ if ( jqueryCdn ) { @>

                        ##  This is the `karma-browserify` equivalent of `browserify.options.exclude`.
                        ##
                        configure:      ( bundle ) -> bundle.on( 'prebundle', () -> bundle.external( 'jquery' ); return ); return<@ } @>
                    )


            unit_ci:
                options:

                    ##  Note that `files` is part of this task's extenstion of `karma.options` and its files are therefore relative to `karma.options.basePath`.
                    ##  Despite appearance, this is **not** a grunt task's `files` declaration.
                    ##
                    ##  https://karma-runner.github.io/1.0/config/files.html
                    ##
                    files: [<@ if ( jqueryCdn ) { @>
                        ##  External dependencies to be loaded as `<script/>`s.
                        ##
                        ##    * jQuery
                        ##
                        ##      jQuery is in this list because the `browserify-shim` config in `package.json` specifies that `require('jquery')` will return
                        ##      the global jQuery object:
                        ##
                        ##      It does this because `jQuery` will be excluded from a browserify build as specified at:
                        ##
                        ##          `browserify.options.exclude`
                        ##
                        ##      We have mimicked that behaviour through `karma.options.browserify.configure`.
                        ##
                        ##      Loading `jquery` from a cdn will also work, but doing it like this will minimize impact on testing during continuous
                        ##      integration ('https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js').
                        ##
                        '../node_modules/jquery/dist/jquery.js'
                    ,<@ } @>
                        ##  Setup / initialization before all tests.
                        ##
                        'unit/init.coffee'
                    ,
                        ##  The unit tests' specs.
                        ##
                        pattern:        'unit/spec/**/*'
                    ,
                        ##  Assets; non-code files.
                        ##  See `proxies` section, below, to see how urls are mapped to these
                        ##
                        pattern:        'unit/asset/**/*'

                        included:       false
                        served:         true
                    ]

                    proxies:
                        '/settings.json':
                            '/base/unit/asset/settings.json'


            unit_dev:
                options:

                    autoWatch:          true
                    colors:             true
                    singleRun:          false

                    files:              '<%= karma.unit_ci.options.files %>'
                    proxies:            '<%= karma.unit_ci.options.proxies %>'


        ##
        ##  Substitute build targets and - for cache-busting reasons - a build-run identifier into your app's main
        ##  entry point.
        ##
        ##  https://github.com/mathiasbynens/grunt-template#readme
        ##

        template:

            bootstrap:
                options:
                    data: () ->

                        file    = grunt.config( 'build.part.brief.tgt' )

                        ##  Don't let grunt handle the exception if this fails.
                        ##
                        brief   = do () ->
                            ### jshint  unused: false   ###
                            try grunt.file.readJSON( file ) catch dummy

                        grunt.fail.fatal( "Unable to read the build brief (\"#{file}\"). Wasn't it created?" ) unless brief?.timestamp

                        environment:    brief.environment

                        app:            path.relative( grunt.config( 'build.assembly.app' ), grunt.config( 'build.part.app.tgt' ))
                        style:          path.relative( grunt.config( 'build.assembly.app' ), grunt.config( 'build.part.style.tgt' ))
                        styleBase:      path.relative( grunt.config( 'build.assembly.app' ), grunt.config( 'build.part.style.tgtDir' ))

                        buildRun:       brief.buildNumber or brief.timestamp
                        debugging:      brief.debugging

                        npm:            grunt.config( 'npm' )

                files: [
                    src:                '<%= build.part.bootstrap.src %>'
                    dest:               '<%= build.part.bootstrap.tgt %>'
                ]


        ##
        ##  Minify your compiled and bundled code.
        ##
        ##  https://github.com/gruntjs/grunt-contrib-uglify#readme
        ##
        ##  https://github.com/mishoo/UglifyJS2#readme
        ##  http://lisperator.net/uglifyjs/
        ##

        uglify:

            app:
                options:
                    compress:
                        drop_console:   true

                files: [
                    src:                '<%= build.part.app.tgt %>'
                    dest:               '<%= build.part.app.tgt %>'
                ]


        ##
        ##  https://github.com/gruntjs/grunt-contrib-watch#readme
        ##
        ##  Note that 'watch' isn't your garden-variety multi-task even though its config makes it deceivingly look
        ##  like one.
        ##
        ##  Its intended mode of operation is as a (non-multi-) task, like: `grunt watch`.
        ##  Doing so will make it watch **all** targets' files and fork their associated `tasks` on any detected change.
        ##
        ##  That doesn't mean that it isn't possible to, say, `grunt watch:coffee`, it is, but its a one or all choice;
        ##  Making it work for multiple targets (except all) is not possible.
        ##
        ##  Also note that a value for `files` can only be a pattern string or an array of such values
        ##  (yes that definition is recursive).
        ##

        watch:
            options:
                spawn:              false

            ##
            ##  The browserify task does its own watching.
            ##
            ##  But for linting purposes we watch all coffee files here too.
            ##

            app:
                files:                  '<%= build.part.app.src.lint %>'
                tasks:                  'lint:app'

            bootstrap:
                options:
                    livereload:         true

                files: [
                    ##                  Watch for changed assembly - targets -
                    ##
                                        '<%= build.part.app.tgt %>'<@ if ( i18n ) { @>
                                        '<%= build.part.i18n.tgt %>**/*'<@ } @>
                                        '<%= build.part.settings.tgt %>'
                                        '<%= build.part.style.tgtDir %>**/*.css'

                    ##                  Watch for changed bootstrap - source -
                    ##
                                        '<%= build.part.bootstrap.src %>'
                ]
                tasks: [
                                        'brief:debug'
                                        'bootstrap:debug'
                ]<@ if ( i18n ) { @>

            i18n:
                files: [
                                        '<%= build.part.i18n.src %>**/*'
                ]
                tasks:                  'i18n'<@ } @>

            settings:
                files:                  '<%= build.part.settings.src %>'
                tasks: [
                                        'environment:<%= build.environment %>'
                                        'settings'
                ]

            style:
                files: [
                                        '<%= build.part.style.src.copy %>**/*'
                                        '<%= build.part.style.src.compass %>**/*'
                ]
                tasks:                  'style:debug'


        ##
        ##  Generate your code's documentation
        ##
        ##  https://github.com/gruntjs/grunt-contrib-yuidoc#readme
        ##
        ##  http://yui.github.io/yuidoc/args/#command-line
        ##  http://yui.github.io/yuidoc/args/#yuidocjson-fields
        ##

        yuidoc:

            app:
                name:                   '<%= npm.pkg.name %>'
                description:            '<%= npm.pkg.description %>'
                url:                    '<%= npm.pkg.homepage %>'
                version:                '<%= npm.pkg.version %>'

                options:
                    ##                  NOTE:   Globbing patterns in `paths` cannot match - any - symbolically linked directories; yuidoc will not find them.
                    ##
                    ##                          Therefore, the 'doc' task will do any globbing expansion beforehand, and then reset `paths` to the result.
                    ##
                    paths:              '<%= build.part.doc.src %>'

                    ##                  NOTE:   `exclude` must be a string containing comma separated paths to directories.
                    ##
                    ##                          This is exactly what the template expansion below will achieve:
                    ##
                    exclude:            '<%= grunt.file.expand( grunt.config( "build.part.doc.srcExclude" )) %>'

                    ##                  NOTE:   Yuidoc will empty the `outdir` directory before construction.
                    ##
                    outdir:             '<%= build.part.doc.tgt %>'

                    extension:          '.coffee'
                    syntaxtype:         'coffee'

                    linkNatives:        true
                    tabtospace:         4

                    ##                  NOTE:   The list of external YUIDoc documentation sets, as bundled with installed packages will be dynamically
                    ##                          established when the 'doc' task is run.
                    ##
                    external:           {}

    )


    ##  ================================================
    ##  The build tools, npm-loaded tasks:
    ##
    ##  Be sure to have `npm install <plugin> --save-dev`-ed each of these:
    ##  ================================================

    grunt.loadNpmTasks( 'grunt-browserify' )
    grunt.loadNpmTasks( 'grunt-coffeelint' )
    grunt.loadNpmTasks( 'grunt-coffee-jshint' )
    grunt.loadNpmTasks( 'grunt-contrib-clean' )
    grunt.loadNpmTasks( 'grunt-contrib-compass' )
    grunt.loadNpmTasks( 'grunt-contrib-compress' )
    grunt.loadNpmTasks( 'grunt-contrib-copy' )
    grunt.loadNpmTasks( 'grunt-contrib-uglify' )
    grunt.loadNpmTasks( 'grunt-contrib-watch' )
    grunt.loadNpmTasks( 'grunt-contrib-yuidoc' )
    grunt.loadNpmTasks( 'grunt-karma' )
    grunt.loadNpmTasks( 'grunt-template' )


    ##  ================================================
    ##  The build tools, internally defined tasks:
    ##  ================================================

    grunt.registerTask(
        'create_brief'
        'Generate \'build.json\' file containing the build details'
        ( debugging ) ->

            stamp       = new Date()
            buildNumber = process.env.BUILD_NUMBER

            unless buildNumber
                localBuild  = 'build.localNumber'
                localNumber = grunt.config( localBuild ) or 0
                buildNumber = "+#{localNumber}"

                grunt.config.set( localBuild, localNumber + 1 )

            buildInfo   =
                buildNumber:    buildNumber
                buildId:        process.env.BUILD_ID or null
                revision:       process.env.GIT_COMMIT or 'working dir'

                grunted:        grunt.template.date( stamp, 'yyyy mmm dd HH:MM:ss' )
                environment:    grunt.config( 'build.environment' )
                debugging:      ( debugging is 'debug' )

                name:           grunt.config( 'npm.pkg.name' )
                version:        grunt.config( 'npm.pkg.version' )

                timestamp:      +stamp

            grunt.file.write( grunt.config( 'build.part.brief.tgt' ), JSON.stringify( buildInfo, null, 4 ))

            return
    )

    grunt.registerTask(
        'environment'
        'Set the target environment'
        ( environment ) ->

            if ( ( override = grunt.option( 'target' ) ? process.env.GRUNT_TARGET )? and override isnt environment )
                grunt.log.ok( "Overriding target environment to \"#{override}\"" )
                environment = override

            grunt.config.set( 'build.environment', environment ) if environment?

            return
    )


    ##  ================================================
    ##  Per build part tasks:
    ##  ================================================

    grunt.registerTask(
        'app'
        'Build the app.'
        ( debugging ) ->
            grunt.task.run(
                'lint:app'

                'clean:app'

                "browserify:app_#{debugging}"<@ if ( i18n ) { @>
                'i18n'<@ } @>
                'settings'
                "style:#{debugging}"

                ##  brief before bootstrap

                "brief:#{debugging}"
                'bootstrap'
            )
    )

    grunt.registerTask(
        'brief'
        'Build the build\'s brief.'
        ( debugging ) ->
            grunt.task.run(
                'clean:brief'
                "create_brief:#{debugging}"
            )
    )

    grunt.registerTask(
        'doc'
        'Build the documentation'
        () ->

            ##  Fully, expand any globs in 'build.part.doc.src' before passing the result to `yuidoc`.
            ##
            ##  Because `yuidoc` expects either a string containing a single directory path or an array of such strings
            ##  We cannot use the grunt template mechanism to do the substitution.
            ##
            path = 'yuidoc.app.options.paths'

            grunt.config( path, grunt.file.expand( grunt.config( path )))

            ##

            ##  Include any installed npm dependencies with bundled YUIDoc documentation, signalled by the presence of a `data.json` and some duck typing.
            ##
            externals =
                glob
                    .sync(
                        "node_modules/@(#{
                            Object
                                ##  Names of installed `dependencies`.
                                ##
                                .keys( grunt.config( 'npm.installed' ))
                                ##
                                ##  Escaped for use in this glob expression.
                                ##
                                .map( ( name ) -> name.replace( /[!()*+?@\[\]^{|}]/g, '\\$&' ) )
                                .join( '|' )
                        })/**/data.json"
                    )
                    .filter( ( path ) ->

                        data = grunt.file.readJSON( path )

                        ##  Does it walk like a duck?
                        ##
                        for prop in [ 'project', 'files', 'modules', 'classes', 'elements', 'classitems', 'warnings' ]
                            return false unless data[ prop ]

                        return true

                    )
                    .map( ( path ) ->

                        base:   "/#{ path.slice( 0, -( 'data.json'.length )) }"
                        json:   path
                    )


            grunt.config( 'yuidoc.app.options.external', data: externals ) if externals.length

            ##

            grunt.task.run(
                'clean:doc'
                'yuidoc:app'
            )
    )

    grunt.registerTask(
        'bootstrap'
        'Build the app\'s startup entry point.'
        [
            'clean:bootstrap'
            'template:bootstrap'
        ]
    )<@ if ( i18n ) { @>

    grunt.registerTask(
        'i18n'
        'Build the app\'s internationalization files'
        [
            'clean:i18n'
            'copy:i18n'
        ]
    )<@ } @>

    grunt.registerTask(
        'lint'
        'Look for lint in the app\'s code'
        ( target = '' ) ->
            grunt.task.run(
                "coffeelint:#{target}"
                "coffee_jshint:#{target}"
            )
    )

    grunt.registerTask(
        'settings'
        'Build the build\'s target environment\'s settings.'
        [
            'clean:settings'
            'copy:settings'
        ]
    )

    grunt.registerTask(
        'style'
        'Build the app\'s stylesheet and related assets'
        ( debugging ) ->
            grunt.task.run(
                'clean:style'
                'copy:style'
                "compass:style_#{debugging}"
            )
    )

    grunt.registerTask(
        'test'
        'Unit test the app\'s code'
        ( mode = 'ci' ) ->
            grunt.task.run(
                "karma:unit_#{mode}"
            )
    )

    ##  ================================================
    ##  Command line tasks; the usual suspects anyway:
    ##  ================================================

    grunt.registerTask(
        'default'
        'Shortcut for `grunt dist` unless the `GRUNT_TASKS` environment variable specifies a space separated list of alternative tasks to run instead.'
        () ->
            tasks = process.env.GRUNT_TASKS?.split( /\s/ )

            grunt.task.run( if tasks?.length then tasks else 'dist' )
    )

    grunt.registerTask(
        'dist'
        [
            'clean:dist'

            'environment:production'

            'app:dist'

            'uglify:app'

            'test:ci'

            'compress:app_dist'

            'doc'
            'compress:doc'
        ]
    )

    grunt.registerTask(
        'debug'
        [
            'clean:dist'

            'environment:testing'

            'app:debug'

            'test:ci'

            'compress:app_debug'
        ]
    )

    grunt.registerTask(
        'dev'
        [
            'clean:dist'

            'environment:local'

            'app:debug'

            'watch'
        ]
    )
