/*
// Copyright (c) 1993-2017 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
////////////////////////////////////////////////////////////////
*/
#include "opennurbs.h"

#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined 
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif

ON_OBJECT_IMPLEMENT(ON_Leader, ON_Annotation, "945BF594-6FF9-4F5C-BFC0-B3AF528F29D2");

void ON_Leader::Internal_Destroy()
{
  if (nullptr != m_curve)
  {
    delete m_curve;
    m_curve = nullptr;
  }
}

void ON_Leader::Internal_CopyFrom(const ON_Leader& src)
{
  if (nullptr != src.m_curve)
    m_curve = new ON_NurbsCurve(*src.m_curve);
  m_points = src.m_points;
  m_text_point = src.m_text_point;
}


ON_Leader::ON_Leader()
  : ON_Annotation(ON::AnnotationType::Leader)
  , m_curve(nullptr)
  , m_text_point(ON_2dPoint::UnsetPoint)
{
}

ON_Leader::~ON_Leader()
{
  Internal_Destroy();
}

ON_Leader::ON_Leader(const ON_Leader& src)
  : ON_Annotation(src)
{
  if (this != &src)
  {
    Internal_CopyFrom(src);
  }
}

ON_Leader& ON_Leader::operator=(const ON_Leader& src)
{
  if (this != &src)
  {
    Internal_Destroy();
    ON_Annotation::operator=(src);
    Internal_CopyFrom(src);
  }
  return *this;
}

bool ON_Leader::IsValid(ON_TextLog* log) const
{
  return true;
}

void ON_Leader::Dump(ON_TextLog& log) const
{
}

bool ON_Leader::Write(ON_BinaryArchive& archive) const
{
  const int chunk_version = 1;
  if (!archive.BeginWrite3dmAnonymousChunk(chunk_version))
    return false;

  bool rc = false;
  for (;;)
  {
    if (false == ON_Annotation::Internal_WriteAnnotation(archive))
      break;

    if (!archive.WriteArray(m_points))
      break;

    rc = true;
    break;
  }

  if (!archive.EndWrite3dmChunk())
    rc = false;

  return rc;
}

bool ON_Leader::Read(ON_BinaryArchive& archive)
{
  *this = ON_Leader::Empty;

  int chunk_version = 0;
  if (!archive.BeginRead3dmAnonymousChunk(&chunk_version))
    return false;

  bool rc = false;
  for (;;)
  {
    if (chunk_version < 1)
      break;

    if (false == ON_Annotation::Internal_ReadAnnotation(archive))
      break;

    if (!archive.ReadArray(m_points))
      break;

    rc = true;
    break;
  }

  if (!archive.EndRead3dmChunk())
    rc = false;

  return rc;
}


ON::object_type ON_Leader::ObjectType() const
{
  return ON::annotation_object;
}

int ON_Leader::Dimension() const
{
  return 3;
}

bool ON_Leader::GetBBox( // returns true if successful
  double* bbox_min,       // boxmin[dim]
  double* bbox_max,       // boxmax[dim]
  bool grow          // true means grow box
  ) const
{
  return false;
  // This doesn't seem to be useful and gives the wrong answer most of the time
  // when the dimstyle is wrong
  //return GetBBox(&ON_DimStyle::Default, 1.0, ON_3dVector::XAxis, ON_3dVector::YAxis, bbox_min, bbox_max, grow);
}


bool ON_Leader::GetTextXform(
  const ON_Viewport* vp,
  const ON_DimStyle* dimstyle,
  double dimscale,
  ON_Xform& text_xform_out
)const
{
  return GetTextXform(nullptr, vp, dimstyle, dimscale, text_xform_out);
}

