var spritesmith = require('spritesmith'),
    json2css = require('json2css'),
	_ = require('underscore'),
	fs = require('fs'),
	path = require('path'),
	url = require('url2');

function ExtFormat() {
  this.formatObj = {};
}
ExtFormat.prototype = {
  'add': function (name, val) {
    this.formatObj[name] = val;
  },
  'get': function (filepath) {
    // Grab the extension from the filepath
    var ext = path.extname(filepath),
        lowerExt = ext.toLowerCase();

    // Look up the file extenion from our format object
    var formatObj = this.formatObj,
        format = formatObj[lowerExt];
    return format;
  }
};

// Create img and css formats
var imgFormats = new ExtFormat(),
    cssFormats = new ExtFormat();

// Add our img formats
imgFormats.add('.png', 'png');
imgFormats.add('.jpg', 'jpeg');
imgFormats.add('.jpeg', 'jpeg');

// Add our css formats
cssFormats.add('.styl', 'stylus');
cssFormats.add('.stylus', 'stylus');
cssFormats.add('.sass', 'sass');
cssFormats.add('.scss', 'scss');
cssFormats.add('.less', 'less');
cssFormats.add('.json', 'json');

module.exports = function (grunt) {
	'use strict';

	// Create a SpriteMaker function
	function SpriteMaker() {
		var data = this.data,
			that = this,
			options = this.options({
				engine: 'auto',
				algorithm: 'top-down',
			});

		grunt.verbose.writeflags(options, 'Options');
		grunt.verbose.writeflags(data, 'Data');

		// Create an async callback
		var cb = this.async(),
			spriteCount = 0;

		this.files.forEach(function(sprite) {
			var exportOpts = sprite.imgOpts || {};
			_.defaults(exportOpts, options.imgOpts);
			_.defaults(exportOpts, {'format': imgFormats.get(sprite.dest) || 'png'});

			var spritesmithParams = {
				src: sprite.src,
				engine: options.engine,
				algorithm: options.algorithm,
				exportOpts: exportOpts
			};
			spritesmith(spritesmithParams, function (err, result) {
				// If an error occurred, callback with it
				if (err) {
					grunt.fatal(err);
					return cb(err);
				}

				// Otherwise, write out the result to destImg
				var destDir = path.dirname(sprite.dest);
				grunt.file.mkdir(destDir);
				fs.writeFileSync(sprite.dest, result.image, 'binary');

				// Otherwise, print a success message.
				grunt.log.writeln('Sprite "' + sprite.dest + '" created.');

// FIXME support processor in any config location
				if (typeof sprite.processor === 'function') {
					sprite.processor(grunt, data, sprite, result);
				}

				// Callback
				spriteCount++;
				if (spriteCount == that.files.length) {
					cb(true);
				}
			});
		});
	}

	// Export the SpriteMaker function
	grunt.registerMultiTask('sprite', 'Spritesheet making utility', SpriteMaker);
};

module.exports.cssFile = function(cssFile, opts) {
	var options = opts || {};
	return function(grunt, that, sprite, result) {

		// Generate a listing of CSS variables
		var coordinates = result.coordinates,
			cleanCoords = {};

		// Clean up the file name of the file
		Object.getOwnPropertyNames(coordinates).forEach(function (file) {
			// Extract the image name (exlcuding extension)
			var fullname = path.basename(file),
					nameParts = fullname.split('.');

			// If there is are more than 2 parts, pop the last one
			if (nameParts.length >= 2) {
				nameParts.pop();
			}

			// Extract out our name
			var name = nameParts.join('.'),
					coords = coordinates[file];

			// Save the cleaned name and coordinates
			cleanCoords[name] = coords;
		});

		// Render the variables via json2css
		var cssFormat = options.format || cssFormats.get(cssFile) || 'json',
				spritePath = options.imgPath || url.relative(cssFile, sprite.dest),
				formatOpts = {
					'spritePath': spritePath
				},
				cssStr = json2css(cleanCoords, {'format': cssFormat, 'formatOpts': formatOpts});

		// Write it out to the CSS file
		var cssDir = path.dirname(cssFile);
		grunt.file.mkdir(cssDir);
		fs.writeFileSync(cssFile, cssStr, 'utf8');

		grunt.log.writeln('CSS "' + cssFile + '" created.');

		return true;
	};
};

@@ -1306,99 +1306,8 @@
 );%0A%0A
-%09%09grunt.verbose.writeflags(options, 'Options');%0A%09%09grunt.verbose.writeflags(data, 'Data');%0A%0A
 %09%09//
@@ -1323,16 +1323,22 @@
 n async 
+grunt 
 callback
@@ -1344,18 +1344,20 @@
 k%0A%09%09var 
-cb
+done
  = this.
@@ -1367,52 +1367,142 @@
 nc()
-,%0A%09%09%09spriteCount = 0;%0A%0A%09%09this.files.forEach(
+;%0A%09%09// and a process function so we can build only one sprite at a time%0A%09%09// (otherwise we open too many files)%0A%09%09var processSprite = 
 func
@@ -1506,22 +1506,24 @@
 unction(
-sprite
+files, i
 ) %7B%0A%09%09%09v
@@ -1525,16 +1525,39 @@
 %0A%09%09%09var 
+sprite = files%5Bi%5D,%0A%09%09%09%09
 exportOp
@@ -2005,18 +2005,20 @@
 %09return 
-cb
+done
 (err);%0A%09
@@ -2495,103 +2495,174 @@
 back
-%0A%09%09%09%09spriteCount++;%0A%09%09%09%09if (spriteCount == that.files.length) %7B%0A%09%09%09%09%09cb(true);%0A%09%09%09%09%7D%0A%09%09%09%7D);%0A%09%09%7D
+ next sprite or done%0A%09%09%09%09i++;%0A%09%09%09%09if (i %3C files.length) %7B%0A%09%09%09%09%09processSprite(files, i);%0A%09%09%09%09%7D else %7B%0A%09%09%09%09%09done(true);%0A%09%09%09%09%7D%0A%09%09%09%7D);%0A%09%09%7D;%0A%0A%09%09processSprite(this.files, 0
 );%0A%09

Make building sprite images sequential. Otherwise it tries to open too many files at a time.