/* Page Curl 1.0 --- plugin effect for Paint.Net
 * Copyright (C) 2008 MadJik (Jean-Claude JAY)
 *
 * You can contact me at jeanclaude.jay@neuf.fr
 *
 * 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 2 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/>.
 *
 */

// Original version writen in C++ by Federico Mena-Quintero in 1996
// V0.5 for TheGimp V0.54 : http://www.nuclecu.unam.mx/~federico/gimp/plug-ins/index.html#pagecurl
// So this plugin is licensed under a  
// GNU General Public License (GNU GPL): 
// http://www.gnu.org/licenses/gpl.html

using PaintDotNet;
using PaintDotNet.IndirectUI;
using PaintDotNet.Effects;
using PaintDotNet.PropertySystem;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;

namespace PageCurlEffect
{
  public sealed class PageCurlFx
      : PropertyBasedEffect
  {
    public static string CICC = CultureInfo.CurrentUICulture.EnglishName;
    public static string StaticName { get { if (CICC == "French")  return "Corner un coin de page"; else return "Page Curl"; } }

    public static Bitmap StaticImage { get { return PageCurl.Resource.PageCurl; } }


    public static string StaticSubMenuName { get { return SubmenuNames.Distort; } }

    public struct vector_t
    {
      internal double x;
      internal double y;
    }

    public bool WITHIN(long a, long b, long c) { return ((((a) <= (b)) && ((b) <= (c))) ? true : false); }

    public enum PropertyNames
    {
      colorW,
      backimage,
      back_tr,
      back_col,
      curlPow,
      curl_col,
      shad_tr,
      curl_tr,
      corner,
      axe
    }

    public enum colorchoice
    {
      Primary,
      Secondary,
      ColorWheel
    }

    public enum corners
    {
      upper_left,
      upper_right,
      lower_left,
      lower_right
    }

    public enum axis
    {
      Vertical,
      Horizontal
    }


    public PageCurlFx()
      : base(StaticName, StaticImage, StaticSubMenuName, EffectFlags.Configurable)
    {
    }

    protected override PropertyCollection OnCreatePropertyCollection()
    {
      List<Property> props = new List<Property>();
      props.Add(StaticListChoiceProperty.CreateForEnum<corners>(PropertyNames.corner, corners.lower_right, false));
      props.Add(StaticListChoiceProperty.CreateForEnum<axis>(PropertyNames.axe, axis.Vertical, false));

      props.Add(new Int32Property(PropertyNames.colorW, ColorBgra.ToOpaqueInt32((ColorBgra)EnvironmentParameters.PrimaryColor), 0, 0xffffff));

      props.Add(new DoubleProperty(PropertyNames.curlPow, 1.5, 0.0, 10.0));
      props.Add(new Int32Property(PropertyNames.curl_tr, 255, 0, 255));
      props.Add(StaticListChoiceProperty.CreateForEnum<colorchoice>(PropertyNames.curl_col, colorchoice.ColorWheel, false));

      props.Add(new Int32Property(PropertyNames.shad_tr, 255, 0, 255));

      props.Add(new BooleanProperty(PropertyNames.backimage, false));
      props.Add(new Int32Property(PropertyNames.back_tr, 255, 0, 255));
      props.Add(StaticListChoiceProperty.CreateForEnum<colorchoice>(PropertyNames.back_col, colorchoice.Secondary, false));

      return new PropertyCollection(props);
    }

    protected override ControlInfo OnCreateConfigUI(PropertyCollection props)
    {
      ControlInfo configUI = CreateDefaultConfigUI(props);

      if (CICC == "French")
      {
        configUI.SetPropertyControlValue(PropertyNames.colorW, ControlInfoPropertyNames.DisplayName, "Choix de la Couleur");
        configUI.SetPropertyControlValue(PropertyNames.backimage, ControlInfoPropertyNames.Description, "Conserver l'arrière plan");
        configUI.SetPropertyControlValue(PropertyNames.back_tr, ControlInfoPropertyNames.DisplayName, "Transparence de l'arrière plan");
        configUI.SetPropertyControlValue(PropertyNames.back_col, ControlInfoPropertyNames.DisplayName, "Couleur de l'arrière plan");
        configUI.SetPropertyControlValue(PropertyNames.curl_tr, ControlInfoPropertyNames.DisplayName, "Transparence du dos");
        configUI.SetPropertyControlValue(PropertyNames.curl_col, ControlInfoPropertyNames.DisplayName, "Couleur du dos");
        configUI.SetPropertyControlValue(PropertyNames.curlPow, ControlInfoPropertyNames.DisplayName, "Obscurité du dos");
        configUI.SetPropertyControlValue(PropertyNames.shad_tr, ControlInfoPropertyNames.DisplayName, "Niveau de l'ombre");
        configUI.SetPropertyControlValue(PropertyNames.corner, ControlInfoPropertyNames.DisplayName, "Coin à corner");
        configUI.SetPropertyControlValue(PropertyNames.axe, ControlInfoPropertyNames.DisplayName, "Sens de la corne");
        PropertyControlInfo pci1 = configUI.FindControlForPropertyName(PropertyNames.corner);
        pci1.SetValueDisplayName(corners.lower_left, "en bas à gauche");
        pci1.SetValueDisplayName(corners.lower_right, "en bas à droite");
        pci1.SetValueDisplayName(corners.upper_left, "en haut à gauche");
        pci1.SetValueDisplayName(corners.upper_right, "en haut à droite");
      }
      else
      {
        configUI.SetPropertyControlValue(PropertyNames.colorW, ControlInfoPropertyNames.DisplayName, "Choose a color");
        configUI.SetPropertyControlValue(PropertyNames.backimage, ControlInfoPropertyNames.Description, "Keep image on back");
        configUI.SetPropertyControlValue(PropertyNames.back_tr, ControlInfoPropertyNames.DisplayName, "Back transparency");
        configUI.SetPropertyControlValue(PropertyNames.back_col, ControlInfoPropertyNames.DisplayName, "Color of the back");
        configUI.SetPropertyControlValue(PropertyNames.curl_tr, ControlInfoPropertyNames.DisplayName, "Transparency of the curl");
        configUI.SetPropertyControlValue(PropertyNames.curl_col, ControlInfoPropertyNames.DisplayName, "Color of the curl");
        configUI.SetPropertyControlValue(PropertyNames.curlPow, ControlInfoPropertyNames.DisplayName, "Darkness of the curl");
        configUI.SetPropertyControlValue(PropertyNames.shad_tr, ControlInfoPropertyNames.DisplayName, "Level of the shadow");
        configUI.SetPropertyControlValue(PropertyNames.corner, ControlInfoPropertyNames.DisplayName, "Corner to curl");
        configUI.SetPropertyControlValue(PropertyNames.axe, ControlInfoPropertyNames.DisplayName, "Way of the curl");
        PropertyControlInfo pci1 = configUI.FindControlForPropertyName(PropertyNames.corner);
        pci1.SetValueDisplayName(corners.lower_left, "lower left");
        pci1.SetValueDisplayName(corners.lower_right, "lower right");
        pci1.SetValueDisplayName(corners.upper_left, "upper left");
        pci1.SetValueDisplayName(corners.upper_right, "upper right");
      }
      configUI.SetPropertyControlValue(PropertyNames.backimage, ControlInfoPropertyNames.DisplayName, String.Empty);
      configUI.SetPropertyControlType(PropertyNames.colorW, PropertyControlType.ColorWheel);
      return configUI;
    }

    private int Back_Col, Curl_Col, Corner, Axe;
    private bool BackImage;
    private ColorBgra ColorW, PC, SC;
    private byte Back_Tr, Curl_Tr, Shad_Tr;
    private double CurlPow;
    /***** Variables *****/

    /* Image parameters */

    private long width, height;

    private int sel_x1, sel_y1, sel_x2, sel_y2;
    private int true_sel_width, true_sel_height;
    private int sel_width, sel_height;

    /* Center and radius of circle */
    private vector_t center;
    private double radius;

    /* Useful points to keep around */
    private vector_t left_tangent;
    private vector_t right_tangent;

    /* Slopes --- these are *not* in the usual geometric sense! */
    private double diagl_slope;
    private double diagr_slope;
    private double diagb_slope;
    private double diagm_slope;

    private PdnRegion selectionRegion;
    private Rectangle selection;

    protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs)
    {
      this.ColorW = ColorBgra.FromOpaqueInt32(newToken.GetProperty<Int32Property>(PropertyNames.colorW).Value);
      this.Back_Col = (int)newToken.GetProperty<StaticListChoiceProperty>(PropertyNames.back_col).Value;
      this.Back_Tr = (byte)newToken.GetProperty<Int32Property>(PropertyNames.back_tr).Value;
      this.CurlPow = newToken.GetProperty<DoubleProperty>(PropertyNames.curlPow).Value;

      this.BackImage = newToken.GetProperty<BooleanProperty>(PropertyNames.backimage).Value;
      this.Shad_Tr = (byte)newToken.GetProperty<Int32Property>(PropertyNames.shad_tr).Value;

      this.Curl_Col = (int)newToken.GetProperty<StaticListChoiceProperty>(PropertyNames.curl_col).Value;
      this.Curl_Tr = (byte)newToken.GetProperty<Int32Property>(PropertyNames.curl_tr).Value;

      this.Corner = (int)newToken.GetProperty<StaticListChoiceProperty>(PropertyNames.corner).Value;
      this.Axe = (int)newToken.GetProperty<StaticListChoiceProperty>(PropertyNames.axe).Value;

      ColorW.A = Back_Tr;


      /* Init calculations */
      double k;
      double alpha, beta;
      double angle;
      vector_t v1, v2;

      /* Image parameters */
      selectionRegion = EnvironmentParameters.GetSelection(SrcArgs.Bounds);
      selection = this.EnvironmentParameters.GetSelection(SrcArgs.Bounds).GetBoundsInt();
      PC = (ColorBgra)EnvironmentParameters.PrimaryColor;
      SC = (ColorBgra)EnvironmentParameters.SecondaryColor;

      sel_x1 = selection.Left;
      sel_x2 = selection.Right;
      sel_y1 = selection.Top;
      sel_y2 = selection.Bottom;

      true_sel_width = sel_x2 - sel_x1;
      true_sel_height = sel_y2 - sel_y1;

      if (Axe == (int)axis.Vertical)
      {
        sel_width = true_sel_width;
        sel_height = true_sel_height;
      }
      else
      {
        sel_width = true_sel_height;
        sel_height = true_sel_width;
      }

      width = SrcArgs.Width;
      height = SrcArgs.Height;

      /* Circle parameters */
      alpha = Math.Atan((double)sel_height / sel_width);
      beta = alpha / 2.0;

      k = sel_width / ((Math.PI + alpha) * Math.Sin(beta) + Math.Cos(beta));

      center = v_set(k * Math.Cos(beta), k * Math.Sin(beta));

      radius = center.y;

      /* left_tangent  */
      left_tangent = v_set(radius * -Math.Sin(alpha), radius * Math.Cos(alpha));
      left_tangent = v_add(left_tangent, center);

      /* right_tangent */
      v1 = v_sub(left_tangent, center);
      v2 = v_set(sel_width - center.x, sel_height - center.y);

      angle = -2.0 * Math.Acos(v_dot(v1, v2) / (v_mag(v1) * v_mag(v2)));

      right_tangent = v_set(
            v1.x * Math.Cos(angle) + v1.y * -Math.Sin(angle),
            v1.x * Math.Sin(angle) + v1.y * Math.Cos(angle));

      right_tangent = v_add(right_tangent, center);

      /* Slopes */
      diagl_slope = (double)sel_width / sel_height;
      diagr_slope = (sel_width - right_tangent.x) / (sel_height - right_tangent.y);
      diagb_slope = (right_tangent.y - left_tangent.y) / (right_tangent.x - left_tangent.x);
      diagm_slope = (sel_width - center.x) / sel_height;

      paint_background();
      shade_under();
      shade_curl();

      base.OnSetRenderInfo(newToken, dstArgs, srcArgs);
    }