bool ON_Leader::GetTextXform(
  const ON_Xform* model_xform,
  const ON_Viewport* vp,
  const ON_DimStyle* dimstyle,
  double dimscale,
  ON_Xform& text_xform_out
) const
{
  if (nullptr == dimstyle)
    return false;

  const ON_TextContent* text = Text();
  if (nullptr == text)
    return true;

  if ( DimStyleTextPositionPropertiesHash() != dimstyle->TextPositionPropertiesHash() )
  {
    ON_wString rtfstr = text->RtfText();
    ON_Plane objectplane = Plane();
    const_cast<ON_TextContent*>(text)->Create(
      rtfstr, ON::AnnotationType::Leader, dimstyle,
      text->TextIsWrapped(), text->FormattingRectangleWidth(), text->TextRotationRadians());

    ON::TextHorizontalAlignment halign = dimstyle->LeaderTextHorizontalAlignment();
    ON::TextVerticalAlignment valign = dimstyle->LeaderTextVerticalAlignment();
    const_cast<ON_TextContent*>(text)->SetAlignment(halign, valign);
  }

  ON_2dVector tail_dir = TailDirection(dimstyle);

  // Find center of scaled text
  double textblock_width = 0.0;
  double textblock_height = 0.0;
  double line_height = 1.0;

  {
    ON_2dPoint text_corners[4];
    ON_2dPoint text_center;
    ON_2dPoint text_pt(0.0, 0.0);
    if (text->Get2dCorners(text_corners)) // Gets unscaled 3d size
    {
      text_center = (text_corners[0] + text_corners[2]) / 2.0;
      textblock_width  = (text_corners[1].x - text_corners[0].x);
      textblock_height = (text_corners[3].y - text_corners[0].y);
      line_height = dimstyle->TextHeight();

      ON_2dVector text_shift;
      text_shift.x = 0.0;
      text_shift.y = 0.0;

      // LeaderAttachStyle - Vertical alignment of text with leader text point
      ON::TextVerticalAlignment attach = dimstyle->LeaderTextVerticalAlignment();
      switch (attach)
      {
      case ON::TextVerticalAlignment::Top:
        text_shift.y = -textblock_height / 2.0;
        break;
      case ON::TextVerticalAlignment::MiddleOfTop:
        text_shift.y = -(textblock_height / 2.0) + (line_height / 2.0);
        break;
      case ON::TextVerticalAlignment::BottomOfTop:
        text_shift.y = -(textblock_height / 2.0) + line_height;
        break;
      case ON::TextVerticalAlignment::Middle:
        text_shift.y = 0.0;
        break;
      case ON::TextVerticalAlignment::MiddleOfBottom:
        text_shift.y = (textblock_height / 2.0) - (line_height / 2.0);
        break;
      case ON::TextVerticalAlignment::Bottom:
        text_shift.y = textblock_height / 2.0;
        break;
      case ON::TextVerticalAlignment::BottomOfBoundingBox:
        text_shift.y = (textblock_height / 2.0) + (dimstyle->TextGap());   /*(line_height / 10.0);*/
        break;
      }

      // 2d Point at center of text but without vertical alignment shift
      ON_2dPoint text_pt2(0.0, 0.0);
      if (0 < m_points.Count())
        text_pt2 = m_points[m_points.Count() - 1];

      double landing_length = 0.0;
      if(dimstyle->LeaderHasLanding())
        landing_length = dimstyle->LeaderLandingLength();
      double text_gap = dimstyle->TextGap();
      double x_offset = dimscale * (landing_length + text_gap + textblock_width / 2.0);

      text_pt2 = text_pt2 + (tail_dir * x_offset);


      // Move from Origin to leader plane
      const ON_Plane& leaderplane = Plane();

      ON_2dVector text_dir(1.0, 0.0);     // Horizontal to cplane - ON_DimStyle::ContentAngleStyle::Horizontal
      ON_DimStyle::ContentAngleStyle textangle_style = dimstyle->LeaderContentAngleStyle();

      if (ON_DimStyle::ContentAngleStyle::Aligned == textangle_style)
      {
        text_dir = tail_dir;
        if (text_dir.x < 0.0)
          text_dir = -text_dir;
      }
      else if (ON_DimStyle::ContentAngleStyle::Rotated == textangle_style)
      {
        text_dir = tail_dir;  // Already has rotation included
      }
      if (!text_dir.Unitize())
        text_dir.Set(1.0, 0.0);

      ON_Xform textscale_xf(ON_Xform::DiagonalTransformation(dimscale));
      ON_Xform textcenter_xf(ON_Xform::IdentityTransformation);        // Centers text block at origin
      ON_Xform textrotation_xf(ON_Xform::IdentityTransformation);      // Text rotation around origin

      ON_Xform world_to_leader_xf(ON_Xform::IdentityTransformation);   // WCS plane to leader plane rotation
      world_to_leader_xf.Rotation(ON_Plane::World_xy, leaderplane);    // Rotate text from starting text plane (wcs) to leader plane

      ON_Xform leader_to_text_pt_xf(ON_Xform::IdentityTransformation); // Leader plane to text point translation

      textrotation_xf.Rotation(text_dir.y, text_dir.x, ON_3dVector::ZAxis, ON_3dPoint::Origin);


      textcenter_xf.m_xform[0][3] = -text_center.x;
      textcenter_xf.m_xform[1][3] = -text_center.y + text_shift.y;

      ON_3dVector view_x = nullptr == vp ? ON_3dVector::XAxis : vp->CameraX();
      ON_3dVector view_y = nullptr == vp ? ON_3dVector::YAxis : vp->CameraY();

      if(ON::TextOrientation::InView == dimstyle->LeaderTextOrientation())
      {
        const ON_Plane& ldrplane = Plane();
        ON_3dVector view_z = ON_CrossProduct(view_x, view_y);
        ON_3dPoint text_point_3d = Plane().PointAt(text_pt2.x, text_pt2.y);
        textrotation_xf.Rotation(text_point_3d, ldrplane.xaxis, ldrplane.yaxis, ldrplane.zaxis, text_point_3d, view_x, view_y, view_z);
        leader_to_text_pt_xf = ON_Xform::TranslationTransformation(text_pt2);
        text_xform_out = textscale_xf * textcenter_xf;
        text_xform_out = leader_to_text_pt_xf * text_xform_out;
        text_xform_out = world_to_leader_xf * text_xform_out;
        text_xform_out = textrotation_xf * text_xform_out;
        return true;
      }
      else
        if (dimstyle->DrawForward())
      {
        ON_Xform dxf(ON_Xform::IdentityTransformation);
        dxf = textrotation_xf * dxf;
        dxf = world_to_leader_xf * dxf;
        ON_3dVector x(ON_3dVector::XAxis);
        ON_3dVector y(ON_3dVector::YAxis);
        //ON_3dVector x(leaderplane.xaxis);
        //ON_3dVector y(leaderplane.yaxis);
        x.Transform(dxf);
        y.Transform(dxf);
        if (nullptr != model_xform)
        {
          x.Transform(*model_xform);
          y.Transform(*model_xform);
        }
        double xovx = view_x * x;
        double yovy = view_y * y;
        bool fx = (0.00015 >= xovx);
        bool fy = (0.00015 >= yovy);
        if (fx || fy)
        {
          double xovy = x * view_y;
          if (0.99985 > xovy)
          {
            ON_Xform mxf(ON_Xform::IdentityTransformation);
            if (fx)
            {
              mxf.Mirror(ON_3dPoint(text_center), ON_3dVector::XAxis);
              textcenter_xf = textcenter_xf * mxf;
            }
            if (fy)
            {
              mxf.Mirror(ON_3dPoint(text_center), ON_3dVector::YAxis);
              textcenter_xf.m_xform[1][3] = -text_center.y - text_shift.y;
              textcenter_xf = textcenter_xf * mxf;
            }
          }
        }
      }

      leader_to_text_pt_xf = ON_Xform::TranslationTransformation(text_pt2);

      text_xform_out = textscale_xf * textcenter_xf;
      text_xform_out = textrotation_xf * text_xform_out;
      text_xform_out = leader_to_text_pt_xf * text_xform_out;
      text_xform_out = world_to_leader_xf * text_xform_out;
    }
  }
  return true;
}

