/***************************************************************************
 *
 * Project:  OpenCPN
 *
 ***************************************************************************
 *   Copyright (C) 2013 by David S. Register                               *
 *                                                                         *
 *   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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA.         *
 **************************************************************************/

#include "wx/wxprec.h"

#include "Route.h"
#include "georef.h"
#include "routeman.h"
#include "ocpndc.h"
#include "cutil.h"
#include "navutil.h"
#include "multiplexer.h"
#include "Select.h"
#include "georef.h"
#include "OCPNPlatform.h"
#include "chcanv.h"
#include "gui_lib.h"

extern WayPointman *pWayPointMan;
extern Routeman *g_pRouteMan;
extern int g_route_line_width;
extern Select *pSelect;
extern MyConfig *pConfig;
extern Multiplexer *g_pMUX;
extern double g_n_arrival_circle_radius;
extern float g_GLMinSymbolLineWidth;
extern double g_PlanSpeed;
extern OCPNPlatform *g_Platform;
extern wxString g_default_routepoint_icon;
extern bool g_bAllowShipToActive;
;

#include <wx/listimpl.cpp>
WX_DEFINE_LIST(RouteList);

Route::Route() {
  m_bRtIsSelected = false;
  m_bRtIsActive = false;
  m_pRouteActivePoint = NULL;
  m_bIsBeingEdited = false;
  m_bIsBeingCreated = false;
  m_nm_sequence = 1;
  m_route_length = 0.0;
  m_route_time = 0.0;
  m_bVisible = true;
  m_bListed = true;
  m_bDeleteOnArrival = false;
  m_width = WIDTH_UNDEFINED;
  m_style = wxPENSTYLE_INVALID;
  m_hiliteWidth = 0;

  pRoutePointList = new RoutePointList;
  m_GUID = pWayPointMan->CreateGUID(NULL);
  m_btemp = false;

  m_ArrivalRadius = g_n_arrival_circle_radius;  // Nautical Miles

  m_LayerID = 0;
  m_bIsInLayer = false;

  m_Colour = wxEmptyString;

  m_lastMousePointIndex = 0;
  m_NextLegGreatCircle = false;

  m_PlannedSpeed = ROUTE_DEFAULT_SPEED;
  if (g_PlanSpeed != ROUTE_DEFAULT_SPEED) m_PlannedSpeed = g_PlanSpeed;

  m_PlannedDeparture = RTE_UNDEF_DEPARTURE;
  m_TimeDisplayFormat = RTE_TIME_DISP_PC;
  m_HyperlinkList = new HyperlinkList;

  m_bsharedWPViz = false;
}

Route::~Route() {
  pRoutePointList->DeleteContents(false);  // do not delete Marks
  delete pRoutePointList;
  delete m_HyperlinkList;
}

// The following is used only for route splitting, assumes just created, empty
// route
//
void Route::CloneRoute(Route *psourceroute, int start_nPoint, int end_nPoint,
  const wxString &suffix,
  const bool duplicate_first_point) {
  m_RouteNameString = psourceroute->m_RouteNameString + suffix;
  m_RouteStartString = psourceroute->m_RouteStartString;
  m_RouteEndString = psourceroute->m_RouteEndString;

  int i;
  for (i = start_nPoint; i <= end_nPoint; i++) {
    if (!psourceroute->m_bIsInLayer &&
      !(i == start_nPoint && duplicate_first_point)) {
      AddPoint(psourceroute->GetPoint(i), false);
    }
    else {
      RoutePoint *psourcepoint = psourceroute->GetPoint(i);
      RoutePoint *ptargetpoint = new RoutePoint(
        psourcepoint->m_lat, psourcepoint->m_lon, psourcepoint->GetIconName(),
        psourcepoint->GetName(), wxEmptyString, false);
      ptargetpoint->m_bShowName =
        psourcepoint->m_bShowName;  // do not change new wpt's name visibility
      AddPoint(ptargetpoint, false);
    }
  }

  FinalizeForRendering();
}

void Route::AddPoint(RoutePoint *pNewPoint, bool b_rename_in_sequence,
  bool b_deferBoxCalc) {
  if (pNewPoint->m_bIsolatedMark) {
    pNewPoint->SetShared(true);
  }
  pNewPoint->m_bIsolatedMark = false;  // definitely no longer isolated
  pNewPoint->m_bIsInRoute = true;

  RoutePoint *prev = GetLastPoint();
  pRoutePointList->Append(pNewPoint);

  if (!b_deferBoxCalc) FinalizeForRendering();

  if (prev) UpdateSegmentDistance(prev, pNewPoint);

  if (b_rename_in_sequence && pNewPoint->GetName().IsEmpty() &&
    !pNewPoint->IsShared()) {
    wxString name;
    name.Printf(_T("%03d"), GetnPoints());
    pNewPoint->SetName(name);
    pNewPoint->m_bDynamicName = true;
  }
  return;
}

void Route::AddPointAndSegment(RoutePoint *pNewPoint, bool b_rename_in_sequence,
  bool b_deferBoxCalc) {
  int npoints = GetnPoints();
  RoutePoint *newpoint = pNewPoint;
  if (newpoint->m_bIsInLayer) {
    newpoint = new RoutePoint(pNewPoint->m_lat, pNewPoint->m_lon,
      pNewPoint->GetIconName(), pNewPoint->GetName(), wxEmptyString, false);
    newpoint->m_bShowName = pNewPoint->m_bShowName; //do not change new wpt's name visibility
  }
  AddPoint(newpoint, false);
  if (npoints != 0) {
    double rlat = GetPoint(npoints)->m_lat;
    double rlon = GetPoint(npoints)->m_lon;
    npoints = GetnPoints();
    pSelect->AddSelectableRouteSegment(rlat, rlon,
      GetPoint(npoints)->m_lat, GetPoint(npoints)->m_lon, GetPoint(npoints - 1), GetPoint(npoints), this);
  }
  m_lastMousePointIndex = GetnPoints();
}

