'use strict';

var fs = require('fs');
var path = require('path');

var typeHandlers = require('./types');
var detector = require('./detector');

// Maximum buffer size, with a default of 512 kilobytes.
// TO-DO: make this adaptive based on the initial signature of the image
var MaxBufferSize = 512*1024;

/**
 * Return size information based on a buffer
 *
 * @param {Buffer} buffer
 * @param {String} filepath
 * @returns {Object}
 */
function lookup (buffer, filepath) {
  // detect the file type.. don't rely on the extension
  var type = detector(buffer, filepath);

  // find an appropriate handler for this file type
  if (type in typeHandlers) {
    var size = typeHandlers[type].calculate(buffer, filepath);
    if (size !== false) {
      size.type = type;
      return size;
    }
  }

  // throw up, if we don't understand the file
  throw new TypeError('unsupported file type: ' + type + ' (file: ' + filepath + ')');
}

/**
 * Reads a file into a buffer.
 *
 * The callback will be called after the process has completed. The
 * callback's first argument will be an error (or null). The second argument
 * will be the Buffer, if the operation was successful.
 *
 * @param {String} filepath
 * @param {Function} callback
 */
function asyncFileToBuffer (filepath, callback) {
  // open the file in read only mode
  fs.open(filepath, 'r', function (err, descriptor) {
    if (err) { return callback(err); }
    fs.fstat(descriptor, function (err, stats) {
      if (err) { return callback(err); }
      var size = stats.size;
      if (size <= 0) {
        return callback(new Error('File size is not greater than 0 —— ' + filepath));
      }
      var bufferSize = Math.min(size, MaxBufferSize);
      var buffer = Buffer.alloc(bufferSize);
      // read first buffer block from the file, asynchronously
      fs.read(descriptor, buffer, 0, bufferSize, 0, function (err) {
        if (err) { return callback(err); }
        // close the file, we are done
        fs.close(descriptor, function (err) {
          callback(err, buffer);
        });
      });
    });
  });
}

/**
 * Synchronously reads a file into a buffer, blocking the nodejs process.
 *
 * @param {String} filepath
 * @returns {Buffer}
 */
function syncFileToBuffer (filepath) {
  // read from the file, synchronously
  var descriptor = fs.openSync(filepath, 'r');
  var size = fs.fstatSync(descriptor).size;
  var bufferSize = Math.min(size, MaxBufferSize);
  var buffer = Buffer.alloc(bufferSize);
  fs.readSync(descriptor, buffer, 0, bufferSize, 0);
  fs.closeSync(descriptor);
  return buffer;
}

/**
 * @param {Buffer|string} input - buffer or relative/absolute path of the image file
 * @param {Function=} callback - optional function for async detection
 */
module.exports = function (input, callback) {

  // Handle buffer input
  if (Buffer.isBuffer(input)) {
    return lookup(input);
  }

  // input should be a string at this point
  if (typeof input !== 'string') {
    throw new TypeError('invalid invocation');
  }

  // resolve the file path
  var filepath = path.resolve(input);

  if (typeof callback === 'function') {
    asyncFileToBuffer(filepath, function (err, buffer) {
      if (err) { return callback(err); }

      // return the dimensions
      var dimensions;
      try {
        dimensions = lookup(buffer, filepath);
      } catch (e) {
        err = e;
      }
      callback(err, dimensions);
    });
  } else {
    var buffer = syncFileToBuffer(filepath);
    return lookup(buffer, filepath);
  }
};

module.exports.types = Object.keys(typeHandlers);