// ON_Annotation override
bool  ON_Leader::GetAnnotationBoundingBox(
  const ON_Viewport* vp,
  const ON_DimStyle* dimstyle,
  double dimscale,
  double* boxmin,
  double* boxmax,
  bool bGrow) const
{
  if (nullptr == dimstyle)
    dimstyle = &ON_DimStyle::Default;

  const ON_SHA1_Hash hash = Internal_GetBBox_InputHash(
    vp, dimstyle, dimscale,
    m_text_point,
    m_points.UnsignedCount(),
    m_points.Array()
    );

  if (Internal_GetBBox_Begin(hash, boxmin, boxmax, bGrow))
    return true;
  if (nullptr == boxmin || nullptr == boxmax)
    return false;

  ON_BoundingBox bbox;
  Internal_GetBBox_TextGlyphBox(
    vp,
    dimstyle,
    dimscale,
    bbox
  );

  const ON_Curve* curve = Curve(dimstyle);
  if (nullptr != curve)
  {
    ON_BoundingBox curve_box;
    curve->GetTightBoundingBox(curve_box);
    bbox.Union(curve_box);
  }

  return Internal_GetBBox_End(bbox, hash, boxmin, boxmax, bGrow);
}

bool ON_Leader::Transform(const ON_Xform& xform)
{
  bool rc = ON_Geometry::Transform(xform);
  if (rc)
  {
    if (xform.IsTranslation())
      rc = m_plane.Transform(xform);
    else
    {
      int cnt = m_points.Count();
      ON_3dPointArray pts(cnt+1);
      for (int i = 0; i < cnt; i++)
      {
        pts.AppendNew() = m_plane.PointAt(m_points[i].x, m_points[i].y);
        pts[i].Transform(xform);
      }
      m_text_point = ON_3dPoint::UnsetPoint;
      rc = m_plane.Transform(xform);
      for (int i = 0; i < cnt; i++)
        m_plane.ClosestPointTo(pts[i], &m_points[i].x, &m_points[i].y);
    }
  }
  if (rc && nullptr != m_curve && !m_curve->Transform(xform))
  {
    delete m_curve;
    m_curve = nullptr;
  }
  return rc;
}

