/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * GIMP PSD Plug-in
 * Copyright 2007 by John Marshall
 *
 * 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/>.
 */

#include "config.h"

#include <string.h>
#include <errno.h>

#include <glib/gstdio.h>
#include <libgimp/gimp.h>

#include "psd.h"
#include "psd-util.h"

#include "libgimp/stdplugins-intl.h"

/*  Local constants */
#define MIN_RUN     3

/*  Local function prototypes  */
static gchar * gimp_layer_mode_effects_name (GimpLayerMode mode);


/* Utility function */
void
psd_set_error (gboolean   file_eof,
               gint       err_no,
               GError   **error)
{
  if (file_eof)
    {
      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                   "%s", _("Unexpected end of file"));
    }
  else
    {
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (err_no),
                   "%s", g_strerror (err_no));
    }

  return;
}

gchar *
fread_pascal_string (gint32   *bytes_read,
                     gint32   *bytes_written,
                     guint16   mod_len,
                     FILE     *f,
                     GError  **error)
{
  /*
   * Reads a pascal string from the file padded to a multiple of mod_len
   * and returns a utf-8 string.
   */

  gchar        *str;
  gchar        *utf8_str;
  guchar        len;
  gint32        padded_len;

  *bytes_read = 0;
  *bytes_written = -1;

  if (fread (&len, 1, 1, f) < 1)
    {
      psd_set_error (feof (f), errno, error);
      return NULL;
    }
  (*bytes_read)++;
  IFDBG(3) g_debug ("Pascal string length %d", len);

  if (len == 0)
    {
      if (fseek (f, mod_len - 1, SEEK_CUR) < 0)
        {
          psd_set_error (feof (f), errno, error);
          return NULL;
        }
      *bytes_read += (mod_len - 1);
      *bytes_written = 0;
      return NULL;
    }

  str = g_malloc (len);
  if (fread (str, len, 1, f) < 1)
    {
      psd_set_error (feof (f), errno, error);
      g_free (str);
      return NULL;
    }
  *bytes_read += len;

  if (mod_len > 0)
    {
      padded_len = len + 1;
      while (padded_len % mod_len != 0)
        {
          if (fseek (f, 1, SEEK_CUR) < 0)
            {
              psd_set_error (feof (f), errno, error);
              g_free (str);
              return NULL;
            }
          (*bytes_read)++;
          padded_len++;
        }
    }

  utf8_str = gimp_any_to_utf8 (str, len, NULL);
  *bytes_written = strlen (utf8_str);
  g_free (str);

  IFDBG(3) g_debug ("Pascal string: %s, bytes_read: %d, bytes_written: %d",
                    utf8_str, *bytes_read, *bytes_written);

  return utf8_str;
}

gint32
fwrite_pascal_string (const gchar  *src,
                      guint16       mod_len,
                      FILE         *f,
                      GError      **error)
{
  /*
   *  Converts utf-8 string to current locale and writes as pascal
   *  string with padding to a multiple of mod_len.
   */

  gchar  *str;
  gchar  *pascal_str;
  gchar   null_str = 0x0;
  guchar  pascal_len;
  gint32  bytes_written = 0;
  gsize   len;

  if (src == NULL)
    {
       /* Write null string as two null bytes (0x0) */
      if (fwrite (&null_str, 1, 1, f) < 1
          || fwrite (&null_str, 1, 1, f) < 1)
        {
          psd_set_error (feof (f), errno, error);
          return -1;
        }
      bytes_written += 2;
    }
  else
    {
      str = g_locale_from_utf8 (src, -1, NULL, &len, NULL);
      if (len > 255)
        pascal_len = 255;
      else
        pascal_len = len;
      pascal_str = g_strndup (str, pascal_len);
      g_free (str);
      if (fwrite (&pascal_len, 1, 1, f) < 1
          || fwrite (pascal_str, pascal_len, 1, f) < 1)
        {
          psd_set_error (feof (f), errno, error);
          g_free (pascal_str);
          return -1;
        }
      bytes_written++;
      bytes_written += pascal_len;
      IFDBG(2) g_debug ("Pascal string: %s, bytes_written: %d",
                        pascal_str, bytes_written);
      g_free (pascal_str);
    }

  /* Pad with nulls */
  if (mod_len > 0)
    {
      while (bytes_written % mod_len != 0)
        {
          if (fwrite (&null_str, 1, 1, f) < 1)
            {
              psd_set_error (feof (f), errno, error);
              return -1;
            }
          bytes_written++;
        }
    }

  return bytes_written;
}