void Route::InsertPointAndSegment(RoutePoint *pNewPoint, int insert_after, bool bRenamePoints, bool b_deferBoxCalc)
{
  {
    bool add = false;

    if (pNewPoint->m_bIsolatedMark) {
      pNewPoint->SetShared(true);
    }
    pNewPoint->m_bIsolatedMark = false;       // definitely no longer isolated
    pNewPoint->m_bIsInRoute = true;

    if (insert_after >= GetnPoints() - 1) {
      wxLogMessage(wxT("Error insert after last point"));
      return;
    }

    int insert = insert_after++;
    pNewPoint->m_bIsInRoute = true;
    pNewPoint->m_bDynamicName = true;
    pNewPoint->SetNameShown(false);
    pRoutePointList->Insert(insert, pNewPoint);
    if (bRenamePoints) RenameRoutePoints();
    m_lastMousePointIndex = GetnPoints();
    FinalizeForRendering();
    UpdateSegmentDistances();
    return;
  }
}

RoutePoint *Route::GetPoint(int nWhichPoint) {
  RoutePoint *prp;
  wxRoutePointListNode *node = pRoutePointList->GetFirst();

  int i = 1;
  while (node) {
    prp = node->GetData();
    if (i == nWhichPoint) {
      return prp;
    }
    i++;
    node = node->GetNext();
  }

  return (NULL);
}

RoutePoint *Route::GetPoint(const wxString &guid) {
  RoutePoint *prp;
  wxRoutePointListNode *node = pRoutePointList->GetFirst();

  while (node) {
    prp = node->GetData();
    if (guid == prp->m_GUID) return prp;

    node = node->GetNext();
  }

  return (NULL);
}

void Route::DrawPointWhich(ocpnDC &dc, ChartCanvas *canvas, int iPoint,
                           wxPoint *rpn) {
  if (iPoint <= GetnPoints()) GetPoint(iPoint)->Draw(dc, canvas, rpn);
}

void Route::DrawSegment(ocpnDC &dc, ChartCanvas *canvas, wxPoint *rp1,
                        wxPoint *rp2, ViewPort &vp, bool bdraw_arrow) {
  if (m_bRtIsSelected)
    dc.SetPen(*g_pRouteMan->GetSelectedRoutePen());
  else if (m_bRtIsActive)
    dc.SetPen(*g_pRouteMan->GetActiveRoutePen());
  else
    dc.SetPen(*g_pRouteMan->GetRoutePen());

  RenderSegment(dc, rp1->x, rp1->y, rp2->x, rp2->y, vp, bdraw_arrow);
}

void Route::Draw(ocpnDC &dc, ChartCanvas *canvas, const LLBBox &box) {
  if (pRoutePointList->empty()) return;

  ViewPort vp = canvas->GetVP();

  LLBBox test_box = GetBBox();
  if (box.IntersectOut(test_box))  // Route is wholly outside window
    return;

  int width = g_route_line_width;
  if (m_width != WIDTH_UNDEFINED) width = m_width;

  if (m_bVisible && m_bRtIsSelected) {
    wxPen spen = *g_pRouteMan->GetSelectedRoutePen();
    spen.SetWidth(width);
    dc.SetPen(spen);
    dc.SetBrush(*g_pRouteMan->GetSelectedRouteBrush());
  } else if (m_bVisible) {
    wxPenStyle style = wxPENSTYLE_SOLID;
    wxColour col;
    if (m_style != wxPENSTYLE_INVALID) style = m_style;
    if (m_Colour == wxEmptyString) {
      col = g_pRouteMan->GetRoutePen()->GetColour();
    } else {
      for (unsigned int i = 0; i < sizeof(::GpxxColorNames) / sizeof(wxString);
           i++) {
        if (m_Colour == ::GpxxColorNames[i]) {
          col = ::GpxxColors[i];
          break;
        }
      }
    }
    dc.SetPen(*wxThePenList->FindOrCreatePen(col, width, style));
    dc.SetBrush(*wxTheBrushList->FindOrCreateBrush(col, wxBRUSHSTYLE_SOLID));
  }

  if (m_bVisible && m_bRtIsActive) {
    wxPen spen = *g_pRouteMan->GetActiveRoutePen();
    spen.SetWidth(width);
    dc.SetPen(spen);
    dc.SetBrush(*g_pRouteMan->GetActiveRouteBrush());
  }

  wxPoint rpt1, rpt2;
  if (m_bVisible) DrawPointWhich(dc, canvas, 1, &rpt1);

  wxRoutePointListNode *node = pRoutePointList->GetFirst();
  RoutePoint *prp1 = node->GetData();
  node = node->GetNext();

  if (m_bVisible || prp1->IsShared()) prp1->Draw(dc, canvas, NULL);

  while (node) {
    RoutePoint *prp2 = node->GetData();

    bool draw_arrow = !(prp2->m_bIsActive && g_bAllowShipToActive);

    if (m_bVisible || prp2->IsShared()) prp2->Draw(dc, canvas, &rpt2);

    if (m_bVisible) {
      //    Handle offscreen points
      bool b_2_on = vp.GetBBox().Contains(prp2->m_lat, prp2->m_lon);
      bool b_1_on = vp.GetBBox().Contains(prp1->m_lat, prp1->m_lon);

      // Simple case
      if (b_1_on && b_2_on)
        RenderSegment(dc, rpt1.x, rpt1.y, rpt2.x, rpt2.y, vp, draw_arrow,
                      m_hiliteWidth);  // with arrows

      //    In the cases where one point is on, and one off
      //    we must decide which way to go in longitude
      //     Arbitrarily, we will go the shortest way

      double pix_full_circle = WGS84_semimajor_axis_meters * mercator_k0 * 2 *
                               PI * vp.view_scale_ppm;
      double dp =
          pow((double)(rpt1.x - rpt2.x), 2) + pow((double)(rpt1.y - rpt2.y), 2);
      double dtest;
      int adder;
      if (b_1_on && !b_2_on) {
        if (rpt2.x < rpt1.x)
          adder = (int)pix_full_circle;
        else
          adder = -(int)pix_full_circle;

        dtest = pow((double)(rpt1.x - (rpt2.x + adder)), 2) +
                pow((double)(rpt1.y - rpt2.y), 2);

        if (dp < dtest) adder = 0;

        RenderSegment(dc, rpt1.x, rpt1.y, rpt2.x + adder, rpt2.y, vp,
                      draw_arrow, m_hiliteWidth);
      } else if (!b_1_on) {
        if (rpt1.x < rpt2.x)
          adder = (int)pix_full_circle;
        else
          adder = -(int)pix_full_circle;

        float rxd = rpt2.x - (rpt1.x + adder);
        float ryd = rpt1.y - rpt2.y;
        dtest = rxd * rxd + ryd * ryd;

        if (dp < dtest) adder = 0;

        RenderSegment(dc, rpt1.x + adder, rpt1.y, rpt2.x, rpt2.y, vp,
                      draw_arrow, m_hiliteWidth);
      }
    }

    rpt1 = rpt2;
    prp1 = prp2;

    node = node->GetNext();
  }
}