// returns the base point and width grip using the current alignments
bool ON_Leader::GetTextGripPoints(
  ON_2dPoint& base,
  ON_2dPoint& width,
  const ON_DimStyle* dimstyle,
  double dimscale) const
{
  const ON_TextContent* text = Text();
  if (nullptr == text)
    return false;

  ON_3dPoint q[4];
  if (!text->Get3dCorners(q))
    return false;

  ON_2dVector taildir = TailDirection(dimstyle);

  ON_3dPoint wp3 = (taildir.x < 0.0)
    ? (q[0] + q[3]) / 2.0
    : (q[1] + q[2]) / 2.0;

  ON_3dPoint bp3 = (taildir.x < 0.0)
    ? (q[1] + q[2]) / 2.0
    : (q[0] + q[3]) / 2.0;

  ON_Xform xform;
  GetTextXform(nullptr, dimstyle, dimscale, xform);
  bp3.Transform(xform);
  wp3.Transform(xform);
  Plane().ClosestPointTo(bp3, &base.x, &base.y);
  Plane().ClosestPointTo(wp3, &width.x, &width.y);

  return true;
}



//bool ON_Leader::Explode(
//  const ON_DimStyle* dimstyle,
//  ON_SimpleArray<const ON_Geometry*> object_parts) const
//{
//  bool rc = true;
//  if (nullptr == dimstyle)
//    dimstyle = &ON_DimStyle::Default;
//  double text_scale = dimstyle->DimScale();
//
//  const ON_TextContent* text = Text();
//  if (nullptr != text)
//  {
//    ON_3dVector view_x(1.0, 0.0, 0.0);
//    ON_3dVector view_y(0.0, 1.0, 0.0);
//    ON_Xform text_xf(1.0);
//    GetTextXform(text_xf, dimstyle, text_scale, view_x, view_y, false);
//    ON_Text* textobj = new ON_Text;
//    if (nullptr != textobj)
//    {
//      // Explode to text object, not to curves
//      ON_Plane plane(ON_xy_plane);
//      plane.Transform(text_xf);
//      ON::TextHorizontalAlignment halign = ON::TextHorizontalAlignment::Left;
//      ON::TextVerticalAlignment valign = ON::TextVerticalAlignment::Top;
//      text->GetAlignment(halign, valign);
//      if (textobj->Create(text->RtfText(), dimstyle, plane, halign, valign, 0.0, 0.0))
//      {
//        object_parts.Append(textobj);
//      }
//      else
//      {
//        delete textobj;
//        textobj = nullptr;
//      }
//    }
//  }
//  const ON_NurbsCurve* curve = Curve(dimstyle);
//  if (nullptr != curve)
//  {
//    ON_NurbsCurve* curvecpy = curve->Duplicate();
//    if (nullptr != curvecpy)
//    {
//      object_parts.Append(curvecpy);
//      rc = true;
//    }
//  }
//
//  if (dimstyle->LeaderHasLanding())
//  {
//    ON_Line line;
//    LandingLine3d(dimstyle, text_scale, line);
//    ON_LineCurve* landing = new ON_LineCurve(line);
//    if (nullptr != landing)
//    {
//      object_parts.Append(landing);
//    }
//  }
//  ON_Arrowhead::arrow_type arrowtype = dimstyle->LeaderArrowType();
//  if (ON_Arrowhead::arrow_type::NoArrow != arrowtype)
//  {
//    ON_Xform arrow_xf(1.0);
//    ON_2dVector uv(1.0, 0.0);
//
//    if (1 < PointCount())
//    {
//      ON_2dPoint c;
//      Point2d(1, c);
//      uv.x = c.x; uv.y = c.y;
//    }
//
//    if (uv.Unitize())
//    {
//      double arrow_scale = dimstyle->LeaderArrowSize() * text_scale;
//      ON_Xform xfr, xfs;
//      arrow_xf.Rotation(ON_xy_plane, Plane());
//      xfs.Scale(arrow_scale, arrow_scale, arrow_scale);
//      xfr.Rotation(uv.y, uv.x, ON_3dVector::ZAxis, ON_3dPoint::Origin);
//      arrow_xf = arrow_xf * xfs;
//      arrow_xf = arrow_xf * xfr;
//    }
//
//    //CRhinoAnnotationDrawing::ExplodeOnArrowhead(arrowtype, dimstyle->LeaderArrowBlockId(), arrow_xf, out_curves, out_objects);
//  }
//
//  return rc;
//
//}

