/******************************************************************************
 *
 * Project:  OpenCPN
 * Purpose:  GRIB Object
 * Author:   David Register
 *
 ***************************************************************************
 *   Copyright (C) 2010 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 "pi_gl.h"
#include "grib_pi.h"

#include "folder.xpm"

extern int m_Altitude;
extern double m_cursor_lat, m_cursor_lon;
extern int m_DialogStyle;

enum SettingsDisplay {
  B_ARROWS,
  ISO_LINE,
  ISO_ABBR,
  D_ARROWS,
  OVERLAY,
  NUMBERS,
  PARTICLES
};

//---------------------------------------------------------------------------------------
//               GRIB Cursor Data  implementation
//---------------------------------------------------------------------------------------
CursorData::CursorData(wxWindow *window, GRIBUICtrlBar &parent)
    : CursorDataBase(window), m_gparent(parent) {
  // transform checkboxes ID to have a formal link to data type and set the
  // initial value
  wxWindowListNode *node = this->GetChildren().GetFirst();
  while (node) {
    wxWindow *win = node->GetData();
    if (win->IsKindOf(CLASSINFO(wxCheckBox))) {
      int winId = ((wxCheckBox *)win)->GetId() - ID_CB_WIND;
      if (m_gparent.InDataPlot(winId)) {
        ((wxCheckBox *)win)->SetId(winId);
        ((wxCheckBox *)win)->SetValue(m_gparent.m_bDataPlot[winId]);
      }
    }
    node = node->GetNext();
  }

  m_bLeftDown = false;

  m_tCursorTrackTimer.Connect(
      wxEVT_TIMER, wxTimerEventHandler(CursorData::OnCursorTrackTimer), NULL,
      this);

  DimeWindow(this);
}

void CursorData::OnCBAny(wxCommandEvent &event) {
  int id = event.GetId();
  wxWindow *win = this->FindWindow(id);
  if (m_gparent.InDataPlot(id))
    m_gparent.m_bDataPlot[id] = ((wxCheckBox *)win)->IsChecked();
  ResolveDisplayConflicts(id);
}

void CursorData::ResolveDisplayConflicts(int Id) {
  // allow multi selection only if there is no display type superposition
  for (int i = 0; i < GribOverlaySettings::GEO_ALTITUDE; i++) {
    if (i != Id && m_gparent.m_bDataPlot[i]) {
      if ((m_gparent.m_OverlaySettings.Settings[Id].m_bBarbedArrows &&
           m_gparent.m_OverlaySettings.Settings[i].m_bBarbedArrows) ||
          (m_gparent.m_OverlaySettings.Settings[Id].m_bDirectionArrows &&
           m_gparent.m_OverlaySettings.Settings[i].m_bDirectionArrows) ||
          (m_gparent.m_OverlaySettings.Settings[Id].m_bIsoBars &&
           m_gparent.m_OverlaySettings.Settings[i].m_bIsoBars) ||
          (m_gparent.m_OverlaySettings.Settings[Id].m_bNumbers &&
           m_gparent.m_OverlaySettings.Settings[i].m_bNumbers) ||
          (m_gparent.m_OverlaySettings.Settings[Id].m_bOverlayMap &&
           m_gparent.m_OverlaySettings.Settings[i].m_bOverlayMap) ||
          (m_gparent.m_OverlaySettings.Settings[Id].m_bParticles &&
           m_gparent.m_OverlaySettings.Settings[i].m_bParticles)) {
        m_gparent.m_bDataPlot[i] = false;
        wxWindow *win = FindWindow(i);
        ((wxCheckBox *)win)->SetValue(false);
      }
    }
  }
  m_gparent.SetFactoryOptions();  // Reload the visibility options
}

void CursorData::AddTrackingControl(wxControl *ctrl1, wxControl *ctrl2,
                                    wxControl *ctrl3, wxControl *ctrl4,
                                    bool show, bool vertical, int wctrl2,
                                    int wctrl3_4) {
  if (show) {
    m_fgTrackingControls->Add(ctrl1, 0, wxALL, 1);
    ctrl1->Show();
    if (ctrl2) {
      m_fgTrackingControls->Add(ctrl2, 0, wxALL, 0);
      ctrl2->SetMinSize(wxSize(wctrl2, -1));
      ctrl2->Show();
    } else
      m_fgTrackingControls->Add(0, 0, 1, wxALL, 1); /* spacer */

    if (ctrl3) {
      long flag1 = wxALIGN_CENTER;
      long flag = vertical ? flag1 : wxALL;
      m_fgTrackingControls->Add(ctrl3, 0, flag, 0);
      ctrl3->SetMinSize(wxSize(wctrl3_4, -1));
      ctrl3->Show();
    } else if (!vertical)
      m_fgTrackingControls->Add(0, 0, 1, wxALL, 1); /* spacer */

    if (ctrl4) {
      m_fgTrackingControls->Add(ctrl4, 0, wxALL, 0);
      ctrl4->SetMinSize(wxSize(vertical ? wctrl2 : wctrl3_4, -1));
      ctrl4->Show();
    } else if (!vertical)
      m_fgTrackingControls->Add(0, 0, 1, wxALL, 1); /* spacer */

  } else {
    if (ctrl1) ctrl1->Hide();
    if (ctrl2) ctrl2->Hide();
    if (ctrl3) ctrl3->Hide();
    if (ctrl4) ctrl4->Hide();
  }
}