static void TestLongitude(double lon, double min, double max, bool &lonl,
                          bool &lonr) {
  double clon = (min + max) / 2;
  if (min - lon > 180) lon += 360;

  lonl = lonr = false;
  if (lon < min) {
    if (lon < clon - 180)
      lonr = true;
    else
      lonl = true;
  } else if (lon > max) {
    if (lon > clon + 180)
      lonl = true;
    else
      lonr = true;
  }
}

void Route::DrawGLLines(ViewPort &vp, ocpnDC *dc, ChartCanvas *canvas) {
#ifdef ocpnUSE_GL
  float pix_full_circle =
      WGS84_semimajor_axis_meters * mercator_k0 * 2 * PI * vp.view_scale_ppm;

  bool r1valid = false;
  wxPoint2DDouble r1;
  wxPoint2DDouble lastpoint;

  wxRoutePointListNode *node = pRoutePointList->GetFirst();
  RoutePoint *prp2 = node->GetData();
  canvas->GetDoubleCanvasPointPix(prp2->m_lat, prp2->m_lon, &lastpoint);

  if (GetnPoints() == 1 &&
      dc) {  // single point.. make sure it shows up for highlighting
    canvas->GetDoubleCanvasPointPix(prp2->m_lat, prp2->m_lon, &r1);
    dc->DrawLine(r1.m_x, r1.m_y, r1.m_x + 2, r1.m_y + 2);
    return;
  }

  //    Handle offscreen points
  LLBBox bbox = vp.GetBBox();

  // dc is passed for thicker highlighted lines (performance not very important)
#ifndef USE_ANDROID_GLES2
  if (!dc) glBegin(GL_LINES);
#endif

  for (node = node->GetNext(); node; node = node->GetNext()) {
    RoutePoint *prp1 = prp2;
    prp2 = node->GetData();

    // Provisional, to properly set status of last point in route
    prp2->m_pos_on_screen = false;
    {
      wxPoint2DDouble r2;
      canvas->GetDoubleCanvasPointPix(prp2->m_lat, prp2->m_lon, &r2);
      if (std::isnan(r2.m_x)) {
        r1valid = false;
        continue;
      }

      lastpoint = r2;  // For active track segment to ownship

      // don't need to perform calculations or render segment
      // if both points are past any edge of the vp
      // TODO: use these optimizations for dc mode
      bool lat1l = prp1->m_lat < bbox.GetMinLat(),
           lat2l = prp2->m_lat < bbox.GetMinLat();
      bool lat1r = prp1->m_lat > bbox.GetMaxLat(),
           lat2r = prp2->m_lat > bbox.GetMaxLat();
      if ((lat1l && lat2l) || (lat1r && lat2r)) {
        r1valid = false;
        prp1->m_pos_on_screen = false;
        continue;
      }

      bool lon1l, lon1r, lon2l, lon2r;
      TestLongitude(prp1->m_lon, bbox.GetMinLon(), bbox.GetMaxLon(), lon1l,
                    lon1r);
      TestLongitude(prp2->m_lon, bbox.GetMinLon(), bbox.GetMaxLon(), lon2l,
                    lon2r);
      if ((lon1l && lon2l) || (lon1r && lon2r)) {
        r1valid = false;
        prp1->m_pos_on_screen = false;
        continue;
      }

      if (!r1valid) {
        canvas->GetDoubleCanvasPointPix(prp1->m_lat, prp1->m_lon, &r1);
        if (std::isnan(r1.m_x)) continue;
      }

      //  we must decide which way to go in longitude
      //  for projections which wrap, in this case, we will render two lines
      //  (one may often be off screen which would be nice to fix but complicate
      //  things here anyway, in some cases both points are on screen, but the
      //  route wraps to either side so two lines are needed to draw this
      //  properly

      double adder = 0;
      if ((vp.m_projection_type == PROJECTION_MERCATOR ||
           vp.m_projection_type == PROJECTION_EQUIRECTANGULAR)) {
        float olon = vp.clon > 0 ? vp.clon - 180 : vp.clon + 180;

        if (prp1->m_lon < prp2->m_lon) {
          if (prp2->m_lon - prp1->m_lon < 180) {
            if (olon > prp1->m_lon && olon < prp2->m_lon)
              adder = pix_full_circle;
          } else if (olon < prp1->m_lon || olon > prp2->m_lon)
            adder = -pix_full_circle;
        } else if (prp1->m_lon - prp2->m_lon < 180) {
          if (olon < prp1->m_lon && olon > prp2->m_lon)
            adder = -pix_full_circle;
        } else if (olon > prp1->m_lon || olon < prp2->m_lon)
          adder = pix_full_circle;
      }

      if (dc)
        if (adder) {
          float adderc = cos(vp.rotation) * adder,
                adders = sin(vp.rotation) * adder;
          dc->DrawLine(r1.m_x, r1.m_y, r2.m_x + adderc, r2.m_y + adders);
          dc->DrawLine(r1.m_x - adderc, r1.m_y - adders, r2.m_x, r2.m_y);
        } else
          dc->DrawLine(r1.m_x, r1.m_y, r2.m_x, r2.m_y);
      else {
#ifndef USE_ANDROID_GLES2
        glVertex2f(r1.m_x, r1.m_y);
        if (adder) {
          float adderc = cos(vp.rotation) * adder,
                adders = sin(vp.rotation) * adder;
          glVertex2f(r2.m_x + adderc, r2.m_y + adders);
          glVertex2f(r1.m_x - adderc, r1.m_y - adders);
        }
        glVertex2f(r2.m_x, r2.m_y);

        // cache screen position for arrows and points
        if (!r1valid) {
          prp1->m_pos_on_screen = !lat1l && !lat1r && !lon1l && !lon1r;
          prp1->m_screen_pos = r1;
        }

        prp2->m_pos_on_screen = !lat2l && !lat2r && !lon2l && !lon2r;
        prp2->m_screen_pos = r2;
#endif
      }

      r1 = r2;
      r1valid = true;
    }
  }

#ifndef USE_ANDROID_GLES2
  if (!dc) glEnd();
#endif

#endif
}