bool ON_Leader::Create(
  const wchar_t* leader_text,
  const ON_DimStyle* dimstyle,
  int point_count,
  const ON_3dPoint* points,
  const ON_Plane& plane,
  bool bWrapped,
  double rect_width
  )
{
  if (point_count < 2)
    return false;
  InvalidateTextPoint();
  
  dimstyle = &ON_DimStyle::DimStyleOrDefault(dimstyle);

  SetDimensionStyleId(*dimstyle);

  SetPlane(plane);
  SetPoints3d(point_count, points);

  ON_TextContent* text = nullptr;
  if (nullptr != leader_text)
  {
    text = new ON_TextContent;
    if (!text->Create(leader_text, Type(), dimstyle, bWrapped, rect_width, 0.0))
    {
      delete text;
      text = 0;
      return false;
    }
  }
  SetText(text);

  return true;
}

void ON_Leader::DeleteCurve() const
{
  if (nullptr != m_curve)
  delete m_curve;
  m_curve = nullptr;
}

const ON_NurbsCurve* ON_Leader::Curve(const ON_DimStyle* dimstyle) const
{
  ON_DimStyle::leader_curve_type curvetype = ON_DimStyle::leader_curve_type::Polyline;
  if (nullptr != dimstyle)
    curvetype = dimstyle->LeaderCurveType();

  if (curvetype == ON_DimStyle::leader_curve_type::None)
  {
    if (nullptr != m_curve)
    {
      delete m_curve;
      m_curve = nullptr;
      const_cast<ON_Leader*>(this)->ClearBoundingBox();
    }
    return nullptr;
  }
  if (nullptr != m_curve &&
      ((curvetype == ON_DimStyle::leader_curve_type::Polyline && m_curve->Order() != 2) ||
       (curvetype == ON_DimStyle::leader_curve_type::Spline && m_curve->Order() == 2 && m_points.Count() > 2)))
  {
    delete m_curve;
    m_curve = nullptr;
  }

  if (nullptr == m_curve)
  {
    // Make a 3d curve to cache for display & picking
    if (m_points.Count() > 1)
    {
      int order;
      int pointcount = m_points.Count();
      ON_3dPointArray points(pointcount);
      for (int i = 0; i < m_points.Count(); i++)
        points.AppendNew() = m_plane.PointAt(m_points[i].x, m_points[i].y);

      if (curvetype == ON_DimStyle::leader_curve_type::Spline)
      {
        order = 4;
        if (points.Count() < 4)
          order = points.Count();
      }
      else
      {
        order = 2;
      }

      ON_NurbsCurve* nc = new ON_NurbsCurve(3, false, order, pointcount);
      if (0 != nc && 0 == m_curve)
      {
        double k = 0.0;
        int ki;
        for (ki = 0; ki < order - 1; ki++)
          nc->m_knot[ki] = k;
        for (int i = 0; i < pointcount - order + 1; i++)
        {
          k += points[i].DistanceTo(points[i + 1]);
          nc->m_knot[ki++] = k;
        }
        if (k > ON_SQRT_EPSILON) // min curve length
        {
          for (int i = 0; i < order - 2; i++)
            nc->m_knot[ki++] = k;
          for (int i = 0; i < pointcount; i++)
            nc->SetCV(i, points[i]);
        }
        else
        {
          delete nc;
          nc = nullptr;
        }
        m_curve = nc;
      }
      const_cast<ON_Leader*>(this)->ClearBoundingBox();
    }
  }
  return m_curve;
}