void CursorData::PopulateTrackingControls(bool vertical) {
  m_fgTrackingControls->Clear();
  if (!vertical) {
    wxFlexGridSizer *ps = (wxFlexGridSizer *)(m_gparent.GetSizer());
    if (ps && (ps->GetCols() == 1))
      m_fgTrackingControls->SetCols(4);  // compact mode
    else
      m_fgTrackingControls->SetCols(12);
  } else
    m_fgTrackingControls->SetCols(2);

  this->Fit();
  // Get text controls sizing data
  wxFont *font = OCPNGetFont(_("Dialog"), 10);
  int wn, wd, ws, wl;
  GetTextExtent(_T("abcdefghihjk"), &wn, NULL, 0, 0,
                font);  // normal width text control size
  GetTextExtent(_T("abcdef"), &ws, NULL, 0, 0,
                font);  // short width text control size for direction only
  GetTextExtent(
      _T("abcdefghijklmopq"), &wd, NULL, 0, 0,
      font);  // long width text control size for double unit wind display
  GetTextExtent(
      _T("abcdefghijklm"), &wl, NULL, 0, 0,
      font);  // long width text control size for double unit wave display
  //
  // create a dummy textCtrl to be used as a "space" in vertical display
  wxTextCtrl *dummy =
      new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition,
                     wxDefaultSize, wxTE_READONLY | wxNO_BORDER);
  //
  bool bf =
      m_gparent.m_OverlaySettings.Settings[GribOverlaySettings::WIND].m_Units ==
      GribOverlaySettings::BFS;
  wd = vertical ? wn : bf ? wn : wd;
  wl = vertical ? wn : wl;

  AddTrackingControl(m_cbWind, m_tcWindSpeed, m_tcWindSpeedBf,
                     m_tcWindDirection, false, vertical, 0,
                     0);  // hide all wind's parameters
  AddTrackingControl(
      m_cbWind, m_tcWindSpeed,
      vertical ? (bf ? dummy : m_tcWindSpeedBf) : m_tcWindDirection,
      vertical ? m_tcWindDirection : 0,
      m_gparent.m_pTimelineSet &&
          m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_WIND_VX) !=
              wxNOT_FOUND &&
          m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_WIND_VY) !=
              wxNOT_FOUND,
      vertical, wd, ws);
  AddTrackingControl(m_cbWindGust, m_tcWindGust, 0, 0,
                     m_gparent.m_pTimelineSet &&
                         m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(
                             Idx_WIND_GUST) != wxNOT_FOUND &&
                         m_Altitude == 0,
                     vertical, wn);
  AddTrackingControl(m_cbPressure, m_tcPressure, 0, 0,
                     m_gparent.m_pTimelineSet &&
                         m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(
                             Idx_PRESSURE) != wxNOT_FOUND &&
                         m_Altitude == 0,
                     vertical, wn);

  /* tracking for wave is funky */
  AddTrackingControl(m_cbWave, m_tcWaveHeight, m_tcWavePeriode,
                     m_tcWaveDirection, false, vertical, 0,
                     0);  // hide all waves's parameters
  if (m_gparent.m_pTimelineSet &&
      m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_HTSIGW) !=
          wxNOT_FOUND) {
    if (m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_WVDIR) !=
        wxNOT_FOUND)
      AddTrackingControl(m_cbWave, m_tcWaveHeight,
                         vertical ? m_tcWavePeriode : m_tcWaveDirection,
                         vertical ? m_tcWaveDirection : 0, m_Altitude == 0,
                         vertical, wl, ws);
    else
      AddTrackingControl(m_cbWave, m_tcWaveHeight, 0, 0, m_Altitude == 0,
                         vertical, wn);
  } else {
    if (m_gparent.m_pTimelineSet &&
        m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_WVDIR) !=
            wxNOT_FOUND)
      AddTrackingControl(m_cbWave, m_tcWaveDirection, 0, 0, m_Altitude == 0,
                         vertical, wn);
  }

  AddTrackingControl(m_cbCurrent, m_tcCurrentVelocity, m_tcCurrentDirection, 0,
                     false, vertical, 0, 0);  // hide all current's parameters
  AddTrackingControl(m_cbCurrent, m_tcCurrentVelocity,
                     vertical ? dummy : m_tcCurrentDirection,
                     vertical ? m_tcCurrentDirection : 0,
                     m_gparent.m_pTimelineSet &&
                         m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(
                             Idx_SEACURRENT_VX) != wxNOT_FOUND &&
                         m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(
                             Idx_SEACURRENT_VY) != wxNOT_FOUND &&
                         m_Altitude == 0,
                     vertical, wn, ws);
  AddTrackingControl(m_cbPrecipitation, m_tcPrecipitation, 0, 0,
                     m_gparent.m_pTimelineSet &&
                         m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(
                             Idx_PRECIP_TOT) != wxNOT_FOUND &&
                         m_Altitude == 0,
                     vertical, wn);
  AddTrackingControl(m_cbCloud, m_tcCloud, 0, 0,
                     m_gparent.m_pTimelineSet &&
                         m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(
                             Idx_CLOUD_TOT) != wxNOT_FOUND &&
                         m_Altitude == 0,
                     vertical, wn);
  AddTrackingControl(m_cbAirTemperature, m_tcAirTemperature, 0, 0,
                     m_gparent.m_pTimelineSet &&
                         m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(
                             Idx_AIR_TEMP) != wxNOT_FOUND &&
                         m_Altitude == 0,
                     vertical, wn);
  AddTrackingControl(m_cbSeaTemperature, m_tcSeaTemperature, 0, 0,
                     m_gparent.m_pTimelineSet &&
                         m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(
                             Idx_SEA_TEMP) != wxNOT_FOUND &&
                         m_Altitude == 0,
                     vertical, wn);
  AddTrackingControl(m_cbCAPE, m_tcCAPE, 0, 0,
                     m_gparent.m_pTimelineSet &&
                         m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(
                             Idx_CAPE) != wxNOT_FOUND &&
                         m_Altitude == 0,
                     vertical, wn);
  AddTrackingControl(m_cbReflC, m_tcReflC, 0, 0,
                     m_gparent.m_pTimelineSet &&
                         m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(
                             Idx_COMP_REFL) != wxNOT_FOUND &&
                         m_Altitude == 0,
                     vertical, wn);
  //
  // init and show extra parameters for altitude tracking if necessary
  AddTrackingControl(m_cbAltitude, m_tcAltitude, 0, 0,
                     m_gparent.m_pTimelineSet &&
                         m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(
                             Idx_GEOP_HGT + m_Altitude) != wxNOT_FOUND &&
                         m_Altitude != 0,
                     vertical, wn);
  AddTrackingControl(m_cbTemp, m_tcTemp, 0, 0,
                     m_gparent.m_pTimelineSet &&
                         m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(
                             Idx_AIR_TEMP + m_Altitude) != wxNOT_FOUND &&
                         m_Altitude != 0,
                     vertical, wn);
  AddTrackingControl(m_cbRelHumid, m_tcRelHumid, 0, 0,
                     m_gparent.m_pTimelineSet &&
                         m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(
                             Idx_HUMID_RE + m_Altitude) != wxNOT_FOUND &&
                         m_Altitude != 0,
                     vertical, wn);
  //
  m_stTrackingText->SetLabel(_("Data at cursor position"));
  // add tooltips
  wxString t;
  double lev;

  lev = m_gparent.m_OverlaySettings.CalibrateValue(
      GribOverlaySettings::GEO_ALTITUDE,
      10);  // convert 10m in current altitude unit
  t.Printf(
      m_Altitude
          ? m_gparent.m_OverlaySettings
                .GetAltitudeFromIndex(
                    m_Altitude, m_gparent.m_OverlaySettings
                                    .Settings[GribOverlaySettings::PRESSURE]
                                    .m_Units)
                .Append(_T(" "))
                .Append(m_gparent.m_OverlaySettings.GetUnitSymbol(
                    GribOverlaySettings::PRESSURE))
          : wxString::Format(_T("%1.*f "), lev == (int)lev ? 0 : 1, lev)
                .Append(m_gparent.m_OverlaySettings.GetUnitSymbol(
                    GribOverlaySettings::GEO_ALTITUDE)));
  wxString pre = _T(" ");
  if (m_Altitude) {
    pre.Append(_("at Geopotential Height"));
    pre.Append(_T(" "));
    m_tcAltitude->SetToolTip(_("Altitude") + t);
    m_tcTemp->SetToolTip(_("Temperature") + t);
    m_tcRelHumid->SetToolTip(_("Relative Humidity") + t);
  } else {
    pre.Append(_("at"));
    pre.Append(_T(" "));
  }
  t.Prepend(pre);

  m_tcWindSpeed->SetToolTip(_("Wind Speed") + t);
  m_tcWindSpeedBf->SetToolTip(_("Wind Speed in Bf") + t);
  m_tcWindDirection->SetToolTip(_("Wind Direction") + t);

  t.Printf(_T(" %1.*f ") + m_gparent.m_OverlaySettings.GetUnitSymbol(
                               GribOverlaySettings::GEO_ALTITUDE),
           lev == (int)lev ? 0 : 1, lev);
  m_tcWindGust->SetToolTip(_("Wind Gust at") + t);

  if (m_gparent.m_pTimelineSet) {
    wxString s[] = {_T(" "), _("Air Temperature at"), _("surface level"),
                    _("Sea Surface Temperature")};

    lev = m_gparent.m_OverlaySettings.CalibrateValue(
        GribOverlaySettings::GEO_ALTITUDE,
        2);  // convert 2m in current altitude unit
    t.Printf(m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(
                 1000 + NORWAY_METNO) != wxNOT_FOUND
                 ? s[0] + s[2]
                 : _T(" %1.*f ") + m_gparent.m_OverlaySettings.GetUnitSymbol(
                                       GribOverlaySettings::GEO_ALTITUDE),
             lev == (int)lev ? 0 : 1, lev);
    m_tcAirTemperature->SetToolTip(s[1] + t);

    m_tcSeaTemperature->SetToolTip(
        m_gparent.m_bGRIBActiveFile->m_GribIdxArray.Index(1000 + NOAA_GFS) !=
                wxNOT_FOUND
            ? s[1] + s[0] + s[2]
            : s[3]);
  }
  dummy->Show(false);
}

