using System.Collections.Generic;
using System.Collections.ObjectModel;

using GMLC01HerramientasCad.Logica;
using COMUN = GMLC01HerramientasCad.Logica;

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using System;

namespace GMLC01HerramientasCad.Acad
{
    class PlineJig : EntityJig, System.IDisposable
    {
      // Use a separate variable for the most recent point...
      // Again, not strictly necessary, but easier to reference
      Point3d pTempPoint;
      Plane pPlane;
      bool pIsArcSeg;
      bool pIsUndoing;

      private Point3dCollection mPoints = new Point3dCollection();

      private Collection<Coordenada> pBoundary;

      // At this stage, weour arc segments will
      // have a fixed bulge of 1.0...
      // Later we may update the routine to determine
      // the bulge based on the relative location
      // of the cursor
      const double KBulge = 1.0;

      public PlineJig(Matrix3d ucs, Collection<Coordenada> boundary)
        : base(new Polyline())
      {
        // Create a temporary plane, to help with calcs
        Point3d origin = new Point3d(0, 0, 0);
        Vector3d normal = new Vector3d(0, 0, 1);
        normal = normal.TransformBy(ucs);
        pPlane = new Plane(origin, normal);

          pBoundary = boundary;

        // Create polyline, set defaults, add dummy vertex
        Polyline pline = Entity as Polyline;
        pline.SetDatabaseDefaults();
                                // For us, our new polyline will have all properties "By Layer".
                                pline.ColorIndex = 256;
                                pline.Linetype = "ByLayer";
                                pline.LineWeight = LineWeight.ByLayer;
        pline.Normal = normal;
        AddDummyVertex();
      }

      protected override SamplerStatus Sampler(JigPrompts prompts)
      {
        JigPromptPointOptions jigOpts =
          new JigPromptPointOptions();
        jigOpts.UserInputControls =
          (UserInputControls.Accept3dCoordinates |
          UserInputControls.NullResponseAccepted |
          UserInputControls.NoNegativeResponseAccepted
          );

        pIsUndoing = false;

        string deshacer = COMUN.Properties.Resources.SiguientePuntoODeshacer.Split('[')[1].Split(']')[0];

        Polyline pline = Entity as Polyline;
        if (pline.NumberOfVertices == 1)
        {
          // For the first vertex, just ask for the point
          jigOpts.Message =
            "\n" + COMUN.Properties.Resources.PuntoInicialDeLaLinea + " ";
        }
        else if (pline.NumberOfVertices > 1)
        {
          // For subsequent vertices, use a base point
          if (pIsArcSeg)
          {
            jigOpts.SetMessageAndKeywords(
              "\nSpecify endpoint of arc or [Linea/deshacer]: ",
              "Linea deshacer"
            );
          }
          else
          {
            jigOpts.SetMessageAndKeywords(
              "\n" + COMUN.Properties.Resources.SiguientePuntoODeshacer + " ",
              deshacer
              // Poniendo esto se permitiría al usuario introducir arcos.
                //"\nSpecify next point or [Arco/deshacer]: ",
                //"Arco deshacer"
            );
          }
        }
        else // should never happen
          return SamplerStatus.Cancel;

        // Get the point itself
        PromptPointResult res =
          prompts.AcquirePoint(jigOpts);

        if (res.Status == PromptStatus.Keyword)
        {
          if (res.StringResult == "Arco")
          {
            pIsArcSeg = true;
          }
          else if (res.StringResult == "Linea")
          {
            pIsArcSeg = false;
          }
          else if (res.StringResult == deshacer)
          {
            pIsUndoing = true;
          }
          return SamplerStatus.OK;
        }
        else if (res.Status == PromptStatus.OK)
        {
          // Check if it has changed or not
          // (reduces flicker)
          if (pTempPoint == res.Value)
          {
            return SamplerStatus.NoChange;
          }
          else
          {
              ////Si hay boundary, se debe mirar que el punto esté dentro de esta.
              //if (InsideBoundary(new Coordenada(res.value.x, res.value.y, 0)))
              //{
              //    pTempPoint = res.value;
              //    return SamplerStatus.OK;
              //}
              //else
              //{
              //    return SamplerStatus.NoChange;
              //}
              pTempPoint = res.Value; 
              return SamplerStatus.OK;
          }
        }
        return SamplerStatus.Cancel;
      }