void ON_Leader::SetPlane(ON_Plane plane)
{
  if (Plane() == plane)
    return;
  ON_Annotation::SetPlane(plane);
  if (nullptr != m_curve)
  {
    delete m_curve;
    m_curve = nullptr;
  }
  ClearBoundingBox();
}

bool ON_Leader::LandingLine2d(
  const ON_DimStyle* dimstyle,
  double dimscale,
  ON_Line& line) const
{
  if (nullptr == dimstyle)
    return false;
  int pointcount = PointCount();
  if (0 == pointcount)
    return false;
  ON_2dVector taildir = TailDirection(dimstyle);
  double landing_length = dimstyle->LeaderLandingLength();
  if (landing_length < ON_SQRT_EPSILON)
  {
    line.from = m_points[pointcount - 1];
    line.to = line.from;
  }

  if (dimstyle->LeaderContentAngleStyle() == ON_DimStyle::ContentAngleStyle::Horizontal)
  {
    if (taildir.x < 0.0)
      taildir.x = -1.0;
    else
      taildir.x = 1.0;

    taildir.y = 0.0;
  }
  line.from = m_points[pointcount - 1];
  line.to = line.from + (landing_length * dimscale) * taildir;
  return true;
}

bool ON_Leader::LandingLine3d(
  const ON_DimStyle* dimstyle,
  double dimscale,
  ON_Line& line) const
{
  ON_Line line2d;
  if (!LandingLine2d(dimstyle, dimscale, line2d))
    return false;
  line.from = m_plane.PointAt(line2d.from.x, line2d.from.y);
  line.to = m_plane.PointAt(line2d.to.x, line2d.to.y);
  return true;
}

