// Load in modules
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var getPixels = require('get-pixels');
var Vinyl = require('vinyl');
var Spritesmith = require('../src/smith.js');

// Set up paths
var spriteDir = path.join(__dirname, 'test_sprites');
var expectedDir = __dirname + '/expected_files';

// DEV: These were unsorted for testing `sort: false` but these work for all tests as is =D
var multipleSprites = [
  path.join(spriteDir, 'sprite1.png'),
  path.join(spriteDir, 'sprite3.png'),
  path.join(spriteDir, 'sprite2.jpg')
];

// Define common utilities
var spritesmithUtils = {
  run: function (params) {
    before(function runFn (done) {
      // Attempt to process the sprites via Spritesmith
      var that = this;
      Spritesmith.run(params, function handleRun (err, result) {
        that.err = err;
        that.result = result;
        done();
      });
    });
    after(function cleanup () {
      delete this.err;
      delete this.result;
    });
  },

  assertNoError: function () {
    return function assertNoErrorFn () {
      assert.strictEqual(this.err, null);
    };
  },

  assertCoordinates: function (filename) {
    return function assertCoordinatesFn () {
      // Load in the coordinates
      var result = this.result;

      // DEV: Write out to actual_files
      if (process.env.TEST_DEBUG) {
        try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) {}
        fs.writeFileSync(__dirname + '/actual_files/' + filename, JSON.stringify(result.coordinates, null, 4));
      }

      // Normalize the actual coordinates
      var expectedCoords = require(expectedDir + '/' + filename);
      var actualCoords = result.coordinates;
      var normCoords = {};
      assert(actualCoords, 'Result does not have a coordinates property');

      Object.getOwnPropertyNames(actualCoords).forEach(function (filepath) {
        var file = path.relative(spriteDir, filepath);
        normCoords[file] = actualCoords[filepath];
      });

      // Assert that the returned coordinates deep equal those in the coordinates.json
      assert.deepEqual(expectedCoords, normCoords, 'Actual coordinates do not match expected coordinates');
    };
  },

  assertProps: function (filename) {
    return function assertPropsFn () {
      // Load in the properties
      var result = this.result;

      // DEV: Write out to actual_files
      if (process.env.TEST_DEBUG) {
        try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) {}
        fs.writeFileSync(__dirname + '/actual_files/' + filename, JSON.stringify(result.properties, null, 4));
      }

      // Assert that the returned properties equals the expected properties
      var actualProps = result.properties;
      var expectedProps = require(expectedDir + '/' + filename);
      assert.deepEqual(expectedProps, actualProps, 'Actual properties do not match expected properties');
    };
  },

  assertSpritesheet: function (filename) {
    return function assertSpritesheetFn (done) {
      // Load our variables
      var actualImageBuff = this.result.image;
      var expectedFilepath = path.join(expectedDir, filename);

      // DEV: Write out to actual_files
      if (process.env.TEST_DEBUG) {
        try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) {}
        fs.writeFileSync(__dirname + '/actual_files/' + filename, actualImageBuff);
      }

      // Assert the actual image is the same expected
      // DEV: We are using pngjs for decoding/encoding in the library but this is testing one more cycle
      getPixels(actualImageBuff, 'image/png', function handleActualPixels (err, actualImage) {
        if (err) { return done(err); }
        getPixels(expectedFilepath, function handleExpectedPixels (err, expectedImage) {
          if (err) { return done(err); }
          assert.deepEqual(actualImage, expectedImage, 'Actual image does not match expected image');
          done();
        });
      });
    };
  }
};

// Start our tests
describe('An array of sprites', function () {
  describe('when processed via spritesmith', function () {
    spritesmithUtils.run({
      src: multipleSprites
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a binary-tree spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('binaryTree.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('binaryTree.properties.json'));
  });

  describe('when converted from left to right', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      algorithm: 'left-right'
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a left-right spritesheet', spritesmithUtils.assertSpritesheet('leftRight.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('leftRight.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('leftRight.properties.json'));
  });

  describe('when provided with a padding parameter', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      algorithm: 'binary-tree',
      padding: 2
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a padded spritesheet', spritesmithUtils.assertSpritesheet('padding.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('padding.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('padding.properties.json'));
  });

  describe('when told not to sort', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      algorithm: 'top-down',
      algorithmOpts: {sort: false}
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders an unsorted spritesheet', spritesmithUtils.assertSpritesheet('unsorted.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('unsorted.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('unsorted.properties.json'));
  });
});

describe('An array of vinyl object sprites', function () {
  describe('when processed via spritesmith', function () {
    spritesmithUtils.run({
      src: multipleSprites.map(function createVinylObject (filepath) {
        return new Vinyl({
          path: filepath,
          contents: fs.readFileSync(filepath)
        });
      })
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a binary-tree spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('binaryTree.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('binaryTree.properties.json'));
  });
});

describe('An empty array', function () {
  var emptySprites = [];

  describe('when processed via spritesmith', function () {
    spritesmithUtils.run({
      src: emptySprites
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders an empty spritesheet', function () {
      assert.deepEqual(this.result.image, new Buffer(0));
    });
    it('returns an empty coordinate mapping', function () {
      assert.deepEqual(this.result.coordinates, {});
    });
    it('has the proper properties', spritesmithUtils.assertProps('empty.properties.json'));
  });
});

describe('`spritesmith` using a custom engine via string', function () {
  describe('processing a set of images', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      engine: 'phantomjssmith'
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.phantomjs.png'));
  });
});

describe('`spritesmith` using a custom engine via an object', function () {
  describe('processing a set of images', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      engine: require('phantomjssmith')
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.phantomjs.png'));
  });
});

// Edge cases
// Test for https://github.com/twolfson/gulp.spritesmith/issues/22
var canvassmith;
try {
  canvassmith = require('canvassmith');
} catch (err) {}
var describeIfCanvassmithExists = canvassmith ? describe : describe.skip;
describeIfCanvassmithExists('`spritesmith` using `canvassmith`', function () {
  describe('processing a bad image', function () {
    spritesmithUtils.run({
      src: [path.join(spriteDir, 'malformed.png')],
      engine: 'canvassmith'
    });

    it('calls back with an error', function () {
      assert.notEqual(this.err, null);
    });
  });
});