void CursorData::UpdateTrackingControls(void) {
  if (!m_gparent.m_pTimelineSet) return;

  GribRecord **RecordArray = m_gparent.m_pTimelineSet->m_GribRecordPtrArray;
  //    Update the wind control
  double vkn, ang;
  if (GribRecord::getInterpolatedValues(
          vkn, ang, RecordArray[Idx_WIND_VX + m_Altitude],
          RecordArray[Idx_WIND_VY + m_Altitude], m_cursor_lon, m_cursor_lat)) {
    double vk = m_gparent.m_OverlaySettings.CalibrateValue(
        GribOverlaySettings::WIND, vkn);

    m_tcWindSpeed->SetValue(
        wxString::Format(_T("%3d ") + m_gparent.m_OverlaySettings.GetUnitSymbol(
                                          GribOverlaySettings::WIND),
                         (int)round(vk)));

    // wind is a special case: if current unit is not bf ==> double speed
    // display (current unit + bf)
    if (m_gparent.m_OverlaySettings.Settings[GribOverlaySettings::WIND]
            .m_Units != GribOverlaySettings::BFS) {
      vk = m_gparent.m_OverlaySettings.GetmstobfFactor(vkn) * vkn;
      if (m_DialogStyle == SEPARATED_VERTICAL)
        m_tcWindSpeedBf->SetValue(
            wxString::Format(_T("%2d bf"), (int)round(vk)));
      else
        m_tcWindSpeed->SetValue(
            m_tcWindSpeed->GetValue().Append(_T(" - ")).Append(
                wxString::Format(_T("%2d bf"), (int)round(vk))));
    }

    m_tcWindDirection->SetValue(wxString::Format(_T("%03d%c"), (int)(ang), 0x00B0));
  } else {
    m_tcWindSpeed->SetValue(_("N/A"));
    m_tcWindSpeedBf->SetValue(_("N/A"));
    m_tcWindDirection->SetValue(_("N/A"));
  }

  //    Update the Wind gusts control
  if (RecordArray[Idx_WIND_GUST]) {
    double vkn = RecordArray[Idx_WIND_GUST]->getInterpolatedValue(
        m_cursor_lon, m_cursor_lat, true);

    if (vkn != GRIB_NOTDEF) {
      vkn = m_gparent.m_OverlaySettings.CalibrateValue(
          GribOverlaySettings::WIND_GUST, vkn);
      m_tcWindGust->SetValue(wxString::Format(
          _T("%2d ") + m_gparent.m_OverlaySettings.GetUnitSymbol(
                           GribOverlaySettings::WIND_GUST),
          (int)round(vkn)));
    } else
      m_tcWindGust->SetValue(_("N/A"));
  }

  //    Update the Pressure control
  if (RecordArray[Idx_PRESSURE]) {
    double press = RecordArray[Idx_PRESSURE]->getInterpolatedValue(
        m_cursor_lon, m_cursor_lat, true);

    if (press != GRIB_NOTDEF) {
      press = m_gparent.m_OverlaySettings.CalibrateValue(
          GribOverlaySettings::PRESSURE, press);
      int p =
          (m_gparent.m_OverlaySettings.Settings[GribOverlaySettings::PRESSURE]
               .m_Units == 2)
              ? 2
              : 1;  // if PRESSURE & inHG = two decimals
      m_tcPressure->SetValue(wxString::Format(
          _T("%2.*f ") + m_gparent.m_OverlaySettings.GetUnitSymbol(
                             GribOverlaySettings::PRESSURE),
          p, (press)));
    } else
      m_tcPressure->SetValue(_("N/A"));
  }

  //    Update the Sig Wave Height
  if (RecordArray[Idx_HTSIGW]) {
    double height = RecordArray[Idx_HTSIGW]->getInterpolatedValue(
        m_cursor_lon, m_cursor_lat, true);

    if (height != GRIB_NOTDEF) {
      height = m_gparent.m_OverlaySettings.CalibrateValue(
          GribOverlaySettings::WAVE, height);
      wxString w(wxString::Format(
          _T("%4.1f ") + m_gparent.m_OverlaySettings.GetUnitSymbol(
                             GribOverlaySettings::WAVE),
          height));
      if (RecordArray[Idx_WVPER]) {
        double period = RecordArray[Idx_WVPER]->getInterpolatedValue(
            m_cursor_lon, m_cursor_lat, true);
        if (period != GRIB_NOTDEF) {
          if (m_DialogStyle == SEPARATED_VERTICAL)
            m_tcWavePeriode->SetValue(
                wxString::Format(_T("%01ds"), (int)round(period)));
          else
            w.Append(wxString::Format(_T(" - %01ds"), (int)round(period)));
        } else
          m_tcWavePeriode->SetValue(_("N/A"));
      } else
        m_tcWavePeriode->SetValue(_("N/A"));

      m_tcWaveHeight->SetValue(w);
    } else
      m_tcWaveHeight->SetValue(_("N/A"));
  }

  // Update the Wave direction
  if (RecordArray[Idx_WVDIR]) {
    double direction = RecordArray[Idx_WVDIR]->getInterpolatedValue(
        m_cursor_lon, m_cursor_lat, true, true);
    if (direction != GRIB_NOTDEF)
      m_tcWaveDirection->SetValue(
          wxString::Format(_T("%03d%c"), (int)direction, 0x00B0));
    else
      m_tcWaveDirection->SetValue(_("N/A"));
  }

  //    Update the Current control
  if (GribRecord::getInterpolatedValues(
          vkn, ang, RecordArray[Idx_SEACURRENT_VX],
          RecordArray[Idx_SEACURRENT_VY], m_cursor_lon, m_cursor_lat)) {
    // Current direction is generally reported as the "flow" direction,
    // which is opposite from wind convention.
    // So, adjust.
    ang += 180;
    if (ang >= 360) ang -= 360;
    if (ang < 0) ang += 360;

    vkn = m_gparent.m_OverlaySettings.CalibrateValue(
        GribOverlaySettings::CURRENT, vkn);

    m_tcCurrentVelocity->SetValue(wxString::Format(
        _T("%4.1f ") + m_gparent.m_OverlaySettings.GetUnitSymbol(
                           GribOverlaySettings::CURRENT),
        vkn));

    m_tcCurrentDirection->SetValue(
        wxString::Format(_T("%03d%c"), (int)(ang), 0x00B0));
  } else {
    m_tcCurrentVelocity->SetValue(_("N/A"));
    m_tcCurrentDirection->SetValue(_("N/A"));
  }

  //    Update total rainfall control
  if (RecordArray[Idx_PRECIP_TOT]) {
    double precip = RecordArray[Idx_PRECIP_TOT]->getInterpolatedValue(
        m_cursor_lon, m_cursor_lat, true);

    if (precip != GRIB_NOTDEF) {
      precip = m_gparent.m_OverlaySettings.CalibrateValue(
          GribOverlaySettings::PRECIPITATION, precip);
      int p = precip < 10. ? 2 : precip < 100. ? 1 : 0;
      p += m_gparent.m_OverlaySettings
                       .Settings[GribOverlaySettings::PRECIPITATION]
                       .m_Units == 1
               ? 1
               : 0;  // if PRESSURE & in = one decimal more
      m_tcPrecipitation->SetValue(wxString::Format(
          _T("%4.*f ") + m_gparent.m_OverlaySettings.GetUnitSymbol(
                             GribOverlaySettings::PRECIPITATION),
          p, precip));
    } else
      m_tcPrecipitation->SetValue(_("N/A"));
  }

  //    Update total cloud control
  if (RecordArray[Idx_CLOUD_TOT]) {
    double cloud = RecordArray[Idx_CLOUD_TOT]->getInterpolatedValue(
        m_cursor_lon, m_cursor_lat, true);

    if (cloud != GRIB_NOTDEF) {
      cloud = m_gparent.m_OverlaySettings.CalibrateValue(
          GribOverlaySettings::CLOUD, cloud);
      wxString val(wxString::Format(_T("%5.0f "), cloud));
      m_tcCloud->SetValue(val + m_gparent.m_OverlaySettings.GetUnitSymbol(
                                    GribOverlaySettings::CLOUD));
    } else
      m_tcCloud->SetValue(_("N/A"));
  }

  //    Update the Air Temperature
  if (RecordArray[Idx_AIR_TEMP]) {
    double temp = RecordArray[Idx_AIR_TEMP]->getInterpolatedValue(
        m_cursor_lon, m_cursor_lat, true);

    if (temp != GRIB_NOTDEF) {
      temp = m_gparent.m_OverlaySettings.CalibrateValue(
          GribOverlaySettings::AIR_TEMPERATURE, temp);
      m_tcAirTemperature->SetValue(wxString::Format(
          _T("%5.1f ") + m_gparent.m_OverlaySettings.GetUnitSymbol(
                             GribOverlaySettings::AIR_TEMPERATURE),
          temp));
    } else
      m_tcAirTemperature->SetValue(_("N/A"));
  }

  //    Update the Sea Surface Temperature
  if (RecordArray[Idx_SEA_TEMP]) {
    double temp = RecordArray[Idx_SEA_TEMP]->getInterpolatedValue(
        m_cursor_lon, m_cursor_lat, true);

    if (temp != GRIB_NOTDEF) {
      temp = m_gparent.m_OverlaySettings.CalibrateValue(
          GribOverlaySettings::SEA_TEMPERATURE, temp);
      m_tcSeaTemperature->SetValue(wxString::Format(
          _T("%5.1f ") + m_gparent.m_OverlaySettings.GetUnitSymbol(
                             GribOverlaySettings::SEA_TEMPERATURE),
          temp));
    } else
      m_tcSeaTemperature->SetValue(_("N/A"));
  }

  //    Update the Convective Available Potential Energy (CAPE)
  if (RecordArray[Idx_CAPE]) {
    double cape = RecordArray[Idx_CAPE]->getInterpolatedValue(
        m_cursor_lon, m_cursor_lat, true);

    if (cape != GRIB_NOTDEF) {
      cape = m_gparent.m_OverlaySettings.CalibrateValue(
          GribOverlaySettings::CAPE, cape);
      m_tcCAPE->SetValue(wxString::Format(
          _T("%5.0f ") + m_gparent.m_OverlaySettings.GetUnitSymbol(
                             GribOverlaySettings::CAPE),
          cape));
    } else
      m_tcCAPE->SetValue(_("N/A"));
  }
  if (RecordArray[Idx_COMP_REFL]) {
    double c_refl = RecordArray[Idx_COMP_REFL]->getInterpolatedValue(
        m_cursor_lon, m_cursor_lat, true);

    if (c_refl != GRIB_NOTDEF) {
      c_refl = m_gparent.m_OverlaySettings.CalibrateValue(
          GribOverlaySettings::COMP_REFL, c_refl);
      m_tcReflC->SetValue(wxString::Format(
          _T("%5.0f ") + m_gparent.m_OverlaySettings.GetUnitSymbol(
                             GribOverlaySettings::COMP_REFL),
          c_refl));
    } else
      m_tcReflC->SetValue(_("N/A"));
  }
  // Update extra data for altitude
  // geopotential altitude
  if (RecordArray[Idx_GEOP_HGT + m_Altitude]) {
    double geop = RecordArray[Idx_GEOP_HGT + m_Altitude]->getInterpolatedValue(
        m_cursor_lon, m_cursor_lat, true);

    if (geop != GRIB_NOTDEF) {
      geop = m_gparent.m_OverlaySettings.CalibrateValue(
          GribOverlaySettings::GEO_ALTITUDE, geop);
      m_tcAltitude->SetValue(wxString::Format(_T("%5.0f "), geop) +
                             m_gparent.m_OverlaySettings.GetUnitSymbol(
                                 GribOverlaySettings::GEO_ALTITUDE));
    } else
      m_tcAltitude->SetValue(_("N/A"));
  }

  // temperature
  if (RecordArray[Idx_AIR_TEMP + m_Altitude]) {
    double temp = RecordArray[Idx_AIR_TEMP + m_Altitude]->getInterpolatedValue(
        m_cursor_lon, m_cursor_lat, true);

    if (temp != GRIB_NOTDEF) {
      temp = m_gparent.m_OverlaySettings.CalibrateValue(
          GribOverlaySettings::AIR_TEMPERATURE, temp);
      m_tcTemp->SetValue(wxString::Format(_T("%5.1f "), temp) +
                         m_gparent.m_OverlaySettings.GetUnitSymbol(
                             GribOverlaySettings::AIR_TEMPERATURE));
    } else
      m_tcTemp->SetValue(_("N/A"));
  }
  // relative humidity
  if (RecordArray[Idx_HUMID_RE + m_Altitude]) {
    double humi = RecordArray[Idx_HUMID_RE + m_Altitude]->getInterpolatedValue(
        m_cursor_lon, m_cursor_lat, true);

    if (humi != GRIB_NOTDEF) {
      humi = m_gparent.m_OverlaySettings.CalibrateValue(
          GribOverlaySettings::REL_HUMIDITY, humi);
      m_tcRelHumid->SetValue(wxString::Format(_T("%5.0f "), humi) +
                             m_gparent.m_OverlaySettings.GetUnitSymbol(
                                 GribOverlaySettings::REL_HUMIDITY));
    } else
      m_tcRelHumid->SetValue(_("N/A"));
  }
}