// Tests if the text is going to the right or left and
// Changes text alignment (justification) if necessary
// This really changes the TextRun offsets and destroys the Text bbox
// Only changes horizontal alignment
void ON_Leader::UpdateTextAlignment(ON_2dVector angle)
{
  return;
  //if (nullptr == m_text)
  //  return;
  //ON::TextHorizontalAlignment ha;
  //
  //if (0.0 > LeaderContentAngleStyle().x)
  //  ha = ON::TextHorizontalAlignment::Right;
  //else
  //  ha = ON::TextHorizontalAlignment::Left;
  //
  //ON::TextHorizontalAlignment current_h;
  //ON::TextVerticalAlignment current_v;
  //m_text->GetAlignment(current_h, current_v);
  //if (ha != current_h)
  //{
  //  m_text->SetAlignment(ha, current_v);
  //  m_text->ClearBoundingBox();
  //}
}

ON_2dVector ON_Leader::TailDirection(const ON_DimStyle* dimstyle) const
{
  ON_2dVector dir = ON_2dVector::XAxis;
  int pointcount = m_points.Count();
  if (2 <= pointcount)
  {
    dir = m_points[pointcount - 1] - m_points[pointcount - 2];    // This works for Aligned
    if (nullptr != dimstyle)
    {
      if (ON_DimStyle::ContentAngleStyle::Horizontal == dimstyle->LeaderContentAngleStyle())
      {
        if (0.0 > dir.x)  // going to the left
          dir.Set(-1.0, 0.0);
        else
          dir.Set(1.0, 0.0);
      }
      else if (ON_DimStyle::ContentAngleStyle::Rotated == dimstyle->LeaderContentAngleStyle())
      {
        double r = dimstyle->LeaderContentAngleRadians();
        if (fabs(r) > ON_SQRT_EPSILON)
        {
          dir.x = cos(r);
          dir.y = sin(r);
        }
        else
        {
          dir.Set(1.0, 0.0);
        }
      }
    }
    dir.Unitize();
  }
  return dir;
}

void ON_Leader::InvalidateTextPoint()
{
  m_text_point = ON_3dPoint::UnsetPoint;
}

ON_2dPointArray& ON_Leader::Points2d()
{
  return m_points;
}

const ON_2dPointArray& ON_Leader::Points2d() const
{
  return m_points;
}

void ON_Leader::SetPoints2d(int count, const ON_2dPoint* points)
{
  m_points.Empty();
  m_points.Append(count, points);
  if (nullptr != m_curve)
  {
    delete m_curve;
    m_curve = nullptr;
  }
  InvalidateTextPoint();
}

void ON_Leader::SetPoints3d(int count, const ON_3dPoint* points)
{
  m_points.Empty();
  ON_2dPoint uv;
  for (int i = 0; i < count; i++)
  {
    if(m_plane.ClosestPointTo(points[i], &uv.x, &uv.y))
      m_points.Append(uv);
  }
  if (nullptr != m_curve)
  {
    delete m_curve;
    m_curve = nullptr;
  }
  InvalidateTextPoint();
}

void ON_Leader::InsertPoint2d(int atidx, ON_2dPoint point)
{
  m_points.Insert(atidx, point);
  if (nullptr != m_curve)
  {
    delete m_curve;
    m_curve = nullptr;
  }
  InvalidateTextPoint();
}

void ON_Leader::InsertPoint3d(int atidx, ON_3dPoint point)
{
  ON_2dPoint p2;
  if(m_plane.ClosestPointTo(point, &p2.x, &p2.y))
    m_points.Insert(atidx, p2);
}

void ON_Leader::RemovePoint(int idx)
{
  m_points.Remove(idx);
  if (nullptr != m_curve)
  {
    delete m_curve;
    m_curve = nullptr;
  }
  InvalidateTextPoint();
}