bool Route::ContainsSharedWP() {
  for (wxRoutePointListNode *node = pRoutePointList->GetFirst(); node;
       node = node->GetNext()) {
    RoutePoint *prp = node->GetData();
    if (prp->IsShared()) return true;
  }
  return false;
}

void Route::DrawGL(ViewPort &vp, ChartCanvas *canvas) {
#ifdef ocpnUSE_GL
  if (pRoutePointList->empty()) return;

  if (!vp.GetBBox().IntersectOut(GetBBox()) && m_bVisible)
    DrawGLRouteLines(vp, canvas);

  /*  Route points  */
  for (wxRoutePointListNode *node = pRoutePointList->GetFirst(); node;
       node = node->GetNext()) {
    RoutePoint *prp = node->GetData();
    // Inflate the bounding box a bit to ensure full drawing in accelerated pan
    // mode.
    // TODO this is a little extravagant, assumming a mark is always a large
    // fixed lat/lon extent.
    //  Maybe better to use the mark's drawn box, once it is known.
    if (vp.GetBBox().ContainsMarge(prp->m_lat, prp->m_lon, .5)) {
      if (m_bVisible || prp->IsShared()) prp->DrawGL(vp, canvas);
    }
  }

#endif
}

void Route::DrawGLRouteLines(ViewPort &vp, ChartCanvas *canvas) {
#ifdef ocpnUSE_GL
  //  Hiliting first
  //  Being special case to draw something for a 1 point route....
  ocpnDC dc;
  if (m_hiliteWidth) {
    wxColour y = GetGlobalColor(_T ( "YELO1" ));
    wxColour hilt(y.Red(), y.Green(), y.Blue(), 128);

    wxPen HiPen(hilt, m_hiliteWidth, wxPENSTYLE_SOLID);

    ocpnDC dc;
    dc.SetPen(HiPen);

    DrawGLLines(vp, &dc, canvas);
  }

  /* determine color and width */
  wxColour col;

  int width = g_pRouteMan->GetRoutePen()->GetWidth();  // g_route_line_width;
  if (m_width != wxPENSTYLE_INVALID) width = m_width;

  if (m_bRtIsActive) {
    col = g_pRouteMan->GetActiveRoutePen()->GetColour();
  } else if (m_bRtIsSelected) {
    col = g_pRouteMan->GetSelectedRoutePen()->GetColour();
  } else {
    if (m_Colour == wxEmptyString) {
      col = g_pRouteMan->GetRoutePen()->GetColour();
    } else {
      for (unsigned int i = 0; i < sizeof(::GpxxColorNames) / sizeof(wxString);
           i++) {
        if (m_Colour == ::GpxxColorNames[i]) {
          col = ::GpxxColors[i];
          break;
        }
      }
    }
  }

  wxPenStyle style = wxPENSTYLE_SOLID;
  if (m_style != wxPENSTYLE_INVALID) style = m_style;
  dc.SetPen(*wxThePenList->FindOrCreatePen(col, width, style));
  dc.SetBrush(*wxTheBrushList->FindOrCreateBrush(col, wxBRUSHSTYLE_SOLID));

  glLineWidth(wxMax(g_GLMinSymbolLineWidth, width));

  dc.SetGLStipple();

#ifdef USE_ANDROID_GLES2
  DrawGLLines(vp, &dc, canvas);
#else
  glColor3ub(col.Red(), col.Green(), col.Blue());
  DrawGLLines(vp, NULL, canvas);
#endif

  glDisable(GL_LINE_STIPPLE);

  /* direction arrows.. could probably be further optimized for opengl */
  wxRoutePointListNode *node = pRoutePointList->GetFirst();
  wxPoint rpt1, rpt2;
  while (node) {
    RoutePoint *prp = node->GetData();
    canvas->GetCanvasPointPix(prp->m_lat, prp->m_lon, &rpt2);
    if (node != pRoutePointList->GetFirst()) {
      if (!prp->m_bIsActive || !g_bAllowShipToActive)
        RenderSegmentArrowsGL(dc, rpt1.x, rpt1.y, rpt2.x, rpt2.y, vp);
    }
    rpt1 = rpt2;
    node = node->GetNext();
  }
#endif
}