    protected override void OnRender(Rectangle[] rois, int startIndex, int length)
    {
      //  for (int i = startIndex; i < startIndex + length; ++i)
      //  {
      //    Rectangle rect = rois[i];
      //    RenderRI(DstArgs.Surface, SrcArgs.Surface, rect);
      //  }
    }

    public vector_t v_set(double x, double y)
    {
      vector_t v;
      v.x = x;
      v.y = y;
      return v;
    }
    public vector_t v_add(vector_t a, vector_t b)
    {
      vector_t v;
      v.x = a.x + b.x;
      v.y = a.y + b.y;
      return v;
    }
    public vector_t v_sub(vector_t a, vector_t b)
    {
      vector_t v;
      v.x = a.x - b.x;
      v.y = a.y - b.y;
      return v;
    }
    public double v_mag(vector_t v)
    {
      return Math.Sqrt(v.x * v.x + v.y * v.y);
    }
    public double v_dot(vector_t a, vector_t b)
    {
      return a.x * b.x + a.y * b.y;
    }

    /*****************************************************
     * Functions to locate the current point in the curl.
     *  The following functions assume an curl in the
     *  lower right corner.
     *  diagb crosses the two tangential points from the
     *  circle with diagl and diagr.
     *
     *   +------------------------------------------------+
     *   |                                          __-- /|
     *   |                                      __--   _//|
     *   |                                  __--      / / |
     *   |                              __--        _/ /  |
     *   |                          __--           /  /   |
     *   |                      __--____         _/  /    |
     *   |                  __----~~    \ diagr_/   /     |
     *   |              __--             |   _/    /      |
     *   |    diagl __--               _/| _/     /       |
     *   |      __--            diagm_/  |/      /diags   |
     *   |  __--                    /___/       /         |
     *   +-------------------------+-----------+----------+
     */
    public bool left_of_diagl(double x, double y)
    {
      return (x < (sel_width + (y - sel_height) * diagl_slope));
    } /* left_of_diagl */
    public bool right_of_diagr(double x, double y)
    {
      return (x > (sel_width + (y - sel_height) * diagr_slope));
    } /* right_of_diagr */
    public bool below_diagb(double x, double y)
    {
      return (y < (right_tangent.y + (x - right_tangent.x) * diagb_slope));
    } /* below_diagb */
    public bool right_of_diagm(double x, double y)
    {
      return (x > (sel_width + (y - sel_height) * diagm_slope));
    } /* right_of_diagm */
    public bool inside_circle(double x, double y)
    {
      double dx, dy;

      dx = x - center.x;
      dy = y - center.y;

      return (Math.Sqrt(dx * dx + dy * dy) <= radius);
    } /* inside_circle */
    public void put_pixel(int x, int y, ColorBgra p)
    {
      int temp;

      /* Flip horizontally if necessary */
      if ((Corner == (int)corners.upper_left) || (Corner == (int)corners.lower_left))
        x = (sel_width - 1) - x;

      /* Flip vertically if necessary */
      if ((Corner == (int)corners.upper_left) || (Corner == (int)corners.upper_right))
        y = (sel_height - 1) - y;

      /* Rotate if necessary */
      if (Axe == (int)axis.Horizontal)
      {
        y = (sel_height - 1) - y;
        temp = x;
        x = y;
        y = (true_sel_height - 1) - temp;

        if ((Corner == (int)corners.upper_right) || (Corner == (int)corners.lower_left))
        {
          x = (true_sel_width - 1) - x;
          y = (true_sel_height - 1) - y;
        } /* if */
      } /* if */

      /* Transform to image coordinates */
      x = sel_x1 + x;
      y = (sel_y2 - 1) - y;

      //if (WITHIN(0, x, width - 1) && WITHIN(0, y, height - 1)) DstArgs.Surface[x, y] = p;
      if (selectionRegion.IsVisible(x, y)) DstArgs.Surface[x, y] = p;
    } /* put_pixel */