gchar *
fread_unicode_string (gint32   *bytes_read,
                      gint32   *bytes_written,
                      guint16   mod_len,
                      FILE     *f,
                      GError  **error)
{
  /*
   * Reads a utf-16 string from the file padded to a multiple of mod_len
   * and returns a utf-8 string.
   */

  gchar     *utf8_str;
  gunichar2 *utf16_str;
  gint32     len;
  gint32     i;
  gint32     padded_len;
  glong      utf8_str_len;

  *bytes_read = 0;
  *bytes_written = -1;

  if (fread (&len, 4, 1, f) < 1)
    {
      psd_set_error (feof (f), errno, error);
      return NULL;
    }
  *bytes_read += 4;
  len = GINT32_FROM_BE (len);
  IFDBG(3) g_debug ("Unicode string length %d", len);

  if (len == 0)
    {
      if (fseek (f, mod_len - 1, SEEK_CUR) < 0)
        {
          psd_set_error (feof (f), errno, error);
          return NULL;
        }
      *bytes_read += (mod_len - 1);
      *bytes_written = 0;
      return NULL;
    }

  utf16_str = g_malloc (len * 2);
  for (i = 0; i < len; ++i)
    {
      if (fread (&utf16_str[i], 2, 1, f) < 1)
        {
          psd_set_error (feof (f), errno, error);
          g_free (utf16_str);
          return NULL;
        }
      *bytes_read += 2;
      utf16_str[i] = GINT16_FROM_BE (utf16_str[i]);
    }

  if (mod_len > 0)
    {
      padded_len = len + 1;
      while (padded_len % mod_len != 0)
        {
          if (fseek (f, 1, SEEK_CUR) < 0)
            {
              psd_set_error (feof (f), errno, error);
              g_free (utf16_str);
              return NULL;
            }
          (*bytes_read)++;
          padded_len++;
        }
    }

  utf8_str = g_utf16_to_utf8 (utf16_str, len, NULL, &utf8_str_len, NULL);
  *bytes_written = utf8_str_len;
  g_free (utf16_str);

  IFDBG(3) g_debug ("Unicode string: %s, bytes_read: %d, bytes_written: %d",
                    utf8_str, *bytes_read, *bytes_written);

  return utf8_str;
}

gint32
fwrite_unicode_string (const gchar  *src,
                       guint16       mod_len,
                       FILE         *f,
                       GError      **error)
{
  /*
   *  Converts utf-8 string to utf-16 and writes 4 byte length
   *  then string padding to multiple of mod_len.
   */

  gunichar2 *utf16_str;
  gchar      null_str = 0x0;
  gint32     utf16_len = 0;
  gint32     bytes_written = 0;
  gint       i;
  glong      len;

  if (src == NULL)
    {
       /* Write null string as four byte 0 int32 */
      if (fwrite (&utf16_len, 4, 1, f) < 1)
        {
          psd_set_error (feof (f), errno, error);
          return -1;
        }
      bytes_written += 4;
    }
  else
    {
      utf16_str = g_utf8_to_utf16 (src, -1, NULL, &len, NULL);
      /* Byte swap as required */
      utf16_len = len;
      for (i = 0; i < utf16_len; ++i)
          utf16_str[i] = GINT16_TO_BE (utf16_str[i]);
      utf16_len = GINT32_TO_BE (utf16_len);

      if (fwrite (&utf16_len, 4, 1, f) < 1
          || fwrite (utf16_str, 2, utf16_len + 1, f) < utf16_len + 1)
        {
          psd_set_error (feof (f), errno, error);
          return -1;
        }
      bytes_written += (4 + 2 * utf16_len + 2);
      IFDBG(2) g_debug ("Unicode string: %s, bytes_written: %d",
                        src, bytes_written);
    }

  /* Pad with nulls */
  if (mod_len > 0)
    {
      while (bytes_written % mod_len != 0)
        {
          if (fwrite (&null_str, 1, 1, f) < 1)
            {
              psd_set_error (feof (f), errno, error);
              return -1;
            }
          bytes_written++;
        }
    }

  return bytes_written;
}