static int s_arrow_icon[] = {0, 0, 5, 2, 18, 6, 12, 0, 18, -6, 5, -2, 0, 0};

void Route::RenderSegment(ocpnDC &dc, int xa, int ya, int xb, int yb,
                          ViewPort &vp, bool bdraw_arrow, int hilite_width) {
  //    Get the dc boundary
  int sx, sy;
  dc.GetSize(&sx, &sy);

  //    Try to exit early if the segment is nowhere near the screen
  wxRect r(0, 0, sx, sy);
  wxRect s(xa, ya, 1, 1);
  wxRect t(xb, yb, 1, 1);
  s.Union(t);
  if (!r.Intersects(s)) return;

  //    Clip the line segment to the dc boundary
  int x0 = xa;
  int y0 = ya;
  int x1 = xb;
  int y1 = yb;

  //    If hilite is desired, use a Native Graphics context to render alpha
  //    colours That is, if wxGraphicsContext is available.....

  if (hilite_width) {
    if (Visible ==
        cohen_sutherland_line_clip_i(&x0, &y0, &x1, &y1, 0, sx, 0, sy)) {
      wxPen psave = dc.GetPen();

      wxColour y = GetGlobalColor(_T ( "YELO1" ));
      wxColour hilt(y.Red(), y.Green(), y.Blue(), 128);

      wxPen HiPen(hilt, hilite_width, wxPENSTYLE_SOLID);

      dc.SetPen(HiPen);
      dc.StrokeLine(x0, y0, x1, y1);

      dc.SetPen(psave);
      dc.StrokeLine(x0, y0, x1, y1);
    }
  } else {
    if (Visible ==
        cohen_sutherland_line_clip_i(&x0, &y0, &x1, &y1, 0, sx, 0, sy))
      dc.StrokeLine(x0, y0, x1, y1);
  }

  if (bdraw_arrow) {
    //    Draw a direction arrow

    double theta = atan2((double)(yb - ya), (double)(xb - xa));
    theta -= PI / 2.;

    wxPoint icon[10];
    double icon_scale_factor = 100 * vp.view_scale_ppm;
    icon_scale_factor = fmin(icon_scale_factor, 1.5);  // Sets the max size
    icon_scale_factor = fmax(icon_scale_factor, .10);

    //    Get the absolute line length
    //    and constrain the arrow to be no more than xx% of the line length
    double nom_arrow_size = 20.;
    double max_arrow_to_leg = .20;
    double lpp = sqrt(pow((double)(xa - xb), 2) + pow((double)(ya - yb), 2));

    double icon_size = icon_scale_factor * nom_arrow_size;
    if (icon_size > (lpp * max_arrow_to_leg))
      icon_scale_factor = (lpp * max_arrow_to_leg) / nom_arrow_size;

    for (int i = 0; i < 7; i++) {
      int j = i * 2;
      double pxa = (double)(s_arrow_icon[j]);
      double pya = (double)(s_arrow_icon[j + 1]);

      pya *= icon_scale_factor;
      pxa *= icon_scale_factor;

      double px = (pxa * sin(theta)) + (pya * cos(theta));
      double py = (pya * sin(theta)) - (pxa * cos(theta));

      icon[i].x = (int)(px) + xb;
      icon[i].y = (int)(py) + yb;
    }
    wxPen savePen = dc.GetPen();
    dc.SetPen(*wxTRANSPARENT_PEN);
    dc.StrokePolygon(6, &icon[0], 0, 0);
    dc.SetPen(savePen);
  }
}

void Route::RenderSegmentArrowsGL(ocpnDC &dc, int xa, int ya, int xb, int yb,
                                  ViewPort &vp) {
#ifdef ocpnUSE_GL
  //    Draw a direction arrow
  float icon_scale_factor = 100 * vp.view_scale_ppm;
  icon_scale_factor = fmin(icon_scale_factor, 1.5);  // Sets the max size
  icon_scale_factor = fmax(icon_scale_factor, .10);

  //    Get the absolute line length
  //    and constrain the arrow to be no more than xx% of the line length
  float nom_arrow_size = 20.;
  float max_arrow_to_leg = (float).20;
  float lpp = sqrtf(powf((float)(xa - xb), 2) + powf((float)(ya - yb), 2));

  float icon_size = icon_scale_factor * nom_arrow_size;
  if (icon_size > (lpp * max_arrow_to_leg))
    icon_scale_factor = (lpp * max_arrow_to_leg) / nom_arrow_size;

  float theta = atan2f((float)yb - ya, (float)xb - xa);
  theta -= (float)PI;

#ifndef USE_ANDROID_GLES2
  glPushMatrix();
  glTranslatef(xb, yb, 0);
  glScalef(icon_scale_factor, icon_scale_factor, 1);
  glRotatef(theta * 180 / PI, 0, 0, 1);

  glBegin(GL_POLYGON);
  for (int i = 0; i < 14; i += 2)
    glVertex2f(s_arrow_icon[i], s_arrow_icon[i + 1]);
  glEnd();

  glPopMatrix();
#else
  // icon_scale_factor = 5;
  wxPoint pts[3];
  // 0
  pts[0].x = s_arrow_icon[0];
  pts[0].y = s_arrow_icon[1];
  pts[1].x = s_arrow_icon[2];
  pts[1].y = s_arrow_icon[3];
  pts[2].x = s_arrow_icon[6];
  pts[2].y = s_arrow_icon[7];
  dc.DrawPolygon(3, pts, xb, yb, icon_scale_factor, theta);

  // 1
  pts[0].x = s_arrow_icon[2];
  pts[0].y = s_arrow_icon[3];
  pts[1].x = s_arrow_icon[4];
  pts[1].y = s_arrow_icon[5];
  pts[2].x = s_arrow_icon[6];
  pts[2].y = s_arrow_icon[7];
  dc.DrawPolygon(3, pts, xb, yb, icon_scale_factor, theta);

  // 2
  pts[0].x = s_arrow_icon[0];
  pts[0].y = -s_arrow_icon[1];
  pts[1].x = s_arrow_icon[2];
  pts[1].y = -s_arrow_icon[3];
  pts[2].x = s_arrow_icon[6];
  pts[2].y = -s_arrow_icon[7];
  dc.DrawPolygon(3, pts, xb, yb, icon_scale_factor, theta);

  // 3
  pts[0].x = s_arrow_icon[2];
  pts[0].y = -s_arrow_icon[3];
  pts[1].x = s_arrow_icon[4];
  pts[1].y = -s_arrow_icon[5];
  pts[2].x = s_arrow_icon[6];
  pts[2].y = -s_arrow_icon[7];
  dc.DrawPolygon(3, pts, xb, yb, icon_scale_factor, theta);

#endif

#endif
}