    public ColorBgra get_pixel(Surface image, int x, int y)
    {
      int temp;

      /* Flip horizontally if necessary */
      if ((Corner == (int)corners.upper_left) || (Corner == (int)corners.lower_left))
        x = (sel_width - 1) - x;

      /* Flip vertically if necessary */
      if ((Corner == (int)corners.upper_left) || (Corner == (int)corners.upper_right))
        y = (sel_height - 1) - y;

      /* Rotate if necessary */
      if (Axe == (int)axis.Horizontal)
      {
        y = (sel_height - 1) - y;
        temp = x;
        x = y;
        y = (true_sel_height - 1) - temp;

        if ((Corner == (int)corners.upper_right) || (Corner == (int)corners.lower_left))
        {
          x = (true_sel_width - 1) - x;
          y = (true_sel_height - 1) - y;
        } /* if */
      } /* if */

      /* Transform to image coordinates */
      x = sel_x1 + x;
      y = (sel_y2 - 1) - y;

      if (WITHIN(0, x, width - 1) && WITHIN(0, y, height - 1))
        return image[x, y];
      else
        return ColorBgra.FromBgra(0, 0, 0, 0);
    } /* get_pixel */

    public void paint_background()
    {
      int x, y;
      ColorBgra p = PC;
      bool copy;
      for (y = sel_height - 1; y >= 0; y--)
      {
        for (x = 0; x < sel_width; x++)
        {
          copy = BackImage
                 || !(right_of_diagr(x, y)
                || (right_of_diagm(x, y) && below_diagb(x, y)
              && !inside_circle(x, y)));

          if (copy)
          {
            p = get_pixel(SrcArgs.Surface, x, y);
          }
          else
          {
            if (BackImage)
            {
              p = get_pixel(SrcArgs.Surface, x, y);
            }
            else
            {
              if (Back_Col == (int)colorchoice.ColorWheel) p = ColorW;
              else if (Back_Col == (int)colorchoice.Primary) p = PC;
              else if (Back_Col == (int)colorchoice.Secondary) p = SC;
            }
            p.A = Back_Tr;
          }
          put_pixel(x, y, p);
        } /* for */
      } /* for */
    } /* paint_background */

