require("babel-polyfill");

import fs from "fs";
import gm from "gm";
import config from "config";
import log from "./log";
import uuid from "uuid/v4";

const gmOptions = {};
if (config.has('timeout.conversion')) {
  const timeout = Number(config.get('timeout.conversion'));
  if (isNaN(timeout)) {
    log('warn', 'The configuration value of timeout.conversion resolved to a NaN value. Ignoring it!');
  } else if (timeout < 0) {
    log('warn', 'The configuration value of timeout.conversion did not resolve to a nonnegative value. Ignoring it!');
  } else if (timeout === 0) {
    log('info', 'Not setting any image timeout');
  } else {
    gmOptions.timeout = timeout;
  }
}

log('info', `Booting gm with the following options: ${JSON.stringify(gmOptions)}`);

const im = gm.subClass({imageMagick: true});

// 10 seconds
const clearTempfilesTimeout = 10000;

// Wrap these calls in promises so we can use async/await
const write = (client, file) => {
  return new Promise((resolve, reject) => client.write(file, err => err ? reject(err) : resolve()));
};
const size = (client) => {
  return new Promise((resolve, reject) => client.size({bufferStream: true}, (err, data) => err ? reject(err) : resolve(data)));
};

// Interlacing for png isn't efficient (both in filesize as render performance), so we only do it for jpg
const interlace = async(client, params) => {
  if (params.mime === 'image/jpeg') {
    return client.interlace('Line');
  }
  return client;
};

// When cropping, we size the image first to fix completely within the bounding box
// Then we crop the requested size, and center to the center of the image
const crop = async(client, params) => {
  // Resize the image to fit within the bounding box
  // The ^ ensures the images is resized while maintaining the ratio
  // However the sizes are treated as minimum values instead of maximum values
  client = client.resize(params.width, params.height, '^');
  const tmpFile = `/tmp/${uuid()}`;
  try {
    await write(client, tmpFile);
    client = im(tmpFile).options(gmOptions);
    const imgSize = await size(client);
    setTimeout(() => fs.unlink(tmpFile), clearTempfilesTimeout);
    return client
      .repage(imgSize.width, imgSize.height, 0, 0)
      .gravity('Center')
      // Crop the image to the exact size (the ! indicates a force)
      // This is ok since we first resized appropriately
      .crop(params.width, params.height, '!');
  } catch (e) {
    log('error', 'could not write tempfile');
    log('error', e.stack);
    throw e;
  }
};

// The sizes in the params given define the bounding box. The image is sized
// proportionally such that it fits in the bounding box without cropping
const clip = async(client, params) => {
  return client.resize(params.width, params.height);
};

const cover = async(client, params) => {
  return client.resize(params.width, params.height, '^');
};

// Same as clipping, however the remainder of the bounding box is filled with
// white.
const canvas = async(client, params) => {
  client = await clip(client, params);
  return client.gravity('Center').extent(params.width, params.height);
};

// Fill a transparent background with white
const background = async(client, params) => {
	if (params.mime === 'image/jpeg') {
		return client.background('white').flatten();
	}
	return client;
};

// Fit the image appropriately.
const fit = async(client, params) => {
  //For original format
  if (!params.width || !params.height) {
    return client;
  }
  if (params.fit === 'crop') {
    return crop(client, params);
  } else if (params.fit === 'canvas') {
    return canvas(client, params);
  } else if (params.fit === 'clip') {
    return clip(client, params);
  } else if (params.fit === 'cover') {
    return cover(client, params);
  }
  throw new Error(`Format '${params.fit}' was accepted but could not be handled`);
};

const setQuality = async(client, params) => {
  if (params.quality === -1) {
    return client;
  }

  // The imagemagick 'quality' parameter has a very different meaning for
  // different image types, so it's not a good idea to just blindly pass it
  // through. For now, we only support jpg compression, which is the most intuitive.
  switch (params.mime) {
    case 'image/jpeg':
      return client.quality(Math.min(100, Math.max(0, params.quality)));
    default:
      //No compression supported for other types
      return client;
  }
};

// Blur the image if requested in the params
const blur = async(client, params) => {
  if (params.blur === null) {
    return client;
  }
  return client.blur(params.blur.radius, params.blur.sigma);
};

export default {
  magic: async function (file, params) {
    let client = im(file).options(gmOptions);
    client = await fit(client, params);
    client = await background(client, params);
    client = await blur(client, params);
    client = await setQuality(client, params);
    client = await interlace(client, params);
    return client;
  },
  writeOriented: async function (source, destination, cropParameters) {
    // if possible, crop first (since the UA had that orientation), then orient
    if (cropParameters) {
      const cropped = im(source).options(gmOptions)
          .crop(cropParameters.width, cropParameters.height, cropParameters.xOffset, cropParameters.yOffset);
      try {
        await write(cropped, source);
      } catch (e) {
        log('error', e.stack);
        throw e;
      }
    }

    const oriented = im(source).options(gmOptions).autoOrient();

    try {
      await write(oriented, destination);
    } catch (e) {
      log('error', e.stack);
      throw e;
    }

    try {
      const imgSize = await size(im(destination).options(gmOptions));
      return {
        originalHeight: imgSize.height || null,
        originalWidth: imgSize.width || null
      };
    } catch (e) {
      log('error', e.stack);
      return {
        originalHeight: null,
        originalWidth: null
      };
    }
  },
  imageSize: async function (path) {
    return size(im(path));
  },
  imageArea: async function (path) {
    try {
      const imgSize = await size(im(path).options(gmOptions));
      return imgSize.width * imgSize.height;
    } catch (e) {
      log('error', e);
      return Number.MAX_SAFE_INTEGER;
    }
  }
};

@@ -674,16 +674,83 @@
 %0A  %7D%0A%7D%0A%0A
+const options = Object.assign(%7B%7D, %7BimageMagick: true%7D, gmOptions);%0A
 log('inf
@@ -810,19 +810,17 @@
 ringify(
-gmO
+o
 ptions)%7D
@@ -847,35 +847,23 @@
 ubClass(
-%7BimageMagick: true%7D
+options
 );%0A%0A// 1
@@ -6061,16 +6061,35 @@
 im(path)
+.options(gmOptions)
 );%0A  %7D,%0A

Apply the timeouts consistently