gint
decode_packbits (const gchar *src,
                 gchar       *dst,
                 guint16      packed_len,
                 guint32      unpacked_len)
{
  /*
   *  Decode a PackBits chunk.
   */

  gint    n;
  gchar   dat;
  gint32  unpack_left = unpacked_len;
  gint32  pack_left = packed_len;
  gint32  error_code = 0;
  gint32  return_val = 0;

  while (unpack_left > 0 && pack_left > 0)
    {
      n = *src;
      src++;
      pack_left--;

      if (n == 128)     /* nop */
        continue;
      else if (n > 128)
        n -= 256;

      if (n < 0)        /* replicate next gchar |n|+ 1 times */
        {
          n  = 1 - n;
          if (! pack_left)
            {
              IFDBG(2) g_debug ("Input buffer exhausted in replicate");
              error_code = 1;
              break;
            }
          if (n > unpack_left)
            {
              IFDBG(2) g_debug ("Overrun in packbits replicate of %d chars", n - unpack_left);
              error_code = 2;
            }
          dat = *src;
          for (; n > 0; --n)
            {
              if (! unpack_left)
                break;
              *dst = dat;
              dst++;
              unpack_left--;
            }
          if (unpack_left)
            {
              src++;
              pack_left--;
            }
        }
      else              /* copy next n+1 gchars literally */
        {
          n++;
          for (; n > 0; --n)
            {
              if (! pack_left)
                {
                  IFDBG(2) g_debug ("Input buffer exhausted in copy");
                  error_code = 3;
                  break;
                }
              if (! unpack_left)
                {
                  IFDBG(2) g_debug ("Output buffer exhausted in copy");
                  error_code = 4;
                  break;
                }
              *dst = *src;
              dst++;
              unpack_left--;
              src++;
              pack_left--;
            }
        }
    }

  if (unpack_left > 0)
    {
      /* Pad with zeros to end of output buffer */
      for (n = 0; n < pack_left; ++n)
        {
          *dst = 0;
          dst++;
        }
    }

  if (unpack_left)
    {
      IFDBG(2) g_debug ("Packbits decode - unpack left %d", unpack_left);
      return_val -= unpack_left;
    }
  if (pack_left)
    {
      /* Some images seem to have a pad byte at the end of the packed data */
      if (error_code || pack_left != 1)
        {
          IFDBG(2) g_debug ("Packbits decode - pack left %d", pack_left);
          return_val = pack_left;
        }
    }

  IFDBG(2)
    if (error_code)
      g_debug ("Error code %d", error_code);

  return return_val;
}