void Route::ClearHighlights(void) {
  RoutePoint *prp = NULL;
  wxRoutePointListNode *node = pRoutePointList->GetFirst();

  while (node) {
    prp = node->GetData();
    if (prp) prp->m_bPtIsSelected = false;
    node = node->GetNext();
  }
}

RoutePoint *Route::InsertPointBefore(RoutePoint *pRP, double rlat, double rlon,
                                     bool bRenamePoints) {
  RoutePoint *newpoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
                                        GetNewMarkSequenced(), wxEmptyString);
  newpoint->m_bIsInRoute = true;
  newpoint->m_bDynamicName = true;
  newpoint->SetNameShown(false);

  int nRP = pRoutePointList->IndexOf(pRP);
  pRoutePointList->Insert(nRP, newpoint);

  if (bRenamePoints) RenameRoutePoints();

  FinalizeForRendering();
  UpdateSegmentDistances();

  return (newpoint);
}

RoutePoint *Route::InsertPointAfter(RoutePoint *pRP, double rlat, double rlon,
                                    bool bRenamePoints) {
  int nRP = pRoutePointList->IndexOf(pRP);
  if (nRP >= GetnPoints() - 1) return NULL;
  nRP++;

  RoutePoint *newpoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
                                        GetNewMarkSequenced(), wxEmptyString);
  newpoint->m_bIsInRoute = true;
  newpoint->m_bDynamicName = true;
  newpoint->SetNameShown(false);

  pRoutePointList->Insert(nRP, newpoint);

  if (bRenamePoints) RenameRoutePoints();

  FinalizeForRendering();
  UpdateSegmentDistances();

  return (newpoint);
}

wxString Route::GetNewMarkSequenced(void) {
  wxString ret;
  ret.Printf(_T ( "NM%03d" ), m_nm_sequence);
  m_nm_sequence++;

  return ret;
}

RoutePoint *Route::GetLastPoint() {
  if (pRoutePointList->IsEmpty()) return NULL;

  return pRoutePointList->GetLast()->GetData();
}

int Route::GetIndexOf(RoutePoint *prp) {
  int ret = pRoutePointList->IndexOf(prp) + 1;
  if (ret == wxNOT_FOUND)
    return 0;
  else
    return ret;
}

void Route::DeletePoint(RoutePoint *rp, bool bRenamePoints) {
  //    n.b. must delete Selectables  and update config before deleting the
  //    point
  if (rp->m_bIsInLayer) return;

  pSelect->DeleteAllSelectableRoutePoints(this);
  pSelect->DeleteAllSelectableRouteSegments(this);
  pConfig->DeleteWayPoint(rp);

  pRoutePointList->DeleteObject(rp);

  delete rp;

  if (bRenamePoints) RenameRoutePoints();

  if (GetnPoints() > 1) {
    pSelect->AddAllSelectableRouteSegments(this);
    pSelect->AddAllSelectableRoutePoints(this);

    pConfig->UpdateRoute(this);

    FinalizeForRendering();
    UpdateSegmentDistances();
  }
}

void Route::RemovePoint(RoutePoint *rp, bool bRenamePoints) {
  if (rp->m_bIsActive && this->IsActive())  // FS#348
    g_pRouteMan->DeactivateRoute();

  pSelect->DeleteAllSelectableRoutePoints(this);
  pSelect->DeleteAllSelectableRouteSegments(this);

  pRoutePointList->DeleteObject(rp);

  // check all other routes to see if this point appears in any other route
  Route *pcontainer_route = g_pRouteMan->FindRouteContainingWaypoint(rp);

  if (pcontainer_route == NULL) {
    rp->m_bIsInRoute = false;  // Take this point out of this (and only) route
    rp->m_bDynamicName = false;
    rp->m_bIsolatedMark = true;  // This has become an isolated mark
  }

  if (bRenamePoints) RenameRoutePoints();

  //      if ( m_nPoints > 1 )
  {
    pSelect->AddAllSelectableRouteSegments(this);
    pSelect->AddAllSelectableRoutePoints(this);

    pConfig->UpdateRoute(this);

    FinalizeForRendering();
    UpdateSegmentDistances();
  }
}