      protected override bool Update()
      {
        // Update the dummy vertex to be our
        // 3D point projected onto our plane
        Polyline pline = Entity as Polyline;
        pline.SetPointAt(
          pline.NumberOfVertices - 1,
          pTempPoint.Convert2d(pPlane)
        );

        // If it's supposed to be an arc segment,
        // set the bulge appropriately
        if (pIsArcSeg)
        {
          pline.SetBulgeAt(
            pline.NumberOfVertices-1,
            KBulge
          );
        }
        // Otherwise, it's a line, so set the bulge
        // to zero
        else
        {
          pline.SetBulgeAt(
            pline.NumberOfVertices-1,
            0
          );
        }
        return true;
      }

      public Entity GetEntity()
      {
          return Entity;
      }

      public double GetMediumHeight()
      {
          if (mPoints.Count == 0) return 0;
          double retVal = 0;

          for (int i = 0; i < mPoints.Count - 1; i++)
          {
              retVal += mPoints[i].Z;
          }

          if (mPoints[0].X != mPoints[mPoints.Count - 1].X || mPoints[0].Y != mPoints[mPoints.Count - 1].Y)
          {
              retVal += mPoints[mPoints.Count].Z;
              return retVal / mPoints.Count;
          }
          else
          {
              return retVal / (mPoints.Count - 1);
          }

      }

      public bool IsArcSegment()
      {
        return pIsArcSeg;
      }

      public bool IsUndoing()
      {
        return pIsUndoing;
      }

      public void AddDummyVertex()
      {
        Polyline pline = Entity as Polyline;
          if (pline.NumberOfVertices == 0 || InsideBoundary(new Coordenada(pTempPoint.X, pTempPoint.Y, 0)))
          {
            // Create a new dummy vertex...
            // can have any initial value
            if (pline.NumberOfVertices > 0) mPoints.Add(pTempPoint);
            pline.AddVertexAt(
              pline.NumberOfVertices,
              new Point2d(0,0),
              0,0,0
            );
          }
          else
          {
          }
      }

      public void RemoveDummyVertex()
      {
        Polyline pline = Entity as Polyline;
        // Let's first remove our dummy vertex        
        if (pline.NumberOfVertices > 0)
        {
          pline.RemoveVertexAt(pline.NumberOfVertices - 1);
        }
        // And then check the type of the last segment
        if (pline.NumberOfVertices >= 2)
        {
          double blg =
            pline.GetBulgeAt(pline.NumberOfVertices - 2);
          pIsArcSeg = (blg != 0);
        }
      }

      public void AdjustSegmentType(bool isArc)
      {
        // Change the bulge of the previous segment,
        // if necessary
        double bulge = 0.0;
        if (isArc)
          bulge = KBulge;

        Polyline pline = Entity as Polyline;
        if (pline.NumberOfVertices >= 2)
          pline.SetBulgeAt(pline.NumberOfVertices - 2, bulge);
      }

      private bool InsideBoundary(Coordenada newPoint)
      {
          if (pBoundary == null) return true;

          Coordenada p1, p2;
          bool inside = false;
          if (pBoundary.Count < 3)
          {
              return inside;
          }
          Coordenada oldPoint = pBoundary[pBoundary.Count - 1];
          for (int i = 0; i < pBoundary.Count; i++)
          {
              Coordenada newPt = pBoundary[i];
              if (newPt.CoordX > oldPoint.CoordX)
              {
                  p1 = oldPoint;
                  p2 = newPt;
              }
              else
              {
                  p1 = newPt;
                  p2 = oldPoint;
              }
              if ((newPt.CoordX < newPoint.CoordX) == (newPoint.CoordX <= oldPoint.CoordX)
              && ((long)newPoint.CoordY - (long)p1.CoordY) * (long)(p2.CoordX - p1.CoordX)
               < ((long)p2.CoordY - (long)p1.CoordY) * (long)(newPoint.CoordX - p1.CoordX))
              {
                  inside = !inside;
              }
              oldPoint = newPt;
          }
          return inside;
      }


      #region Miembros de IDisposable

      public void Dispose()
      {
          GC.SuppressFinalize(this);
          if (this.pPlane != null)
            this.pPlane.Dispose();
          if (this.mPoints != null)
              this.mPoints.Dispose();
      }

      #endregion
    }
}