void CursorData::OnMenuCallBack(wxMouseEvent &event) {
  // populate menu
  wxMenu *menu = new wxMenu();
  int id = event.GetId();
  switch (id) {
    case GribOverlaySettings::WIND:
      MenuAppend(menu, B_ARROWS, _("Barbed Arrows"), id);
      MenuAppend(menu, ISO_LINE, _("Display Isotachs"), id);
      MenuAppend(menu, OVERLAY, _("OverlayMap"), id);
      MenuAppend(menu, NUMBERS, _("Numbers"), id);
      MenuAppend(menu, PARTICLES, _("Particle Map"), id);
      break;
    case GribOverlaySettings::WIND_GUST:
      MenuAppend(menu, ISO_LINE, _("Display Isotachs"), id);
      MenuAppend(menu, OVERLAY, _("OverlayMap"), id);
      MenuAppend(menu, NUMBERS, _("Numbers"), id);
      break;
    case GribOverlaySettings::PRESSURE:
      MenuAppend(menu, ISO_LINE, _("Display Isobars"), id);
      MenuAppend(menu, ISO_ABBR, _("Abbreviated Isobars Numbers"), id);
      MenuAppend(menu, NUMBERS, _("Numbers"), id);
      break;
    case GribOverlaySettings::AIR_TEMPERATURE:
    case GribOverlaySettings::SEA_TEMPERATURE:
      MenuAppend(menu, ISO_LINE, _("Display Isotherms"), id);
      // fall through
    case GribOverlaySettings::CLOUD:
    case GribOverlaySettings::PRECIPITATION:
      MenuAppend(menu, OVERLAY, _("OverlayMap"), id);
      MenuAppend(menu, NUMBERS, _("Numbers"), id);
      break;
    case GribOverlaySettings::CAPE:
      MenuAppend(menu, ISO_LINE, _("Display Iso CAPE"), id);
      MenuAppend(menu, OVERLAY, _("OverlayMap"), id);
      MenuAppend(menu, NUMBERS, _("Numbers"), id);
      break;
    case GribOverlaySettings::COMP_REFL:
      MenuAppend(menu, ISO_LINE, _("Display Iso Reflectivity"), id);
      MenuAppend(menu, OVERLAY, _("OverlayMap"), id);
      MenuAppend(menu, NUMBERS, _("Numbers"), id);
      break;
    case GribOverlaySettings::WAVE:
      MenuAppend(menu, D_ARROWS, _("Direction Arrows"), id);
      MenuAppend(menu, OVERLAY, _("OverlayMap"), id);
      MenuAppend(menu, NUMBERS, _("Numbers"), id);
      break;
    case GribOverlaySettings::CURRENT:
      MenuAppend(menu, D_ARROWS, _("Direction Arrows"), id);
      MenuAppend(menu, OVERLAY, _("OverlayMap"), id);
      MenuAppend(menu, NUMBERS, _("Numbers"), id);
      MenuAppend(menu, PARTICLES, _("Particle Map"), id);
  }

  PopupMenu(menu);

  // apply new config
  wxwxMenuItemListNode *node = menu->GetMenuItems().GetFirst();
  while (node) {
    wxMenuItem *it = node->GetData();
    switch (it->GetId()) {
      case B_ARROWS:
        m_gparent.m_OverlaySettings.Settings[id].m_bBarbedArrows =
            it->IsChecked();
        break;
      case ISO_LINE:
        m_gparent.m_OverlaySettings.Settings[id].m_bIsoBars = it->IsChecked();
        break;
      case ISO_ABBR:
        m_gparent.m_OverlaySettings.Settings[id].m_bAbbrIsoBarsNumbers =
            it->IsChecked();
        break;
      case D_ARROWS:
        m_gparent.m_OverlaySettings.Settings[id].m_bDirectionArrows =
            it->IsChecked();
        break;
      case OVERLAY:
        m_gparent.m_OverlaySettings.Settings[id].m_bOverlayMap =
            it->IsChecked();
        break;
      case NUMBERS:
        m_gparent.m_OverlaySettings.Settings[id].m_bNumbers = it->IsChecked();
        break;
      case PARTICLES:
        m_gparent.m_OverlaySettings.Settings[id].m_bParticles = it->IsChecked();
    }
    node = node->GetNext();
  }

  // if the current parameter type is selected then resolve display conflicts
  if (m_gparent.InDataPlot(id) && m_gparent.m_bDataPlot[id])
    ResolveDisplayConflicts(id);

  // save new config
  m_gparent.m_OverlaySettings.Write();

  delete menu;
}