void ON_Leader::AppendPoint2d(ON_2dPoint point)
{
  m_points.Append(point);
  if (nullptr != m_curve)
  {
    delete m_curve;
    m_curve = nullptr;
  }
  InvalidateTextPoint();
}

bool ON_Leader::AppendPoint3d(ON_3dPoint point)
{
  ON_2dPoint uv;
  if (m_plane.ClosestPointTo(point, &uv.x, &uv.y))
  {
    m_points.Append(uv);
    if (nullptr != m_curve)
    {
      delete m_curve;
      m_curve = nullptr;
    }
    InvalidateTextPoint();
    return true;
  }
  return false;
}

bool ON_Leader::SetPoint2d(int idx, ON_2dPoint point)
{
  if (idx >= 0 && idx < m_points.Count())
  {
    m_points[idx] = point;
    if (nullptr != m_curve)
    {
      delete m_curve;
      m_curve = nullptr;
    }
    InvalidateTextPoint();
    return true;
  }
  return false;
}

bool ON_Leader::SetPoint3d(int idx, ON_3dPoint point)
{
  if (idx >= 0 && idx < m_points.Count())
  {
    ON_2dPoint uv;
    if (m_plane.ClosestPointTo(point, &uv.x, &uv.y))
    {
      m_points[idx] = uv;
      if (nullptr != m_curve)
      {
        delete m_curve;
        m_curve = nullptr;
      }
      InvalidateTextPoint();
      return true;
    }
  }
  return false;
}

bool ON_Leader::Point2d(int idx, ON_2dPoint& point) const
{
  if (idx >= 0 && idx < m_points.Count())
  {
    point = m_points[idx];
    return true;
  }
  return false;
}

bool ON_Leader::Point3d(int idx, ON_3dPoint& point) const
{
  if (idx >= 0 && idx < m_points.Count())
  {
    point = m_plane.PointAt(m_points[idx].x, m_points[idx].y);
    return true;
  }
  return false;
}

ON__UINT32 ON_Leader::PointCount() const
{
  return m_points.Count();
}

bool ON_Leader::GetTextPoint2d(
  const ON_DimStyle* dimstyle,
  double leaderscale,
  ON_2dPoint& point) const
{
  //if (m_text_point.IsUnsetPoint())
  {
    if (!const_cast<ON_Leader*>(this)->UpdateTextPosition(dimstyle, leaderscale))
      return false;
  }
  point = m_text_point;
  return m_text_point.IsValid();
}

//bool ON_Leader::GetTextPoint3d(ON_3dPoint& point) const
//{
//  //if (m_text_point.IsUnsetPoint())
//  {
//    if (!const_cast<ON_Leader*>(this)->UpdateTextPosition())
//      return false;
//  }
//  point = m_plane.PointAt(m_text_point.x, m_text_point.y);
//  return point.IsValid();
//}

// sets m_text_point and m_text_angle from m_points
// m_text_point is the point near the end of the leader tail
// where content attaches
bool ON_Leader::UpdateTextPosition(
  const ON_DimStyle* dimstyle,
  double leaderscale)
{
  if (nullptr == dimstyle)
    return false;
  ON_2dVector text_dir;
  ON_2dPoint attachpt(ON_2dPoint::Origin);
  ON_2dPoint from(ON_2dPoint::Origin);
  ON_Line ll;
  if (LandingLine2d(dimstyle, leaderscale, ll)) // from ponit is end of landing line
  {
    text_dir = ll.Direction();
    if (text_dir.Unitize())
      from = ll.to;
  }
  else // No landing line - from point is last leader point
  {
    text_dir = TailDirection(dimstyle);
    if (0 < m_points.Count())
      from = m_points[m_points.Count() - 1];
  }

  attachpt = from + (text_dir * dimstyle->TextGap() * leaderscale);
  if (attachpt.IsValid())
  {
    if (attachpt != m_text_point)
      ClearBoundingBox();
    m_text_point = attachpt;
    return true;
  }
  else
    return false;
}




