/**
 *
 * Magnolia SimpleMedia Module (http://www.openmindlab.com/lab/products/media.html)
 * Copyright (C)2008 - 2010, Openmind S.r.l. http://www.openmindonline.it
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.sourceforge.openutils.mgnlmedia.media.utils;

import info.magnolia.cms.beans.runtime.FileProperties;
import info.magnolia.cms.core.Content;
import info.magnolia.cms.core.HierarchyManager;
import info.magnolia.cms.core.NodeData;
import info.magnolia.cms.util.ExclusiveWrite;
import info.magnolia.context.Context;
import info.magnolia.context.MgnlContext;
import info.magnolia.context.MgnlContext.SystemContextOperation;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TimeZone;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;

import net.sourceforge.openutils.mgnlmedia.media.configuration.ImageProcessorsManager;
import net.sourceforge.openutils.mgnlmedia.media.configuration.MediaConfigurationManager;
import net.sourceforge.openutils.mgnlmedia.media.configuration.MediaTypeConfiguration;
import net.sourceforge.openutils.mgnlmedia.media.lifecycle.MediaModule;
import net.sourceforge.openutils.mgnlmedia.media.processors.ImagePostProcessor;
import net.sourceforge.openutils.mgnlmedia.media.types.impl.BaseTypeHandler;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Main utility class that works with images and media nodes
 * @author molaschi
 * @version $Id: ImageUtils.java 2357 2010-05-06 21:47:14Z fgiust $
 */
public final class ImageUtils
{

    /**
     * Logger.
     */
    private static Logger log = LoggerFactory.getLogger(ImageUtils.class);

    private static SimpleDateFormat sdf;

    /**
     * Nodedata name where resolution is saved
     */
    public static String RESOLUTION_PROPERTY = "resolution";

    private static final String[] extensions = new String[]{"jpg", "gif", "png" };

    static
    {
        sdf = new SimpleDateFormat();
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
    }

    private ImageUtils()
    {
    }

    private static int getType(ColorModel cm)
    {
        if (cm.getTransparency() == ColorModel.BITMASK)
        {
            if (cm.isAlphaPremultiplied())
            {
                return BufferedImage.TYPE_4BYTE_ABGR_PRE;
            }
            else
            {
                return BufferedImage.TYPE_4BYTE_ABGR;
            }
        }
        if (cm.getTransparency() == ColorModel.TRANSLUCENT)
        {
            if (cm.isAlphaPremultiplied())
            {
                return BufferedImage.TYPE_INT_ARGB_PRE;
            }
            else
            {
                return BufferedImage.TYPE_INT_ARGB;
            }
        }

        if (cm.getPixelSize() == 8)
        {
            return BufferedImage.TYPE_3BYTE_BGR;
        }
        return BufferedImage.TYPE_INT_RGB;
    }

    /**
     * Resize an image to x,y
     * @param original original image
     * @param x new width
     * @param y new height
     * @return resized image
     */
    public static BufferedImage resizeImage(BufferedImage original, int x, int y)
    {
        return resizeImage(original, x, y, x, y, null);
    }

    /**
     * Resize an image to x,y
     * @param original original image
     * @param x new width
     * @param y new height
     * @param canvasX canvas width
     * @param canvasY canvas height
     * @param background background color
     * @return resized image
     */
    public static BufferedImage resizeImage(BufferedImage original, int x, int y, int canvasX, int canvasY,
        Color background)
    {
        if (x <= 0)
        {
            x = 1;
            // throw new IllegalArgumentException("x=" + x + " (must be >0)");
        }
        if (y <= 0)
        {
            y = 1;
            // throw new IllegalArgumentException("y=" + y + " (must be >0)");
        }
        if (canvasX <= 0)
        {
            canvasX = 1;
            // throw new IllegalArgumentException("canvasX=" + canvasX + " (must be >0)");
        }
        if (canvasY <= 0)
        {
            canvasY = 1;
            // throw new IllegalArgumentException("canvasY=" + canvasY + " (must be >0)");
        }

        BufferedImage resizedImage;
        try
        {
            resizedImage = new BufferedImage(canvasX, canvasY, getType(original.getColorModel()));
        }
        catch (NegativeArraySizeException e)
        {
            throw new RuntimeException("NegativeArraySizeException caught when resizing image to ["
                + canvasX
                + ", "
                + canvasY
                + "]");
        }
        Graphics2D graphics2D = resizedImage.createGraphics();

        graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        if (canvasX > x || canvasY > y)
        {
            graphics2D.clearRect(0, 0, canvasX, canvasY);

            if (background == null && original.getColorModel().getTransparency() == Transparency.OPAQUE)
            {
                background = Color.WHITE;
            }
            // fill bands
            if (background != null)
            {
                graphics2D.setColor(background);

                if (canvasX > x)
                {
                    graphics2D.fillRect(0, 0, (canvasX - x) / 2, canvasY);
                    graphics2D.fillRect(canvasX - (canvasX - x) / 2, 0, canvasX, canvasY);
                }
                if (canvasY > y)
                {
                    graphics2D.fillRect(0, 0, canvasX, (canvasY - y) / 2);
                    graphics2D.fillRect(0, canvasY - (canvasY - y) / 2, canvasX, canvasY);
                }
            }
        }

        if (x > original.getWidth())
        {
            graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            graphics2D.drawImage(original, (canvasX - x) / 2, (canvasY - y) / 2, x, y, background, null);
        }
        else
        {
            Image scaledInstance = original.getScaledInstance(x, y, Image.SCALE_SMOOTH);
            graphics2D.drawImage(scaledInstance, (canvasX - x) / 2, (canvasY - y) / 2, x, y, background, null);
        }

        return resizedImage;
    }