    public void shade_under()
    {
      int x, y;
      double alpha;
      vector_t v, dl, dr;
      double dl_mag, dr_mag;
      double angle, factor;
      ColorBgra p;

      dl = v_set(-sel_width, -sel_height);
      dl_mag = v_mag(dl);

      dr = v_set(-(sel_width - right_tangent.x), -(sel_height - right_tangent.y));
      dr_mag = v_mag(dr);

      alpha = Math.Acos(v_dot(dl, dr) / (dl_mag * dr_mag));

      for (y = sel_height - 1; y >= 0; y--)
      {
        for (x = 0; x < sel_width; x++)
        {
          if (!left_of_diagl(x, y) && !right_of_diagr(x, y) &&
              !(right_of_diagm(x, y) && below_diagb(x, y) && !inside_circle(x, y)))
          {
            v.x = -(sel_width - x);
            v.y = -(sel_height - y);

            angle = Math.Acos(v_dot(v, dl) / (v_mag(v) * dl_mag));
            factor = (1.0 - angle / alpha) * (Shad_Tr /255.0);

            p = get_pixel(SrcArgs.Surface, x, y);

            //p.A *= factor;
            p.R = Utility.ClampToByte(p.R * factor);
            p.G = Utility.ClampToByte(p.G * factor);
            p.B = Utility.ClampToByte(p.B * factor);

            put_pixel(x, y, p);
          } /* if */
        } /* for */
      } /* for */
    } /* shade_under */