gchar *
encode_packbits (const gchar *src,
                 guint32      unpacked_len,
                 guint16     *packed_len)
{
  /*
   *  Encode a PackBits chunk.
   */

  GString *dst_str;                      /* destination string */
  gint     curr_char;                    /* current character */
  gchar    char_buff[128];               /* buffer of already read characters */
  guchar   run_len;                      /* number of characters in a run */
  gint32   unpack_left = unpacked_len;

  IFDBG(2) g_debug ("Encode packbits");

  /* Initialise destination string */
  dst_str = g_string_sized_new (unpacked_len);

  /* prime the read loop */
  curr_char = *src;
  src++;
  run_len = 0;

  /* read input until there's nothing left */
  while (unpack_left > 0)
    {
        char_buff[run_len] = (gchar) curr_char;
        IFDBG(2) g_debug ("buff %x, run len %d, curr char %x",
                          char_buff[run_len], run_len, (gchar) curr_char);
        run_len++;

        if (run_len >= MIN_RUN)
          {
            gint i;

            /* check for run  */
            for (i = 2; i <= MIN_RUN; ++i)
              {
                if (curr_char != char_buff[run_len - i])
                  {
                    /* no run */
                    i = 0;
                    break;
                  }
              }

            if (i != 0)
              {
                /* we have a run write out buffer before run*/
                gint next_char;

                if (run_len > MIN_RUN)
                  {
                    /* block size - 1 followed by contents */
                    g_string_append_c (dst_str, (run_len - MIN_RUN - 1));
                    g_string_append_len (dst_str, char_buff, (run_len - MIN_RUN));
                    IFDBG(2) g_debug ("(1) Number of chars: %d, run length tag: %d",
                                      run_len - MIN_RUN, run_len - MIN_RUN - 1);
                  }

                /* determine run length (MIN_RUN so far) */
                run_len = MIN_RUN;

                /* get next character */
                next_char = *src;
                src++;
                unpack_left--;
                while (next_char == curr_char)
                  {
                    run_len++;
                    if (run_len == 128)
                      {
                        /* run is at max length */
                        break;
                      }
                    next_char = *src;
                    src++;
                    unpack_left--;
                  }

                /* write out encoded run length and run symbol */
                g_string_append_c (dst_str, (gchar)(1 - (gint)(run_len)));
                g_string_append_c (dst_str, curr_char);
                IFDBG(2) g_debug ("(2) Number of chars: %d, run length tag: %d",
                                  run_len, (1 - (gint)(run_len)));

                if (unpack_left > 0)
                  {
                    /* make run breaker start of next buffer */
                    char_buff[0] = next_char;
                    run_len = 1;
                  }
                else
                  {
                    /* file ends in a run */
                    run_len = 0;
                  }
              }
          }

        if (run_len == 128)
          {
            /* write out buffer */
            g_string_append_c (dst_str, 127);
            g_string_append_len (dst_str, char_buff, 128);
            IFDBG(2) g_debug ("(3) Number of chars: 128, run length tag: 127");

            /* start a new buffer */
            run_len = 0;
          }

        curr_char = *src;
        src++;
        unpack_left--;
    }

  /* write out last buffer */
  if (run_len != 0)
    {
        if (run_len <= 128)
          {
            /* write out entire copy buffer */
            g_string_append_c (dst_str, run_len - 1);
            g_string_append_len (dst_str, char_buff, run_len);
            IFDBG(2) g_debug ("(4) Number of chars: %d, run length tag: %d",
                              run_len, run_len - 1);
          }
        else
          {
            IFDBG(2) g_debug ("(5) Very bad - should not be here");
          }
    }

  *packed_len = dst_str->len;
  IFDBG(2) g_debug ("Packed len %d, unpacked %d", *packed_len, unpacked_len);

  return g_string_free (dst_str, FALSE);
}