    /**
     * Crop an image from left, top for width, height
     * @param original original image
     * @param left start crop point left
     * @param top start crop point top
     * @param width crop width
     * @param height crop height
     * @return resized image
     */
    public static BufferedImage cropImage(BufferedImage original, int left, int top, int width, int height)
    {
        BufferedImage resizedImage = new BufferedImage(width, height, getType(original.getColorModel()));
        Graphics2D graphics2D = resizedImage.createGraphics();
        graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        if (width > original.getWidth())
        {
            graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        }
        else
        {
            graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        }
        graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_DEFAULT);
        graphics2D.drawImage(original, 0, 0, width, height, left, top, width + left, height + top, null);

        return resizedImage;
    }

    /**
     * Create rounded corners on image
     * @param original original image
     * @param backgroundColor optional background color
     * @param radius corners radius
     * @return image with rounded corners
     */
    public static BufferedImage addRoundedCorners(BufferedImage original, Color backgroundColor, int radius)
    {
        int originalImageType = getType(original.getColorModel());
        int roundedCornersImageType = BufferedImage.TYPE_4BYTE_ABGR;

        if (originalImageType != BufferedImage.TYPE_4BYTE_ABGR)
        {
            if (originalImageType != BufferedImage.TYPE_4BYTE_ABGR_PRE)
            {
                // the image has not alpha channel so fill background
                if (backgroundColor == null)
                {
                    backgroundColor = Color.WHITE;
                }
            }
            else
            {
                roundedCornersImageType = BufferedImage.TYPE_4BYTE_ABGR_PRE;
            }
        }

        // use a 4 byte image type to create antialiased rounded corners
        BufferedImage roundedImage;
        try
        {
            roundedImage = new BufferedImage(original.getWidth(), original.getHeight(), roundedCornersImageType);
        }
        catch (NegativeArraySizeException e)
        {
            throw new RuntimeException("NegativeArraySizeException caught when adding rounded corners");
        }

        Graphics2D roundedGraphics2D = roundedImage.createGraphics();
        roundedGraphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        roundedGraphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

        roundedGraphics2D.setColor(Color.WHITE);
        roundedGraphics2D.fillRoundRect(0, 0, original.getWidth(), original.getHeight(), radius, radius);
        roundedGraphics2D.setComposite(AlphaComposite.SrcIn);
        roundedGraphics2D.drawImage(original, 0, 0, original.getWidth(), original.getHeight(), null);

        if (backgroundColor != null)
        {

            BufferedImage destImage;
            try
            {
                destImage = new BufferedImage(original.getWidth(), original.getHeight(), originalImageType);
            }
            catch (NegativeArraySizeException e)
            {
                throw new RuntimeException("NegativeArraySizeException caught when resizing image]");
            }
            // draw new image
            Graphics2D destImageGraphics2D = destImage.createGraphics();
            destImageGraphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            destImageGraphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

            destImageGraphics2D.setBackground(backgroundColor);
            destImageGraphics2D.clearRect(0, 0, original.getWidth(), original.getHeight());
            // destImageGraphics2D.setComposite(AlphaComposite.DstIn);
            destImageGraphics2D.drawImage(roundedImage, 0, 0, original.getWidth(), original.getHeight(), null);
            return destImage;
        }

        return roundedImage;
    }

    /**
     * Save a resolution for an image to a node (in resolutions/res-[width]x[height]/data.jpg)
     * @param image image to save
     * @param saveTo node to save to
     * @param extension extension
     * @param quality image quality
     * @param forceProgressive true to force progressive mode
     * @throws RepositoryException exception in jcr operations
     * @throws IOException exception converting image to jpg
     */
    public static void saveResolution(BufferedImage image, Content saveTo, String extension, float quality,
        boolean forceProgressive) throws RepositoryException, IOException
    {
        saveResolution(image, saveTo, null, extension, quality, forceProgressive);
    }

    /**
     * Save a resolution for an image to a node (in resolutions/[name]/data.jpg)
     * @param image image to save
     * @param saveTo node to save to
     * @param name name for this resolution
     * @param extension extension
     * @param quality image quality
     * @param forceProgressive true to force progressive mode
     * @throws RepositoryException exception in jcr operations
     * @throws IOException exception converting image to jpg
     */
    public static void saveResolution(BufferedImage image, Content saveTo, String name, String extension,
        float quality, boolean forceProgressive) throws RepositoryException, IOException
    {

        InputStream stream = null;

        try
        {
            stream = getStream(image, extension, quality, forceProgressive);

            if (stream == null)
            {
                throw new IllegalArgumentException("Stream is null");
            }

            Content resolutions = getResolutionsNode(saveTo);
            if (resolutions == null)
            {
                resolutions = saveTo.createContent("resolutions", MediaConfigurationManager.RESOLUTIONS);
                resolutions.getMetaData().setModificationDate();
            }

            String resolution = name;
            if (resolution == null)
            {
                resolution = "res-" + image.getWidth() + "x" + image.getHeight();
            }

            String originalRes = resolution;
            resolution = getResolutionPath(resolution);

            synchronized (ExclusiveWrite.getInstance())
            {

                if (resolutions.hasNodeData(resolution))
                {
                    NodeData nd = resolutions.getNodeData(resolution);
                    nd.delete();
                }

                // don't remove deprecated method call, needed for magnolia 4.0 compatibility
                NodeData nd = resolutions.createNodeData(resolution, PropertyType.BINARY);

                log.info("setting value to {}", nd.getHandle());
                nd.setValue(stream);
                String mimetype = "image/" + extension;
                if ("jpg".equals(extension))
                {
                    mimetype = "image/jpeg";
                }
                nd.setAttribute(ImageUtils.RESOLUTION_PROPERTY, originalRes);
                nd.setAttribute(FileProperties.PROPERTY_EXTENSION, extension);
                nd.setAttribute(FileProperties.PROPERTY_FILENAME, saveTo.getName());
                nd.setAttribute(FileProperties.PROPERTY_CONTENTTYPE, mimetype);
                nd.setAttribute(FileProperties.PROPERTY_LASTMODIFIED, GregorianCalendar.getInstance(TimeZone
                    .getDefault()));
                nd.setAttribute(FileProperties.PROPERTY_WIDTH, "" + image.getWidth());
                nd.setAttribute(FileProperties.PROPERTY_HEIGHT, "" + image.getHeight());

            }
        }
        finally
        {
            IOUtils.closeQuietly(stream);
        }
    }

    /**
     * Get resolution nodedata name for a given resolution string
     * @param resolution resolution string
     * @return resolution nodedata name
     */
    public static String getResolutionPath(String resolution)
    {
        if (resolution.indexOf(';') > 0)
        {
            return StringUtils.substringBefore(resolution, ";")
                + "-p"
                + StringUtils.substringAfter(resolution, ";").hashCode();
        }
        return resolution;
    }

    /**
     * Get an inputstream for an image and the target file extension
     * @param image image to get the inputstream from
     * @param extension target file extension
     * @param quality image quality
     * @param forceProgressive true if image has to be saved as progressive mode
     * @return inputstream
     * @throws IOException
     */
    public static InputStream getStream(BufferedImage image, String extension, float quality, boolean forceProgressive)
        throws IOException
    {

        final File tempFile = File.createTempFile("image-", "." + extension);
        FileOutputStream fos = new FileOutputStream(tempFile);

        BufferedOutputStream out = new BufferedOutputStream(fos);

        try
        {

            Iterator<ImageWriter> writers;
            ImageOutputStream imageOutputStream;
            ImageWriteParam params;
            ImageWriter imageWriter;

            writers = ImageIO.getImageWritersBySuffix(extension);

            if (writers != null && writers.hasNext())
            {
                // Fetch the first writer in the list
                imageWriter = writers.next();

                // Specify the parameters according to those the output file will be written

                // Get Default parameters
                params = imageWriter.getDefaultWriteParam();

                try
                {

                    String[] compressionTypes = params.getCompressionTypes();

                    if (compressionTypes != null && compressionTypes.length > 0)
                    {
                        // Define compression mode
                        params.setCompressionMode(javax.imageio.ImageWriteParam.MODE_EXPLICIT);

                        params.setCompressionType(compressionTypes[0]);

                        // Define compression quality
                        params.setCompressionQuality(quality);
                    }
                    else
                    {
                        params.setCompressionMode(javax.imageio.ImageWriteParam.MODE_COPY_FROM_METADATA);
                    }

                    // Define progressive mode
                    if (forceProgressive)
                    {
                        params.setProgressiveMode(javax.imageio.ImageWriteParam.MODE_DEFAULT);
                    }
                    else
                    {
                        params.setProgressiveMode(javax.imageio.ImageWriteParam.MODE_COPY_FROM_METADATA);
                    }
                }
                catch (UnsupportedOperationException e)
                {
                    // go on
                }

                // Define destination type - used the ColorModel and SampleModel of the Input Image
                params.setDestinationType(new ImageTypeSpecifier(image.getColorModel(), image.getSampleModel()));

                imageOutputStream = ImageIO.createImageOutputStream(out);
                imageWriter.setOutput(imageOutputStream);

                IIOImage iioimage = new IIOImage(image, null, null);

                try
                {
                    // Write the changed Image
                    imageWriter.write(null, iioimage, params);
                }
                catch (NullPointerException e)
                {
                    // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6287936
                    // GIF writer is buggy for totally transparent gifs

                    // workaround: draw a point!
                    image.createGraphics().drawRect(0, 0, 1, 1);
                    imageWriter.write(null, iioimage, params);

                }
                finally
                {
                    // Close the streams
                    imageOutputStream.close();
                    imageWriter.dispose();
                }
            }
            else
            {
                ImageIO.write(image, extension, out);
            }
        }
        catch (IOException ex)
        {
            log.error("Error writing image to buffer", ex);
            throw ex;
        }

        out.flush();
        IOUtils.closeQuietly(out);
        return new BufferedInputStream(new FileInputStream(tempFile)
        {

            /**
             * {@inheritDoc}
             */
            @Override
            public void close() throws IOException
            {
                tempFile.delete();
                super.close();
            }

        });
    }

    /**
     * Check if the resolution for a media is already present. Otherwise create it
     * @param media media to check the resolutoin on
     * @param resolutionTarget target resolution
     * @return false if resolution doesn't exist and there is a problem in generate it; true otherwise
     */
    public static boolean checkOrCreateResolution(final Content media, final String resolutionTarget)
    {

        return checkOrCreateResolution(media, resolutionTarget, BaseTypeHandler.ORGINAL_NODEDATA_NAME);
    }

    /**
     * Check if the resolution for a media is already present. Otherwise create it
     * @param media media to check the resolutoin on
     * @param resolutionTarget target resolution
     * @param nodeDataName nodedata where the image to resize is stored
     * @return false if resolution doesn't exist and there is a problem in generate it; true otherwise
     */
    public static boolean checkOrCreateResolution(final Content media, final String resolutionTarget,
        String nodeDataName)
    {

        Content resolutions = getResolutionsNode(media);

        String resolution = resolutionTarget;

        if (!"thumbnail".equals(resolutionTarget) && !"preview".equals(resolutionTarget))
        {
            resolution = "res-" + resolutionTarget;
        }
        try
        {
            if (resolutions != null && resolutions.hasNodeData(getResolutionPath(resolution)))
            {
                return true;
            }
        }
        catch (RepositoryException e1)
        {
            // go on with res calculation
        }

        String type = media.getNodeData("type").getString();
        if (StringUtils.equals(type, "other"))
        {
            return false;
        }

        if (nodeDataName == null)
        {

            MediaTypeConfiguration mtc = MediaConfigurationManager.getInstance().getMediaTypeConfigurationFromMedia(
                media);
            if (mtc == null)
            {
                nodeDataName = BaseTypeHandler.ORGINAL_NODEDATA_NAME;
            }
            else
            {
                nodeDataName = mtc.getHandler().getPreviewImageNodeDataName();
            }
        }

        try
        {
            if (!media.hasNodeData(nodeDataName))
            {
                return false;
            }
        }
        catch (RepositoryException e2)
        {
            log.warn(e2.getMessage(), e2);
        }

        final String finalNodeDataName = nodeDataName;

        try
        {
            ImageUtils.doInSystemContext(new MgnlContext.SystemContextOperation()
            {

                /**
                 * {@inheritDoc}
                 */
                public void exec()
                {
                    HierarchyManager hm = MgnlContext.getHierarchyManager(MediaModule.REPO);
                    String resolution = resolutionTarget;

                    if ("thumbnail".equals(resolution))
                    {
                        resolution = "l100x100";
                    }
                    if ("preview".equals(resolution))
                    {
                        resolution = "l450x350";
                    }

                    Content node;
                    try
                    {
                        node = hm.getContent(media.getHandle());
                    }
                    catch (RepositoryException e)
                    {
                        throw new RuntimeException(e);
                    }

                    NodeData image = node.getNodeData(finalNodeDataName);

                    if (image.getContentLength() == 0)
                    {
                        throw new RuntimeException("Invalid image nodedata "
                            + image.getHandle()
                            + ", size "
                            + image.getContentLength());
                    }

                    String extension = image.getAttribute(FileProperties.PROPERTY_EXTENSION);
                    if (!Arrays.asList(extensions).contains(extension))
                    {
                        try
                        {
                            image.setAttribute(FileProperties.PROPERTY_EXTENSION, "jpg");
                        }
                        catch (RepositoryException e)
                        {
                            throw new RuntimeException(e);
                        }
                        extension = "jpg";
                    }

                    BufferedImage original = createBufferedImage(image);

                    String resolutionName = "res-" + resolution;

                    Map<String, String> params = parseParameters(resolution);

                    BufferedImage img;
                    try
                    {
                        img = ImageUtils.getImageForResolution(original, resolution, params);
                    }
                    catch (IllegalArgumentException e)
                    {
                        throw new RuntimeException(e);
                    }

                    try
                    {
                        float quality = 0.8F;
                        if (StringUtils.isNotEmpty(params.get("quality")))
                        {
                            try
                            {
                                quality = NumberUtils.toFloat(params.get("quality"));
                                if (quality > 1.0F)
                                {
                                    quality = 1.0F;
                                }
                            }
                            catch (NumberFormatException ex)
                            {
                                log.error("quality parameter must be a float number but was {}", params.get("quality"));
                            }
                        }
                        boolean forceProgressive = false;
                        if (StringUtils.isNotEmpty(params.get("progressive")))
                        {
                            forceProgressive = true;
                        }
                        if ("thumbnail".equals(resolutionTarget) || "preview".equals(resolutionTarget))
                        {
                            ImageUtils
                                .saveResolution(img, node, resolutionTarget, extension, quality, forceProgressive);
                        }
                        else
                        {
                            ImageUtils.saveResolution(img, node, resolutionName, extension, quality, forceProgressive);
                        }
                    }
                    catch (RepositoryException e)
                    {
                        throw new RuntimeException(e);
                    }
                    catch (IOException e)
                    {
                        throw new RuntimeException(e);
                    }

                    try
                    {
                        hm.save();
                    }
                    catch (RepositoryException e)
                    {
                        throw new RuntimeException(e);
                    }

                }

            });
        }
        catch (RuntimeException ex)
        {
            log.error(ClassUtils.getShortClassName(ex.getClass())
                + " checking resolution for "
                + media.getHandle()
                + ": "
                + ex.getMessage(), ex);
            return false;
        }

        return true;
    }

    /**
     * Returns the "resolutions" node, checking for existence
     * @param media
     * @return
     */
    protected static Content getResolutionsNode(final Content media)
    {
        Content resolutions = null;

        try
        {
            if (media.hasContent("resolutions"))
            {
                resolutions = media.getContent("resolutions");
            }
        }
        catch (RepositoryException e)
        {
            // ignore, try to create it
        }
        return resolutions;
    }

    private static Map<String, String> parseParameters(String resolution)
    {

        Map<String, String> params = new HashMap<String, String>();
        if (StringUtils.contains(resolution, ";"))
        {
            String parameters = StringUtils.substringAfter(resolution, ";");
            resolution = StringUtils.substringBefore(resolution, ";");

            String tokens[] = StringUtils.split(parameters, ',');
            for (String token : tokens)
            {
                if (token.indexOf("=") > 0)
                {
                    String[] keyvalue = StringUtils.split(token, '=');
                    params.put(keyvalue[0], keyvalue[1]);
                }
                else
                {
                    params.put(token, "true");
                }
            }
        }
        return params;
    }

    /**
     * Get image for a resolution
     * @param original original image
     * @param resolution resolution
     * @param params parameters
     * @return new image
     */
    public static BufferedImage getImageForResolution(BufferedImage original, String resolution,
        Map<String, String> params)
    {

        if (original == null)
        {
            throw new IllegalArgumentException("input image is null");
        }
        if (resolution == null || resolution.length() < 1)
        {
            throw new IllegalArgumentException("Invalid resolution: " + resolution);
        }

        BufferedImage img = null;

        resolution = StringUtils.lowerCase(resolution);

        char controlChar = resolution.charAt(0);

        Point size = parseForSize(resolution);

        if (ImageProcessorsManager.getInstance().isValidControlChar(controlChar))
        {
            img = ImageProcessorsManager.getInstance().getImageResolutionProcessor(controlChar).getImageForResolution(
                original,
                size.x,
                size.y,
                params);
        }
        else if (controlChar < '0' || controlChar > '9')
        {
            throw new IllegalArgumentException("Invalid control char: " + controlChar);
        }
        else
        {
            img = ImageProcessorsManager.getInstance().getDefaultImageResolutionProcessor().getImageForResolution(
                original,
                size.x,
                size.y,
                params);
        }

        for (ImagePostProcessor ipp : ImageProcessorsManager.getInstance().getImagePostProcessorsList())
        {
            img = ipp.processImage(img, size.x, size.y, params);
        }

        return img;
    }

    /**
     * Get file extension for a resolution stored in a media node
     * @param media media
     * @param resolution resolution
     * @return file extension for a resolution stored in a media node
     */
    public static String getExtension(Content media, String resolution)
    {
        try
        {
            Content resolutions = media.getContent("resolutions");
            NodeData res = resolutions.getNodeData(resolution);
            return res.getAttribute(FileProperties.PROPERTY_EXTENSION);
        }
        catch (RepositoryException ex)
        {
            return "jpg";
        }
    }

    /**
     * Create a buffered image from the binary data stored in nodedata
     * @param image nodedata
     * @return buffered image from the binary data stored in nodedata
     */
    public static BufferedImage createBufferedImage(NodeData image)
    {
        InputStream is = image.getStream();
        try
        {
            return ImageIO.read(is);
        }
        catch (IOException e)
        {
            // CMYK jpeg can't be read be ImageIO
            // Caused by: javax.imageio.IIOException: Unsupported Image Type
            BufferedImage result = JpegUtils.processNonStandardImage(image);

            if (result == null)
            {
                // throw the original exception back
                throw new BadImageFormatException("Unable to handle " + image.getHandle(), e);
            }
            return result;

        }
        finally
        {
            IOUtils.closeQuietly(is);
        }
    }

    /**
     * Parse resolution string for required size
     * @param res resolution string
     * @return required size parsed from given resolution string
     */
    public static java.awt.Point parseForSize(String res)
    {
        Point size = new Point();
        String resolution = StringUtils.lowerCase(res);

        if (StringUtils.contains(resolution, ";"))
        {
            resolution = StringUtils.substringBefore(resolution, ";");
        }

        if (ImageProcessorsManager.getInstance().isValidControlChar(resolution.charAt(0))
            || resolution.charAt(0) < '0'
            || resolution.charAt(0) > '9')
        {
            resolution = resolution.substring(1);
        }

        String[] resXY = StringUtils.split(resolution, "x");

        size.x = NumberUtils.toInt(resXY[0]);
        size.y = NumberUtils.toInt(resXY[1]);
        return size;
    }

    private static void doInSystemContext(SystemContextOperation op)
    {
        final Context originalCtx = MgnlContext.hasInstance() ? MgnlContext.getInstance() : null;
        try
        {
            MgnlContext.setInstance(MgnlContext.getSystemContext());
            op.exec();
        }
        finally
        {
            MgnlContext.setInstance(originalCtx);
        }
    }

}