    public void shade_curl()
    {
      int x, y;
      ColorBgra p = PC, c = SC;
      vector_t v, dl, dr;
      double dl_mag, dr_mag;
      double alpha;
      double angle;
      double intensity, shade;
      shade = (double)Curl_Tr / 255.0;

      dl = v_set(-sel_width, -sel_height);
      dl_mag = v_mag(dl);

      dr = v_set(-(sel_width - right_tangent.x), -(sel_height - right_tangent.y));
      dr_mag = v_mag(dr);

      alpha = Math.Acos(v_dot(dl, dr) / (dl_mag * dr_mag));

      for (y = sel_height - 1; y >= 0; y--)
      {
        for (x = 0; x < sel_width; x++)
        {
          if (!left_of_diagl(x, y) && !right_of_diagr(x, y)
              && !inside_circle(x, y) && !below_diagb(x, y))
          {

            if (Curl_Col == (int)colorchoice.ColorWheel) c = ColorW;
            else if (Curl_Col == (int)colorchoice.Primary) c = PC;
            else if (Curl_Col == (int)colorchoice.Secondary) c = SC;

            v.x = -(sel_width - x);
            v.y = -(sel_height - y);

            angle = Math.Acos(v_dot(v, dl) / (v_mag(v) * dl_mag));

            intensity = Math.Pow(Math.Sin(Math.PI * angle / alpha), CurlPow);

            p = get_pixel(DstArgs.Surface, x, y);

            p.R = Utility.ClampToByte(shade * (intensity * (double)c.R) + (1.0 - shade) * (double)p.R);
            p.G = Utility.ClampToByte(shade * (intensity * (double)c.G) + (1.0 - shade) * (double)p.G);
            p.B = Utility.ClampToByte(shade * (intensity * (double)c.B) + (1.0 - shade) * (double)p.B);
            put_pixel(x, y, p);
          } /* if */
        } /* for */
      } /* for */
    } /* shade_curl */

  }
}