void Route::DeSelectRoute() {
  wxRoutePointListNode *node = pRoutePointList->GetFirst();

  RoutePoint *rp;
  while (node) {
    rp = node->GetData();
    rp->m_bPtIsSelected = false;

    node = node->GetNext();
  }
}

void Route::ReloadRoutePointIcons() {
  wxRoutePointListNode *node = pRoutePointList->GetFirst();

  RoutePoint *rp;
  while (node) {
    rp = node->GetData();
    rp->ReLoadIcon();

    node = node->GetNext();
  }
}

void Route::FinalizeForRendering() { RBBox.Invalidate(); }

LLBBox &Route::GetBBox(void) {
  if (RBBox.GetValid()) return RBBox;

  double bbox_lonmin, bbox_lonmax, bbox_latmin, bbox_latmax;

  wxRoutePointListNode *node = pRoutePointList->GetFirst();
  RoutePoint *data = node->GetData();

  if (data->m_wpBBox.GetValid()) {
    bbox_lonmax = data->m_wpBBox.GetMaxLon();
    bbox_lonmin = data->m_wpBBox.GetMinLon();
    bbox_latmax = data->m_wpBBox.GetMaxLat();
    bbox_latmin = data->m_wpBBox.GetMinLat();
  } else {
    bbox_lonmax = bbox_lonmin = data->m_lon;
    bbox_latmax = bbox_latmin = data->m_lat;
  }

  double lastlon = data->m_lon, wrap = 0;

  node = node->GetNext();
  while (node) {
    data = node->GetData();

    if (lastlon - data->m_lon > 180)
      wrap += 360;
    else if (data->m_lon - lastlon > 180)
      wrap -= 360;

    double lon = data->m_lon + wrap;

    if (lon > bbox_lonmax) bbox_lonmax = lon;
    if (lon < bbox_lonmin) bbox_lonmin = lon;

    if (data->m_lat > bbox_latmax) bbox_latmax = data->m_lat;
    if (data->m_lat < bbox_latmin) bbox_latmin = data->m_lat;

    lastlon = data->m_lon;
    node = node->GetNext();
  }

  if (bbox_lonmin < -360)
    bbox_lonmin += 360, bbox_lonmax += 360;
  else if (bbox_lonmax > 360)
    bbox_lonmin -= 360, bbox_lonmax -= 360;

  if (bbox_lonmax - bbox_lonmin > 360) bbox_lonmin = -180, bbox_lonmax = 180;

  RBBox.Set(bbox_latmin, bbox_lonmin, bbox_latmax, bbox_lonmax);

  return RBBox;
}

void Route::CalculateDCRect(wxDC &dc_route, ChartCanvas *canvas,
                            wxRect *prect) {
  dc_route.ResetBoundingBox();
  dc_route.DestroyClippingRegion();

  wxRect update_rect;

  // Draw the route in skeleton form on the dc
  // That is, draw only the route points, assuming that the segements will
  // always be fully contained within the resulting rectangle.
  // Can we prove this?
  if (m_bVisible) {
    wxRoutePointListNode *node = pRoutePointList->GetFirst();
    while (node) {
      RoutePoint *prp2 = node->GetData();
      bool blink_save = prp2->m_bBlink;
      prp2->m_bBlink = false;
      ocpnDC odc_route(dc_route);
      prp2->Draw(odc_route, canvas, NULL);
      prp2->m_bBlink = blink_save;

      wxRect r = prp2->CurrentRect_in_DC;
      r.Inflate(
          m_hiliteWidth,
          m_hiliteWidth);  // allow for large hilite circles at segment ends

      update_rect.Union(r);
      node = node->GetNext();
    }
  }

  *prect = update_rect;
}

/*
 Update a single route segment lengths
 Also, compute total route length by summing segment distances.
 */
void Route::UpdateSegmentDistance(RoutePoint *prp0, RoutePoint *prp,
                                  double planspeed) {
  double slat1 = prp0->m_lat, slon1 = prp0->m_lon;
  double slat2 = prp->m_lat, slon2 = prp->m_lon;

  //    Calculate the absolute distance from 1->2

  double dd;
  double br;
  // why are we using mercator rather than great circle here?? [sean 8-11-2015]
  DistanceBearingMercator(slat2, slon2, slat1, slon1, &br, &dd);

  prp->SetCourse(br);
  prp->SetDistance(dd);

  //    And store in Point 2
  prp->m_seg_len = dd;

  m_route_length += dd;

  //    If Point1 Description contains VMG, store it for Properties Dialog in
  //    Point2 If Point1 Description contains ETD, store it in Point1

  if (planspeed > 0.) {
    wxDateTime etd;

    double legspeed = planspeed;
    if (prp->GetPlannedSpeed() > 0.1 && prp->GetPlannedSpeed() < 1000.)
      legspeed = prp->GetPlannedSpeed();
    if (legspeed > 0.1 && legspeed < 1000.) {
      m_route_time += 3600. * dd / legspeed;
      prp->m_seg_vmg = legspeed;
    }
    wxLongLong duration = wxLongLong(3600.0 * prp->m_seg_len / prp->m_seg_vmg);
    prp->SetETE(duration);
    wxTimeSpan ts(0, 0, duration);
    if (!prp0->GetManualETD().IsValid()) {
      prp0->m_manual_etd = false;
      if (prp0->GetETA().IsValid()) {
        prp0->m_seg_etd = prp0->GetETA();
      } else {
        prp0->m_seg_etd =
            m_PlannedDeparture + wxTimeSpan(0, 0, m_route_time - duration);
      }
    }
    prp->m_seg_eta = prp0->GetETD() + ts;
    if (!prp->m_manual_etd || !prp->GetETD().IsValid()) {
      prp->m_seg_etd = prp->m_seg_eta;
      prp->m_manual_etd = false;
    }
  }
}