GimpLayerMode
psd_to_gimp_blend_mode (const gchar            *psd_mode,
                        GimpLayerCompositeMode *layer_composite)
{
  /* FIXME: Do all photoshop modes use the union rule?  We always return
   * "union", effectively, only returning "auto" for modes that default to
   * "union", to reduce UI clutter.
   */
  if (layer_composite) *layer_composite = GIMP_LAYER_COMPOSITE_SRC_ATOP;

  if (g_ascii_strncasecmp (psd_mode, "norm", 4) == 0)           /* Normal (ps3) */
    {
      if (layer_composite) *layer_composite = GIMP_LAYER_COMPOSITE_AUTO;
      return GIMP_LAYER_MODE_NORMAL_LEGACY;
    }

  if (g_ascii_strncasecmp (psd_mode, "dark", 4) == 0)           /* Darken (ps3) */
    return GIMP_LAYER_MODE_DARKEN_ONLY;

  if (g_ascii_strncasecmp (psd_mode, "lite", 4) == 0)           /* Lighten (ps3) */
      return GIMP_LAYER_MODE_LIGHTEN_ONLY;

  if (g_ascii_strncasecmp (psd_mode, "hue ", 4) == 0)           /* Hue (ps3) */
    return GIMP_LAYER_MODE_LCH_HUE;

  if (g_ascii_strncasecmp (psd_mode, "sat ", 4) == 0)           /* Saturation (ps3) */
    {
      if (CONVERSION_WARNINGS)
        {
          static gchar  *mode_name = "SATURATION";
          g_message ("Gimp uses a different equation to photoshop for "
                     "blend mode: %s. Results will differ.",
                     mode_name);
        }
      return GIMP_LAYER_MODE_LCH_CHROMA;
    }

  if (g_ascii_strncasecmp (psd_mode, "colr", 4) == 0)           /* Color (ps3) */
    return GIMP_LAYER_MODE_LCH_COLOR;

  if (g_ascii_strncasecmp (psd_mode, "lum ", 4) == 0)           /* Luminosity (ps3) */
    {
      if (CONVERSION_WARNINGS)
        {
          static gchar  *mode_name = "LUMINOSITY (VALUE)";
          g_message ("Gimp uses a different equation to photoshop for "
                     "blend mode: %s. Results will differ.",
                     mode_name);
        }
      return GIMP_LAYER_MODE_LCH_LIGHTNESS;
    }

  if (g_ascii_strncasecmp (psd_mode, "mul ", 4) == 0)           /* Multiply (ps3) */
    return GIMP_LAYER_MODE_MULTIPLY;

  if (g_ascii_strncasecmp (psd_mode, "lddg", 4) == 0)           /* Linear Dodge (cs2) */
    return GIMP_LAYER_MODE_ADDITION;

  if (g_ascii_strncasecmp (psd_mode, "scrn", 4) == 0)           /* Screen (ps3) */
    return GIMP_LAYER_MODE_SCREEN;

  if (g_ascii_strncasecmp (psd_mode, "diss", 4) == 0)           /* Dissolve (ps3) */
    {
      if (layer_composite) *layer_composite = GIMP_LAYER_COMPOSITE_AUTO;
      return GIMP_LAYER_MODE_DISSOLVE;
    }

  if (g_ascii_strncasecmp (psd_mode, "over", 4) == 0)           /* Overlay (ps3) */
    return GIMP_LAYER_MODE_OVERLAY;

  if (g_ascii_strncasecmp (psd_mode, "hLit", 4) == 0)           /* Hard light (ps3) */
    return GIMP_LAYER_MODE_HARDLIGHT;

  if (g_ascii_strncasecmp (psd_mode, "sLit", 4) == 0)           /* Soft light (ps3) */
    {
      if (CONVERSION_WARNINGS)
        {
          static gchar  *mode_name = "SOFT LIGHT";
          g_message ("GIMP uses a different equation to Photoshop for "
                     "blend mode: %s. Results will differ.",
                     mode_name);
        }
    return GIMP_LAYER_MODE_SOFTLIGHT;
    }

  if (g_ascii_strncasecmp (psd_mode, "diff", 4) == 0)           /* Difference (ps3) */
    return GIMP_LAYER_MODE_DIFFERENCE;

  if (g_ascii_strncasecmp (psd_mode, "smud", 4) == 0)           /* Exclusion (ps6) */
      return GIMP_LAYER_MODE_EXCLUSION;

  if (g_ascii_strncasecmp (psd_mode, "div ", 4) == 0)           /* Color dodge (ps6) */
      return GIMP_LAYER_MODE_DODGE;

  if (g_ascii_strncasecmp (psd_mode, "idiv", 4) == 0)           /* Color burn (ps6) */
      return GIMP_LAYER_MODE_BURN;

  if (g_ascii_strncasecmp (psd_mode, "lbrn", 4) == 0)           /* Linear burn (ps7)*/
      return GIMP_LAYER_MODE_LINEAR_BURN;

  if (g_ascii_strncasecmp (psd_mode, "lddg", 4) == 0)           /* Linear dodge (ps7)*/
      return GIMP_LAYER_MODE_ADDITION;

  if (g_ascii_strncasecmp (psd_mode, "lLit", 4) == 0)           /* Linear light (ps7)*/
      return GIMP_LAYER_MODE_LINEAR_LIGHT;

  if (g_ascii_strncasecmp (psd_mode, "pLit", 4) == 0)           /* Pin light (ps7)*/
      return GIMP_LAYER_MODE_PIN_LIGHT;

  if (g_ascii_strncasecmp (psd_mode, "vLit", 4) == 0)           /* Vivid light (ps7)*/
    return GIMP_LAYER_MODE_VIVID_LIGHT;

  if (g_ascii_strncasecmp (psd_mode, "hMix", 4) == 0)           /* Hard Mix (CS)*/
      return GIMP_LAYER_MODE_HARD_MIX;

  if (g_ascii_strncasecmp (psd_mode, "dkCl", 4) == 0)           /* Darker Color */
    {
      if (CONVERSION_WARNINGS)
        {
          static gchar  *mode_name = "DARKER COLOR";
          g_message ("Unsupported blend mode: %s. Mode reverts to normal",
                     mode_name);
        }
      if (layer_composite) *layer_composite = GIMP_LAYER_COMPOSITE_AUTO;
      return GIMP_LAYER_MODE_NORMAL_LEGACY;
    }

  if (g_ascii_strncasecmp (psd_mode, "lgCl", 4) == 0)           /* Lighter Color */
    {
      if (CONVERSION_WARNINGS)
        {
          static gchar  *mode_name = "LIGHTER COLOR";
          g_message ("Unsupported blend mode: %s. Mode reverts to normal",
                     mode_name);
        }
      if (layer_composite) *layer_composite = GIMP_LAYER_COMPOSITE_AUTO;
      return GIMP_LAYER_MODE_NORMAL_LEGACY;
    }

  if (CONVERSION_WARNINGS)
    {
      gchar  *mode_name = g_strndup (psd_mode, 4);
      g_message ("Unsupported blend mode: %s. Mode reverts to normal",
                 mode_name);
      g_free (mode_name);
    }

  if (layer_composite) *layer_composite = GIMP_LAYER_COMPOSITE_AUTO;

  return GIMP_LAYER_MODE_NORMAL_LEGACY;
}

