

/*!
 * Stylus - plugin - url
 * Copyright (c) Automattic <developer.wordpress.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var utils = require('../utils')
  , Buffer = require('buffer').Buffer
  , fs = require('fs')
  , path = require('path')
  , sax = require('sax');

module.exports = class Image {
  /**
   * Initialize a new `Image` with the given `ctx` and `path.
   *
   * @param {Evaluator} ctx
   * @param {String} path
   * @api private
   */

  constructor(ctx, path) {
    this.ctx = ctx;
    this.path = utils.lookup(path, ctx.paths);
    if (!this.path) throw new Error('failed to locate file ' + path);
  }


  /**
   * Open the image for reading.
   *
   * @api private
   */

  open() {
    this.fd = fs.openSync(this.path, 'r');
    this.length = fs.fstatSync(this.fd).size;
    this.extname = path.extname(this.path).slice(1);
  };

  /**
   * Close the file.
   *
   * @api private
   */

  close() {
    if (this.fd) fs.closeSync(this.fd);
  };

  /**
   * Return the type of image, supports:
   *
   *  - gif
   *  - png
   *  - jpeg
   *  - svg
   *
   * @return {String}
   * @api private
   */

  type() {
    var type
      , buf = Buffer.alloc(4);

    fs.readSync(this.fd, buf, 0, 4, 0);

    // GIF
    if (0x47 == buf[0] && 0x49 == buf[1] && 0x46 == buf[2]) type = 'gif';

    // PNG
    else if (0x50 == buf[1] && 0x4E == buf[2] && 0x47 == buf[3]) type = 'png';

    // JPEG
    else if (0xff == buf[0] && 0xd8 == buf[1]) type = 'jpeg';

    // SVG
    else if ('svg' == this.extname) type = this.extname;

    return type;
  };

  /**
   * Return image dimensions `[width, height]`.
   *
   * @return {Array}
   * @api private
   */

  size() {
    var type = this.type()
      , width
      , height
      , buf
      , offset
      , blockSize
      , parser;

    function uint16(b) { return b[1] << 8 | b[0]; }
    function uint32(b) { return b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]; }

    // Determine dimensions
    switch (type) {
      case 'jpeg':
        buf = Buffer.alloc(this.length);
        fs.readSync(this.fd, buf, 0, this.length, 0);
        offset = 4;
        blockSize = buf[offset] << 8 | buf[offset + 1];

        while (offset < this.length) {
          offset += blockSize;
          if (offset >= this.length || 0xff != buf[offset]) break;
          // SOF0 or SOF2 (progressive)
          if (0xc0 == buf[offset + 1] || 0xc2 == buf[offset + 1]) {
            height = buf[offset + 5] << 8 | buf[offset + 6];
            width = buf[offset + 7] << 8 | buf[offset + 8];
          } else {
            offset += 2;
            blockSize = buf[offset] << 8 | buf[offset + 1];
          }
        }
        break;
      case 'png':
        buf = Buffer.alloc(8);
        // IHDR chunk width / height uint32_t big-endian
        fs.readSync(this.fd, buf, 0, 8, 16);
        width = uint32(buf);
        height = uint32(buf.slice(4, 8));
        break;
      case 'gif':
        buf = Buffer.alloc(4);
        // width / height uint16_t little-endian
        fs.readSync(this.fd, buf, 0, 4, 6);
        width = uint16(buf);
        height = uint16(buf.slice(2, 4));
        break;
      case 'svg':
        offset = Math.min(this.length, 1024);
        buf = Buffer.alloc(offset);
        fs.readSync(this.fd, buf, 0, offset, 0);
        buf = buf.toString('utf8');
        parser = sax.parser(true);
        parser.onopentag = function (node) {
          if ('svg' == node.name && node.attributes.width && node.attributes.height) {
            width = parseInt(node.attributes.width, 10);
            height = parseInt(node.attributes.height, 10);
          }
        };
        parser.write(buf).close();
        break;
    }

    if ('number' != typeof width) throw new Error('failed to find width of "' + this.path + '"');
    if ('number' != typeof height) throw new Error('failed to find height of "' + this.path + '"');

    return [width, height];
  };
};