/*
 Update the route segment lengths, storing each segment length in <destination>
 point. Also, compute total route length by summing segment distances.
 */
void Route::UpdateSegmentDistances(double planspeed) {
  wxPoint rpt, rptn;

  m_route_length = 0.0;
  m_route_time = 0.0;

  wxRoutePointListNode *node = pRoutePointList->GetFirst();

  if (node) {
    RoutePoint *prp0 = node->GetData();
    if (!prp0->m_manual_etd) {
      prp0->m_seg_etd = m_PlannedDeparture;
    }
    node = node->GetNext();

    while (node) {
      RoutePoint *prp = node->GetData();
      UpdateSegmentDistance(prp0, prp, planspeed);

      prp0 = prp;

      node = node->GetNext();
    }
  }
}

void Route::Reverse(bool bRenamePoints) {
  //    Reverse the GUID list
  wxArrayString RoutePointGUIDList;

  int ncount = pRoutePointList->GetCount();
  for (int i = 0; i < ncount; i++)
    RoutePointGUIDList.Add(GetPoint(ncount - i)->m_GUID);

  pRoutePointList->DeleteContents(false);
  pRoutePointList->Clear();
  m_route_length = 0.0;

  //    iterate over the RoutePointGUIDs
  for (unsigned int ip = 0; ip < RoutePointGUIDList.GetCount(); ip++) {
    wxString GUID = RoutePointGUIDList[ip];

    //    And on the RoutePoints themselves
    wxRoutePointListNode *prpnode = pWayPointMan->GetWaypointList()->GetFirst();
    while (prpnode) {
      RoutePoint *prp = prpnode->GetData();

      if (prp->m_GUID == GUID) {
        AddPoint(prp);
        break;
      }
      prpnode = prpnode->GetNext();  // RoutePoint
    }
  }

  if (bRenamePoints) RenameRoutePoints();

  // Switch start/end strings. anders, 2010-01-29
  wxString tmp = m_RouteStartString;
  m_RouteStartString = m_RouteEndString;
  m_RouteEndString = tmp;
}

void Route::SetVisible(bool visible, bool includeWpts) {
  m_bVisible = visible;

  if (!includeWpts) return;

  wxRoutePointListNode *node = pRoutePointList->GetFirst();
  RoutePoint *rp;
  while (node) {
    rp = node->GetData();

    // if this is a "shared" point, then do not turn off visibility.
    // This step keeps the point available for selection to other routes,
    // or may be manaully hidden in route-manager dialog.
    if (rp->IsShared()) {
      if (visible) rp->SetVisible(visible);
    }
    node = node->GetNext();
  }
}

void Route::SetListed(bool visible) { m_bListed = visible; }

void Route::AssembleRoute(void) {}

void Route::ShowWaypointNames(bool bshow) {
  wxRoutePointListNode *node = pRoutePointList->GetFirst();

  while (node) {
    RoutePoint *prp = node->GetData();
    prp->SetNameShown(bshow);

    node = node->GetNext();
  }
}

bool Route::AreWaypointNamesVisible() {
  bool bvis = false;
  wxRoutePointListNode *node = pRoutePointList->GetFirst();

  while (node) {
    RoutePoint *prp = node->GetData();
    if (prp->GetNameShown()) bvis = true;

    node = node->GetNext();
  }

  return bvis;
}

void Route::RenameRoutePoints(void) {
  //    iterate on the route points.
  //    If dynamically named, rename according to current list position

  wxRoutePointListNode *node = pRoutePointList->GetFirst();

  int i = 1;
  while (node) {
    RoutePoint *prp = node->GetData();
    if (prp->m_bDynamicName) {
      wxString name;
      name.Printf(_T ( "%03d" ), i);
      prp->SetName(name);
    }

    node = node->GetNext();
    i++;
  }
}

int Route::SendToGPS(const wxString &com_name, bool bsend_waypoints,
                     SendToGpsDlg *dialog) {
  int result = 0;

  if (g_pMUX) {
    ::wxBeginBusyCursor();
    result = g_pMUX->SendRouteToGPS(this, com_name, bsend_waypoints, dialog);
    ::wxEndBusyCursor();
  }

  wxString msg;
  if (0 == result)
    msg = _("Route Transmitted.");
  else {
    if (result == ERR_GARMIN_INITIALIZE)
      msg = _("Error on Route Upload.  Garmin GPS not connected");
    else
      msg = _("Error on Route Upload.  Please check logfiles...");

    OCPNMessageBox(NULL, msg, _("OpenCPN Info"), wxOK | wxICON_INFORMATION);
  }

  return (result == 0);
}

//    Is this route equal to another, meaning,
//    Do all routepoint positions and names match?
bool Route::IsEqualTo(Route *ptargetroute) {
  wxRoutePointListNode *pthisnode = (this->pRoutePointList)->GetFirst();
  wxRoutePointListNode *pthatnode = (ptargetroute->pRoutePointList)->GetFirst();

  if (NULL == pthisnode) return false;

  if (this->m_bIsInLayer || ptargetroute->m_bIsInLayer) return false;

  if (this->GetnPoints() != ptargetroute->GetnPoints()) return false;

  while (pthisnode) {
    if (NULL == pthatnode) return false;

    RoutePoint *pthisrp = pthisnode->GetData();
    RoutePoint *pthatrp = pthatnode->GetData();

    if ((fabs(pthisrp->m_lat - pthatrp->m_lat) > 1.0e-6) ||
        (fabs(pthisrp->m_lon - pthatrp->m_lon) > 1.0e-6))
      return false;

    if (!pthisrp->GetName().IsSameAs(pthatrp->GetName())) return false;

    pthisnode = pthisnode->GetNext();
    pthatnode = pthatnode->GetNext();
  }

  return true;  // success, they are the same
}