void CursorData::MenuAppend(wxMenu *menu, int id, wxString label, int setting) {
  wxMenuItem *item = new wxMenuItem(menu, id, label, _T(""), wxITEM_CHECK);

#ifdef __WXMSW__
  wxFont *qFont = OCPNGetFont(_("Menu"), 10);
  item->SetFont(*qFont);
#endif

  menu->Append(item);

  bool check;
  if (id == B_ARROWS)
    check = m_gparent.m_OverlaySettings.Settings[setting].m_bBarbedArrows;
  else if (id == ISO_LINE)
    check = m_gparent.m_OverlaySettings.Settings[setting].m_bIsoBars;
  else if (id == ISO_ABBR)
    check = m_gparent.m_OverlaySettings.Settings[setting].m_bAbbrIsoBarsNumbers;
  else if (id == D_ARROWS)
    check = m_gparent.m_OverlaySettings.Settings[setting].m_bDirectionArrows;
  else if (id == OVERLAY)
    check = m_gparent.m_OverlaySettings.Settings[setting].m_bOverlayMap;
  else if (id == NUMBERS)
    check = m_gparent.m_OverlaySettings.Settings[setting].m_bNumbers;
  else if (id == PARTICLES)
    check = m_gparent.m_OverlaySettings.Settings[setting].m_bParticles;
  else
    check = false;
  item->Check(check);
}

