context: using System;
  using System.Collections.Generic;
  using System.Drawing;
  using System.Linq;
  using System.Runtime.InteropServices;
  
  using Microsoft.Office.Core;
  using Microsoft.Office.Interop.PowerPoint;
  
  using PowerPointLabs.Models;
  
  using Shape = Microsoft.Office.Interop.PowerPoint.Shape;
  using ShapeRange = Microsoft.Office.Interop.PowerPoint.ShapeRange;
  using TextFrame2 = Microsoft.Office.Interop.PowerPoint.TextFrame2;
  
  namespace PowerPointLabs.Utils
  {
      internal static class ShapeUtil
      {
  #pragma warning disable 0618
          #region Constants
  
          private const int MaxShapeNameLength = 255;
  
          #endregion
  
          #region API
  
          #region Shape Types
  
          #region Shape
  
          // TODO: This could be an extension method of shape.
          public static bool IsHidden(Shape shape)
          {
              return shape.Visible == MsoTriState.msoFalse;
          }
  
          public static bool IsAGroup(Shape shape)
          {
              return shape.Type == MsoShapeType.msoGroup;
          }
  
          public static bool IsAChild(Shape shape)
          {
              try
              {
                  Shape parent = shape.ParentGroup;
                  return true;
              }
              catch (UnauthorizedAccessException)
              {
                  // Expected exception thrown if shape does not have a parent
                  return false;
              }
          }
          
          public static bool IsStraightLine(Shape shape)
          {
              return shape.Type == MsoShapeType.msoLine ||
                      (shape.Type == MsoShapeType.msoAutoShape &&
                       shape.AutoShapeType == MsoAutoShapeType.msoShapeMixed &&
                       shape.ConnectorFormat.Type == MsoConnectorType.msoConnectorStraight);
          }
  
          public static bool IsShape(Shape shape)
          {
              return shape.Type == MsoShapeType.msoAutoShape
                  || shape.Type == MsoShapeType.msoFreeform
                  || shape.Type == MsoShapeType.msoGroup;
          }
  
          public static bool IsPicture(Shape shape)
          {
              return shape.Type == MsoShapeType.msoPicture ||
                     shape.Type == MsoShapeType.msoLinkedPicture;
          }
  
          public static bool IsPictureOrShape(Shape shape)
          {
              return IsPicture(shape) || IsShape(shape);
          }
  
          public static bool IsSameType(Shape refShape, Shape candidateShape)
          {
              return refShape != null &&
                     candidateShape != null &&
                     refShape.Type == candidateShape.Type &&
                     (refShape.Type != MsoShapeType.msoAutoShape ||
                     refShape.AutoShapeType == candidateShape.AutoShapeType);
          }
  
          public static bool CanAddArrows(Shape shape)
          {
              try
              {
                  if (shape.Line.Visible != MsoTriState.msoTrue)
                  {
                      return false;
                  }
                  MsoArrowheadStyle style = shape.Line.BeginArrowheadStyle;
                  return true;
              }
              catch (ArgumentException)
              {
                  return false;
              }
          }
  
          #endregion
  
          #region ShapeRange
  
          public static bool IsAllPictureOrShape(ShapeRange shapeRange)
          {
              return (from Shape shape in shapeRange select shape).All(IsPictureOrShape);
          }
  
          public static bool IsAllPicture(ShapeRange shapeRange)
          {
              return (from Shape shape in shapeRange select shape).All(IsPicture);
          }
  
          public static bool IsAllPictureWithReferenceObject(ShapeRange shapeRange)
          {
              if (!IsPictureOrShape(shapeRange[1]))
              {
                  return false;
              }
              for (int i = 2; i <= shapeRange.Count; i++)
              {
                  if (!IsPicture(shapeRange[i]))
                  {
                      return false;
                  }
              }
              return true;
          }
  
          public static bool IsAllShape(ShapeRange shapeRange)
          {
              return (from Shape shape in shapeRange select shape).All(IsShape);
          }
  
          #endregion
  
          #region Selection
  
          public static bool IsSelectionShape(Selection selection)
          {
              return selection.Type == PpSelectionType.ppSelectionShapes &&
                      selection.ShapeRange.Count >= 1;
          }
  
          public static bool IsSelectionShapeOrText(Selection selection)
          {
              if (selection.Type != PpSelectionType.ppSelectionShapes &&
                  selection.Type != PpSelectionType.ppSelectionText)
              {
                  return false;
              }
  
              foreach (Shape shape in selection.ShapeRange)
              {
                  if (shape.Type == MsoShapeType.msoPlaceholder)
                  {
                      return false;
                  }
              }
  
              return true;
          }
  
          public static bool HasPlaceholderInSelection(Selection selection)
          {
              if (selection.Type != PpSelectionType.ppSelectionShapes)
              {
                  return false;
              }
  
              foreach (Shape shape in selection.ShapeRange)
              {
                  if (shape.Type == MsoShapeType.msoPlaceholder)
                  {
                      return true;
                  }
              }
  
              return false;
          }
  
          public static bool IsSelectionSingleShape(Selection selection)
          {
              if (selection.Type != PpSelectionType.ppSelectionShapes)
              {
                  return false;
              }
  
              if (selection.HasChildShapeRange)
              {
                  return selection.ChildShapeRange.Count == 1;
              }
  
              return selection.ShapeRange.Count == 1;
          }
  
          public static bool IsSelectionMultipleOrGroup(Selection selection)
          {
              if (selection.Type != PpSelectionType.ppSelectionShapes)
              {
                  return false;
              }
  
              if (selection.ShapeRange.Count > 1)
              {
                  return true;
              }
  
              if (IsAGroup(selection.ShapeRange[1]))
              {
                  return true;
              }
  
              return false;
          }
  
          public static bool IsSelectionMultipleSameShapeType(Selection selection)
          {
              bool result = false;
  
              if (selection.Type != PpSelectionType.ppSelectionShapes)
              {
                  return result;
              }
  
              Shape shape = selection.ShapeRange[1];
  
              if (selection.ShapeRange.Count > 1)
              {
                  foreach (Shape tempShape in selection.ShapeRange)
                  {
                      if (shape.Type == tempShape.Type)
                      {
                          result = true;
                      }
                      if (shape.Type == MsoShapeType.msoAutoShape && shape.AutoShapeType != tempShape.AutoShapeType)
                      {
                          result = false;
                          break;
                      }
                  }
              }
              return result;
          }
  
          public static bool IsSelectionAllRectangle(Selection selection)
          {
              if (selection.Type != PpSelectionType.ppSelectionShapes)
              {
                  return false;
              }
  
              ShapeRange shapes = selection.ShapeRange;
              foreach (Shape shape in shapes)
              {
                  if ((shape.Type != MsoShapeType.msoAutoShape ||
                     shape.AutoShapeType != MsoAutoShapeType.msoShapeRectangle) &&
                     shape.Type != MsoShapeType.msoPicture)
                  {
                      return false;
                  }
              }
              return true;
          }
  
          public static bool IsSelectionAllShapeWithArea(Selection selection)
          {
              if (selection.Type != PpSelectionType.ppSelectionShapes)
              {
                  return false;
              }
  
              ShapeRange shapes = selection.ShapeRange;
              foreach (Shape shape in shapes)
              {
                  if (shape.Type != MsoShapeType.msoAutoShape && shape.Type != MsoShapeType.msoFreeform &&
                      shape.Type != MsoShapeType.msoTextBox && shape.Type != MsoShapeType.msoPlaceholder &&
                      shape.Type != MsoShapeType.msoCallout && shape.Type != MsoShapeType.msoInk &&
                      shape.Type != MsoShapeType.msoGroup)
                  {
                      return false;
                  }
              }
              return true;
          }
  
          public static bool IsSelectionChildShapeRange(Selection selection)
          {
              return selection.HasChildShapeRange;
          }
  
          #endregion
  
          #endregion
  
          #region Shape Name
  
          public static bool IsShapeNameOverMaximumLength(string shapeName)
          {
              return shapeName.Length > MaxShapeNameLength;
          }
  
          // TODO: This could be an extension method of shape.
          public static bool HasDefaultName(Shape shape)
          {
              var copy = shape.Duplicate()[1];
              bool hasDefaultName = copy.Name != shape.Name;
              copy.Delete();
              return hasDefaultName;
          }
  
          #endregion
  
          #region Corruption Handling
  
          public static bool IsCorrupted(Shape shape)
          {
              try
              {
                  return shape.Parent == null;
              }
              catch (Exception)
              {
                  return true;
              }
          }
  
          public static Shape CorruptionCorrection(Shape shape, PowerPointSlide ownerSlide)
          {
              // in case of random corruption of shape, cut-paste a shape before using its property
              Shape correctedShape = ownerSlide.CopyShapeToSlide(shape);
              shape.Delete();
              return correctedShape;
          }
  
          public static ShapeRange CorruptionCorrection(ShapeRange shapes, PowerPointSlide ownerSlide)
          {
              List<Shape> correctedShapeList = new List<Shape>();
              foreach (Shape shape in shapes)
              {
                  Shape correctedShape = CorruptionCorrection(shape, ownerSlide);
                  correctedShapeList.Add(correctedShape);
              }
              return ownerSlide.ToShapeRange(correctedShapeList);
          }
  
          #endregion
  
          #region Size and Position
  
          public static bool IsSamePosition(Shape refShape, Shape candidateShape,
                                            bool exactMatch = true, float blurRadius = float.Epsilon)
          {
              if (exactMatch)
              {
                  blurRadius = float.Epsilon;
              }
  
              return refShape != null &&
                     candidateShape != null &&
                     Math.Abs(refShape.Left - candidateShape.Left) < blurRadius &&
                     Math.Abs(refShape.Top - candidateShape.Top) < blurRadius;
          }
  
          public static bool IsSameSize(Shape refShape, Shape candidateShape,
                                        bool exactMatch = true, float blurRadius = float.Epsilon)
          {
              if (exactMatch)
              {
                  blurRadius = float.Epsilon;
              }
  
              return refShape != null &&
                     candidateShape != null &&
                     Math.Abs(refShape.Width - candidateShape.Width) < blurRadius &&
                     Math.Abs(refShape.Height - candidateShape.Height) < blurRadius;
          }
  
          // TODO: This could be an extension method of shape.
          public static float GetMidpointX(Shape shape)
          {
              return shape.Left + shape.Width / 2;
          }
  
          // TODO: This could be an extension method of shape.
          public static void SetMidpointX(Shape shape, float value)
          {
              shape.Left = value - shape.Width / 2;
          }
  
          // TODO: This could be an extension method of shape.
          public static float GetMidpointY(Shape shape)
          {
              return shape.Top + shape.Height / 2;
          }
  
          // TODO: This could be an extension method of shape.
          public static void SetMidpointY(Shape shape, float value)
          {
              shape.Top = value - shape.Height / 2;
          }
  
          // TODO: This could be an extension method of shape.
          public static float GetRight(Shape shape)
          {
              return shape.Left + shape.Width;
          }
  
          // TODO: This could be an extension method of shape.
          public static void SetRight(Shape shape, float value)
          {
              shape.Left = value - shape.Width;
          }
  
          // TODO: This could be an extension method of shape.
          public static float GetBottom(Shape shape)
          {
              return shape.Top + shape.Height;
          }
  
          // TODO: This could be an extension method of shape.
          public static void SetBottom(Shape shape, float value)
          {
              shape.Top = value - shape.Height;
          }
  
          public static float GetScaleWidth(Shape shape)
          {
              if (IsShape(shape))
              {
                  return 1.0f;
              }
  
              var isAspectRatioLocked = shape.LockAspectRatio;
              shape.LockAspectRatio = MsoTriState.msoFalse;
  
              float oldWidth = shape.Width;
              shape.ScaleWidth(1, MsoTriState.msoCTrue);
              float scaleFactorWidth = oldWidth / shape.Width;
  
              shape.ScaleWidth(scaleFactorWidth, MsoTriState.msoCTrue);
  
              shape.LockAspectRatio = isAspectRatioLocked;
              return scaleFactorWidth;
          }
  
          public static float GetScaleHeight(Shape shape)
          {
              if (IsShape(shape))
              {
                  return 1.0f;
              }
  
              var isAspectRatioLocked = shape.LockAspectRatio;
              shape.LockAspectRatio = MsoTriState.msoFalse;
  
              float oldHeight = shape.Height;
              shape.ScaleHeight(1, MsoTriState.msoCTrue);
              float scaleFactorHeight = oldHeight / shape.Height;
  
              shape.ScaleHeight(scaleFactorHeight, MsoTriState.msoCTrue);
  
              shape.LockAspectRatio = isAspectRatioLocked;
              return scaleFactorHeight;
          }
  
          public static PointF GetCenterPoint(Shape s)
          {
              return new PointF(s.Left + s.Width / 2, s.Top + s.Height / 2);
          }
  
          // TODO: This could be an extension method of shape.
          /// <summary>
          /// anchorFraction = 0 means left side, anchorFraction = 1 means right side.
          /// </summary>
          public static void SetShapeX(Shape shape, float value, float anchorFraction)
          {
              shape.Left = value - shape.Width * anchorFraction;
          }
  
          /// <summary>
          /// anchorFraction = 0 means top side, anchorFraction = 1 means bottom side.
          /// </summary>
          public static void SetShapeY(Shape shape, float value, float anchorFraction)
          {
              shape.Top = value - shape.Height * anchorFraction;
          }
  
          #endregion
  
          #region ZOrder
  
          /// <summary>
          /// Sort by increasing Z-Order.
          /// (From front to back).
          /// </summary>
          public static void SortByZOrder(List<Shape> shapes)
          {
              shapes.Sort((sh1, sh2) => sh2.ZOrderPosition - sh1.ZOrderPosition);
          }
  
          /// <summary>
          /// Moves shiftShape until it is at destination zOrder index.
          /// </summary>
          public static void MoveZTo(Shape shiftShape, int destination)
          {
              while (shiftShape.ZOrderPosition < destination)
              {
                  int currentValue = shiftShape.ZOrderPosition;
                  shiftShape.ZOrder(MsoZOrderCmd.msoBringForward);
                  if (shiftShape.ZOrderPosition == currentValue)
                  {
                      // Break if no change. Guards against infinite loops.
                      break;
                  }
              }
  
              while (shiftShape.ZOrderPosition > destination)
              {
                  int currentValue = shiftShape.ZOrderPosition;
                  shiftShape.ZOrder(MsoZOrderCmd.msoSendBackward);
                  if (shiftShape.ZOrderPosition == currentValue)
                  {
                      // Break if no change. Guards against infinite loops.
                      break;
                  }
              }
          }
  
          /// <summary>
          /// Moves shiftShape forward until it is in front of destinationShape.
          /// (does nothing if already in front)
          /// </summary>
          public static void MoveZUntilInFront(Shape shiftShape, Shape destinationShape)
          {
              while (shiftShape.ZOrderPosition < destinationShape.ZOrderPosition)
              {
                  int currentValue = shiftShape.ZOrderPosition;
                  shiftShape.ZOrder(MsoZOrderCmd.msoBringForward);
                  if (shiftShape.ZOrderPosition == currentValue)
                  {
                      // Break if no change. Guards against infinite loops.
                      break;
                  }
              }
          }
  
          /// <summary>
          /// Moves shiftShape backward until it is behind destinationShape.
          /// (does nothing if already behind)
          /// </summary>
          public static void MoveZUntilBehind(Shape shiftShape, Shape destinationShape)
          {
              while (shiftShape.ZOrderPosition > destinationShape.ZOrderPosition)
              {
                  int currentValue = shiftShape.ZOrderPosition;
                  shiftShape.ZOrder(MsoZOrderCmd.msoSendBackward);
                  if (shiftShape.ZOrderPosition == currentValue)
                  {
                      // Break if no change. Guards against infinite loops.
                      break;
                  }
              }
          }
  
          public static void SwapZOrder(Shape originalShape, Shape destinationShape)
          {
              Shape lowerZOrderShape = originalShape;
              Shape higherZOrderShape = destinationShape;
              if (originalShape.ZOrderPosition > destinationShape.ZOrderPosition)
              {
                  lowerZOrderShape = destinationShape;
                  higherZOrderShape = originalShape;
              }
              int lowerZOrder = lowerZOrderShape.ZOrderPosition;
              int higherZOrder = higherZOrderShape.ZOrderPosition;
  
              // If shape is a group, our target zOrder needs to be offset by the number of items in the group
              // This is to account for the zOrder of the items in the group
              if (IsAGroup(lowerZOrderShape))
              {
                  higherZOrder -= lowerZOrderShape.GroupItems.Count;
              }
  
              if (IsAGroup(higherZOrderShape))
              {
                  higherZOrder += higherZOrderShape.GroupItems.Count;
              }
  
              MoveZTo(lowerZOrderShape, higherZOrder);
  
              MoveZTo(higherZOrderShape, lowerZOrder);
          }
  
          /// <summary>
          /// Moves shiftShape forward/backward until it is just behind destinationShape
          /// </summary>
          public static void MoveZToJustBehind(Shape shiftShape, Shape destinationShape)
          {
              // Step 1: Shift forward until it overshoots destination.
              MoveZUntilInFront(shiftShape, destinationShape);
  
              // Step 2: Shift backward until it overshoots destination.
              MoveZUntilBehind(shiftShape, destinationShape);
          }
  
          /// <summary>
          /// Moves shiftShape forward/backward until it is just in front of destinationShape
          /// </summary>
          public static void MoveZToJustInFront(Shape shiftShape, Shape destinationShape)
          {
              // Step 1: Shift backward until it overshoots destination.
              MoveZUntilBehind(shiftShape, destinationShape);
  
              // Step 2: Shift forward until it overshoots destination.
              MoveZUntilInFront(shiftShape, destinationShape);
          }
  
          #endregion
  
          #region Transformations
  
          public static void FitShapeToSlide(ref Shape shapeToMove)
          {
              shapeToMove.LockAspectRatio = MsoTriState.msoFalse;
              shapeToMove.Left = 0;
              shapeToMove.Top = 0;
              shapeToMove.Width = PowerPointPresentation.Current.SlideWidth;
              shapeToMove.Height = PowerPointPresentation.Current.SlideHeight;
          }
  
          /// <summary>
          /// anchorX and anchorY are between 0 and 1. They represent the pivot to rotate the shape about.
          /// The shape rotates by angle difference angle from its current angle. angle is in degrees.
          /// </summary>
          public static void RotateShapeAboutPivot(Shape shape, float angle, float anchorX, float anchorY)
          {
              double pivotX = shape.Left + anchorX * shape.Width;
              double pivotY = shape.Top + anchorY * shape.Height;
              double midpointX = GetMidpointX(shape);
              double midpointY = GetMidpointY(shape);
  
              double dx = midpointX - pivotX;
  
gt:             double dy = midpointY - pivotY;