gchar *
gimp_to_psd_blend_mode (GimpLayerMode          layer_mode,
                        GimpLayerCompositeMode layer_composite)
{
  gchar *psd_mode;

  if (((layer_mode == GIMP_LAYER_MODE_NORMAL_LEGACY ||
        layer_mode == GIMP_LAYER_MODE_DISSOLVE) &&
       (layer_composite != GIMP_LAYER_COMPOSITE_AUTO &&
        layer_composite != GIMP_LAYER_COMPOSITE_SRC_OVER)) ||
      layer_composite != GIMP_LAYER_COMPOSITE_SRC_OVER)
    {
      /* FIXME, and above if() */
    }

  switch (layer_mode)
    {
    case GIMP_LAYER_MODE_NORMAL_LEGACY:
      psd_mode = g_strndup ("norm", 4);                       /* Normal (ps3) */
      break;

    case GIMP_LAYER_MODE_DISSOLVE:
      psd_mode = g_strndup ("diss", 4);                       /* Dissolve (ps3) */
      break;

    case GIMP_LAYER_MODE_BEHIND:
      if (CONVERSION_WARNINGS)
        g_message ("Unsupported blend mode: %s. Mode reverts to normal",
                   gimp_layer_mode_effects_name (layer_mode));
      psd_mode = g_strndup ("norm", 4);
      break;

    case GIMP_LAYER_MODE_MULTIPLY:
    case GIMP_LAYER_MODE_MULTIPLY_LEGACY:
      psd_mode = g_strndup ("mul ", 4);                       /* Multiply (ps3) */
      break;

    case GIMP_LAYER_MODE_SCREEN:
    case GIMP_LAYER_MODE_SCREEN_LEGACY:
      psd_mode = g_strndup ("scrn", 4);                       /* Screen (ps3) */
      break;

    case GIMP_LAYER_MODE_OVERLAY:
      psd_mode = g_strndup ("over", 4);                       /* Overlay (ps3) */
      break;

    case GIMP_LAYER_MODE_DIFFERENCE:
    case GIMP_LAYER_MODE_DIFFERENCE_LEGACY:
      psd_mode = g_strndup ("diff", 4);                       /* Difference (ps3) */
      break;

    case GIMP_LAYER_MODE_ADDITION:
    case GIMP_LAYER_MODE_ADDITION_LEGACY:
      psd_mode = g_strndup ("lddg", 4);                       /* Linear dodge (ps7)*/
      break;

    case GIMP_LAYER_MODE_SUBTRACT_LEGACY:
      if (CONVERSION_WARNINGS)
        g_message ("Unsupported blend mode: %s. Mode reverts to normal",
                   gimp_layer_mode_effects_name (layer_mode));
      psd_mode = g_strndup ("norm", 4);
      break;

    case GIMP_LAYER_MODE_DARKEN_ONLY:
    case GIMP_LAYER_MODE_DARKEN_ONLY_LEGACY:
      psd_mode = g_strndup ("dark", 4);                       /* Darken (ps3) */
      break;

    case GIMP_LAYER_MODE_LIGHTEN_ONLY:
    case GIMP_LAYER_MODE_LIGHTEN_ONLY_LEGACY:
      psd_mode = g_strndup ("lite", 4);                       /* Lighten (ps3) */
      break;

    case GIMP_LAYER_MODE_LCH_HUE:
    case GIMP_LAYER_MODE_HSV_HUE_LEGACY:
      psd_mode = g_strndup ("hue ", 4);                       /* Hue (ps3) */
      break;

    case GIMP_LAYER_MODE_LCH_CHROMA:
    case GIMP_LAYER_MODE_HSV_SATURATION_LEGACY:
      if (CONVERSION_WARNINGS)
        g_message ("GIMP uses a different equation to Photoshop for "
                   "blend mode: %s. Results may differ.",
                   gimp_layer_mode_effects_name (layer_mode));
      psd_mode = g_strndup ("sat ", 4);                       /* Saturation (ps3) */
      break;

    case GIMP_LAYER_MODE_LCH_COLOR:
    case GIMP_LAYER_MODE_HSL_COLOR_LEGACY:
      psd_mode = g_strndup ("colr", 4);                       /* Color (ps3) */
      break;

    case GIMP_LAYER_MODE_LCH_LIGHTNESS:
    case GIMP_LAYER_MODE_HSV_VALUE_LEGACY:
      if (CONVERSION_WARNINGS)
        g_message ("GIMP uses a different equation to Photoshop for "
                   "blend mode: %s. Results may differ.",
                   gimp_layer_mode_effects_name (layer_mode));
      psd_mode = g_strndup ("lum ", 4);                       /* Luminosity (ps3) */
      break;

    case GIMP_LAYER_MODE_DIVIDE_LEGACY:
      if (CONVERSION_WARNINGS)
        g_message ("Unsupported blend mode: %s. Mode reverts to normal",
                   gimp_layer_mode_effects_name (layer_mode));
      psd_mode = g_strndup ("norm", 4);
      break;

    case GIMP_LAYER_MODE_DODGE:
    case GIMP_LAYER_MODE_DODGE_LEGACY:
      psd_mode = g_strndup ("div ", 4);                       /* Color Dodge (ps6) */
      break;

    case GIMP_LAYER_MODE_EXCLUSION:
      psd_mode = g_strndup ("smud", 4);                       /* Exclusion (ps6) */
      break;

    case GIMP_LAYER_MODE_BURN:
    case GIMP_LAYER_MODE_BURN_LEGACY:
      psd_mode = g_strndup ("idiv", 4);                       /* Color Burn (ps6) */
      break;

    case GIMP_LAYER_MODE_LINEAR_BURN:
      psd_mode = g_strndup ("lbrn", 4);                       /* Linear Burn (ps6) */
      break;

    case GIMP_LAYER_MODE_HARDLIGHT:
    case GIMP_LAYER_MODE_HARDLIGHT_LEGACY:
      psd_mode = g_strndup ("hLit", 4);                       /* Hard Light (ps3) */
      break;

    case GIMP_LAYER_MODE_VIVID_LIGHT:
      if (CONVERSION_WARNINGS)
        g_message ("GIMP uses a different equation to Photoshop for "
                   "blend mode: %s. Results may differ.",
                   gimp_layer_mode_effects_name (layer_mode));
      psd_mode = g_strndup ("vLit", 4);                       /* Vivid Light (ps3) */
      break;

    case GIMP_LAYER_MODE_LINEAR_LIGHT:
      psd_mode = g_strndup ("lLit", 4);                       /* Linear light (ps7)*/
      break;

    case GIMP_LAYER_MODE_PIN_LIGHT:
      psd_mode = g_strndup ("pLit", 4);                       /* Pin light (ps7)*/
      break;

    case GIMP_LAYER_MODE_HARD_MIX:
      psd_mode = g_strndup ("hMix", 4);                       /* Hard Mix (CS)*/
      break;

    case GIMP_LAYER_MODE_OVERLAY_LEGACY:
    case GIMP_LAYER_MODE_SOFTLIGHT_LEGACY:
      if (CONVERSION_WARNINGS)
        g_message ("Unsupported blend mode: %s. Mode reverts to normal",
                   gimp_layer_mode_effects_name (layer_mode));
      psd_mode = g_strndup ("sLit", 4);                       /* Soft Light (ps3) */
      break;

    case GIMP_LAYER_MODE_GRAIN_EXTRACT:
    case GIMP_LAYER_MODE_GRAIN_EXTRACT_LEGACY:
      if (CONVERSION_WARNINGS)
        g_message ("Unsupported blend mode: %s. Mode reverts to normal",
                   gimp_layer_mode_effects_name (layer_mode));
      psd_mode = g_strndup ("norm", 4);
      break;

    case GIMP_LAYER_MODE_GRAIN_MERGE:
    case GIMP_LAYER_MODE_GRAIN_MERGE_LEGACY:
      if (CONVERSION_WARNINGS)
        g_message ("Unsupported blend mode: %s. Mode reverts to normal",
                   gimp_layer_mode_effects_name (layer_mode));
      psd_mode = g_strndup ("norm", 4);
      break;

    case GIMP_LAYER_MODE_COLOR_ERASE:
    case GIMP_LAYER_MODE_COLOR_ERASE_LEGACY:
      if (CONVERSION_WARNINGS)
        g_message ("Unsupported blend mode: %s. Mode reverts to normal",
                   gimp_layer_mode_effects_name (layer_mode));
      psd_mode = g_strndup ("norm", 4);
      break;

    default:
      if (CONVERSION_WARNINGS)
        g_message ("Unsupported blend mode: %s. Mode reverts to normal",
                   gimp_layer_mode_effects_name (layer_mode));
      psd_mode = g_strndup ("norm", 4);
    }

  return psd_mode;
}