void CursorData::OnMouseEvent(wxMouseEvent &event) {
  if (event.RightDown()) {
    if (m_DialogStyle >> 1 == ATTACHED) {
      wxMouseEvent evt(event);
      m_gparent.OnMouseEvent(evt);
    }
    return;
  }

  static wxPoint s_gspt;
  int x, y;

  event.GetPosition(&x, &y);
  wxPoint spt = wxPoint(x, y);
  if (event.GetId() != 1000)
    spt = ClientToScreen(spt);
  else
    spt = GetParent()->ClientToScreen(spt);

#ifdef __WXOSX__
  if (!m_bLeftDown && event.LeftIsDown()) {
    m_bLeftDown = true;
    s_gspt = spt;
    if (!HasCapture()) CaptureMouse();
  } else if (m_bLeftDown && !event.LeftIsDown()) {
    // GetParent()->Move( GetParent()->GetPosition() );
    m_bLeftDown = false;
    if (HasCapture()) ReleaseMouse();
  }
#else

  if (event.LeftDown()) {
    s_gspt = spt;
    if (!HasCapture()) CaptureMouse();
  }

  if (event.LeftUp()) {
    // GetParent()->Move( GetParent()->GetPosition() );
    if (HasCapture()) ReleaseMouse();
  }
#endif

  if (event.Dragging()) {
    wxPoint par_pos_old = GetParent()->GetPosition();

    wxPoint par_pos = par_pos_old;
    par_pos.x += spt.x - s_gspt.x;
    par_pos.y += spt.y - s_gspt.y;

    wxPoint pos_in_parent = GetOCPNCanvasWindow()->ScreenToClient(par_pos);
    wxPoint pos_in_parent_old =
        GetOCPNCanvasWindow()->ScreenToClient(par_pos_old);

    // X
    if (pos_in_parent.x < pos_in_parent_old.x) {  // moving left
      if (pos_in_parent.x < 10) {
        pos_in_parent.x = 0;
      }
    } else if (pos_in_parent.x > pos_in_parent_old.x) {  // moving right
      int max_right =
          GetOCPNCanvasWindow()->GetClientSize().x - GetParent()->GetSize().x;
      if (pos_in_parent.x > (max_right - 10)) {
        pos_in_parent.x = max_right;
      }
    }

    // Y
    if (pos_in_parent.y < pos_in_parent_old.y) {  // moving up
      if (pos_in_parent.y < 10) {
        pos_in_parent.y = 0;
      }
    } else if (pos_in_parent.y > pos_in_parent_old.y) {  // moving dow
      int max_down =
          GetOCPNCanvasWindow()->GetClientSize().y - GetParent()->GetSize().y;
      if (pos_in_parent.y > (max_down - 10)) {
        pos_in_parent.y = max_down;
      }
    }

    wxPoint final_pos = GetOCPNCanvasWindow()->ClientToScreen(pos_in_parent);

    GetParent()->Move(final_pos);

    s_gspt = spt;
  }
}