static gchar *
gimp_layer_mode_effects_name (GimpLayerMode mode)
{
  static gchar *layer_mode_effects_names[] =
  {
    "NORMAL",
    "DISSOLVE",
    "BEHIND",
    "MULTIPLY",
    "SCREEN",
    "OVERLAY",
    "DIFFERENCE",
    "ADD",
    "SUBTRACT",
    "EXCLUSION"
    "DARKEN",
    "LIGHTEN",
    "HUE",
    "SATURATION",
    "COLOR",
    "VALUE",
    "DIVIDE",
    "DODGE",
    "BURN",
    "LINEAR BURN"
    "HARD LIGHT",
    "SOFT LIGHT",
    "VIVID LIGHT",
    "LINEAR LIGHT",
    "PIN LIGHT",
    "HARD MIX"
    "GRAIN EXTRACT",
    "GRAIN MERGE",
    "COLOR ERASE"
  };
  static gchar *err_name = NULL;
  if (mode >= 0 && mode <= GIMP_LAYER_MODE_COLOR_ERASE_LEGACY)
    return layer_mode_effects_names[mode];
  g_free (err_name);

  err_name = g_strdup_printf ("UNKNOWN (%d)", mode);
  return err_name;
}

GimpColorTag
psd_to_gimp_layer_color_tag (guint16 layer_color_tag)
{
  GimpColorTag colorTag;

  switch (layer_color_tag)
    {
    case 1:
      colorTag = GIMP_COLOR_TAG_RED;
      break;

    case 2:
      colorTag = GIMP_COLOR_TAG_ORANGE;
      break;

    case 3:
      colorTag = GIMP_COLOR_TAG_YELLOW;
      break;

    case 4:
      colorTag = GIMP_COLOR_TAG_GREEN;
      break;

    case 5:
      colorTag = GIMP_COLOR_TAG_BLUE;
      break;

    case 6:
      colorTag = GIMP_COLOR_TAG_VIOLET;
      break;

    case 7:
      colorTag = GIMP_COLOR_TAG_GRAY;
      break;

    default:
      if (CONVERSION_WARNINGS)
        g_message ("Unsupported Photoshop layer color tag: %i. GIMP layer color tag set to none.",
                       layer_color_tag);
      colorTag = GIMP_COLOR_TAG_NONE;
    }

  return colorTag;
}

guint16
gimp_to_psd_layer_color_tag (GimpColorTag layer_color_tag)
{
  guint16 color_tag;

  switch (layer_color_tag)
    {
    case GIMP_COLOR_TAG_RED:
        color_tag = 1;
      break;

    case GIMP_COLOR_TAG_ORANGE:
        color_tag = 2;
      break;

    case GIMP_COLOR_TAG_YELLOW:
        color_tag = 3;
      break;

    case GIMP_COLOR_TAG_GREEN:
        color_tag = 4;
      break;

    case GIMP_COLOR_TAG_BLUE:
        color_tag = 5;
      break;

    case GIMP_COLOR_TAG_VIOLET:
        color_tag = 6;
      break;

    case GIMP_COLOR_TAG_GRAY:
        color_tag = 7;
      break;

    default:
      if (CONVERSION_WARNINGS)
        g_message ("Photoshop doesn't support GIMP layer color tag: %i. Photoshop layer color tag set to none.",
                   layer_color_tag);
        color_tag = 0;
    }

  return color_tag;
}
