/*
 * Copyright (c) 2003-2017 Rony Shapiro <ronys@pwsafe.org>.
 * All rights reserved. Use of the code is allowed under the
 * Artistic License 2.0 terms, as specified in the LICENSE file
 * distributed with this code, or available from
 * http://www.opensource.org/licenses/artistic-license-2.0.php
 */

/** \file pwsgrid.cpp
*
*/
// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

////@begin includes
////@end includes

#include <utility> // for make_pair
#include "PWSgrid.h"
#include "passwordsafeframe.h" // for DispatchDblClickAction()
#include <wx/memory.h>
#include <algorithm>
#include <functional>
#include "./PWSgridtable.h"

#ifdef __WXMSW__
#include <wx/msw/msvcrt.h>
#endif

////@begin XPM images
////@end XPM images

using pws_os::CUUID;

/*!
 * PWSGrid type definition
 */

IMPLEMENT_CLASS( PWSGrid, wxGrid )

/*!
 * PWSGrid event table definition
 */

BEGIN_EVENT_TABLE( PWSGrid, wxGrid )

////@begin PWSGrid event table entries
  EVT_GRID_CELL_RIGHT_CLICK( PWSGrid::OnCellRightClick )
  EVT_GRID_CELL_LEFT_DCLICK( PWSGrid::OnLeftDClick )
  EVT_GRID_SELECT_CELL( PWSGrid::OnSelectCell )
  EVT_CHAR( PWSGrid::OnChar )
  EVT_CONTEXT_MENU(PWSGrid::OnContextMenu)
  EVT_CUSTOM(wxEVT_GUI_DB_PREFS_CHANGE, wxID_ANY, PWSGrid::OnDBGUIPrefsChange)
////@end PWSGrid event table entries

END_EVENT_TABLE()

/*!
 * PWSGrid constructors
 */

PWSGrid::PWSGrid(PWScore &core) : m_core(core)
{
  Init();
}

PWSGrid::PWSGrid(wxWindow* parent, PWScore &core,
                 wxWindowID id, const wxPoint& pos,
                 const wxSize& size, long style) : m_core(core)
{
  Init();
  Create(parent, id, pos, size, style);
}

/*!
 * PWSGrid creator
 */

bool PWSGrid::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
{
////@begin PWSGrid creation
  wxGrid::Create(parent, id, pos, size, style);
  CreateControls();
  EnableGridLines(PWSprefs::GetInstance()->GetPref(PWSprefs::ListViewGridLines));
  EnableDragColMove(true);

  //column picker is free if built with wx2.9.1
#if wxCHECK_VERSION(2, 9, 1)
  UseNativeColHeader(true);
#endif
////@end PWSGrid creation
  return true;
}

/*!
 * PWSGrid destructor
 */

PWSGrid::~PWSGrid()
{
////@begin PWSGrid destruction
////@end PWSGrid destruction
}

/*!
 * Member initialisation
 */

void PWSGrid::Init()
{
////@begin PWSGrid member initialisation
////@end PWSGrid member initialisation
}

/*!
 * Control creation for PWSGrid
 */

void PWSGrid::CreateControls()
{
////@begin PWSGrid content construction
////@end PWSGrid content construction
  CreateGrid(0, PWSGridTable::GetNumHeaderCols(), wxGrid::wxGridSelectRows);
  SetColLabelValue(0, _("Title"));
  SetColLabelValue(1, _("User"));
  SetRowLabelSize(0);
  int w,h;
  GetClientSize(&w, &h);
  int cw = w/2; // 2 = number of columns
  SetColSize(0, cw);
  SetColSize(1, cw);
}

void PWSGrid::OnPasswordListModified()
{
  m_row_map.clear();
  m_uuid_map.clear();

  ItemListConstIter iter;
  int row = 0;
  for (iter = m_core.GetEntryIter();
       iter != m_core.GetEntryEndIter();
       iter++) {
    AddItem(iter->second, row);
    row++;
  }
}

/*!
 * Should we show tooltips?
 */

bool PWSGrid::ShowToolTips()
{
  return true;
}

/*!
 * Get bitmap resources
 */

wxBitmap PWSGrid::GetBitmapResource( const wxString& WXUNUSED(name) )
{
  // Bitmap retrieval
////@begin PWSGrid bitmap retrieval
  return wxNullBitmap;
////@end PWSGrid bitmap retrieval
}

/*!
 * Get icon resources
 */

wxIcon PWSGrid::GetIconResource( const wxString& WXUNUSED(name) )
{
  // Icon retrieval
////@begin PWSGrid icon retrieval
  return wxNullIcon;
////@end PWSGrid icon retrieval
}

void PWSGrid::AddItem(const CItemData &item, int row)
{
  int nRows = GetNumberRows();
  if (row == -1)
    row = nRows;
  uuid_array_t uuid;
  item.GetUUID(uuid);
  m_row_map.insert(std::make_pair(row, CUUID(uuid)));
  m_uuid_map.insert(std::make_pair(CUUID(uuid), row));
  InsertRows(row);
}

void PWSGrid::UpdateItem(const CItemData &item)
{
  uuid_array_t uuid;
  item.GetUUID(uuid);
  UUIDRowMapT::iterator iter = m_uuid_map.find(CUUID(uuid));
  if (iter != m_uuid_map.end()) {
    int row = iter->second;
    DeleteRows(row);
    InsertRows(row);
  }
}

void PWSGrid::RefreshRow(int row)
{
  wxRect rect(CellToRect( row, 0 ));
  rect.x = 0;
  rect.width = GetGridWindow()->GetClientSize().GetWidth();
  int dummy;
  CalcScrolledPosition(0, rect.y, &dummy, &rect.y);
  GetGridWindow()->Refresh( false, &rect );
}

void PWSGrid::RefreshItemRow(const pws_os::CUUID& uuid)
{
  const int row = FindItemRow(uuid);
  if (row != wxNOT_FOUND)
    RefreshRow(row);
}

void PWSGrid::RefreshItemField(const pws_os::CUUID& uuid, CItemData::FieldType ft)
{
  int row = FindItemRow(uuid);
  int col = PWSGridTable::Field2Column(ft);
  if (row != wxNOT_FOUND && col != wxNOT_FOUND && IsVisible(row, col, false)) {
    //last param is false to check if the required cell is even partially visible
    RefreshItemRow(uuid);
  }
}

struct moveup : public std::binary_function<UUIDRowMapT::value_type, int, void> {
  void operator()(UUIDRowMapT::value_type& v, int rowDeleted) const {
    if (v.second > rowDeleted)
      v.second = v.second - 1;
  }
};

void PWSGrid::Remove(const CUUID &uuid)
{
  UUIDRowMapT::iterator iter = m_uuid_map.find(uuid);
  if (iter != m_uuid_map.end()) {

    const int row = iter->second;

    //The UI element must be removed first, since the entry in m_core is deleted after
    //this callback returns, and the deletion process might check for consistency
    //in number of entries in various UI maps and m_core
    DeleteRows(row);

    //move all the rows up by 1
    for (size_t newRow = row + 1; newRow < m_row_map.size(); ++newRow) {
      m_row_map[reinterpret_cast<int &>(newRow) - 1] = m_row_map[reinterpret_cast<int &>(newRow)];
    }

    //remove the last row, which is now extraneous
    m_row_map.erase(static_cast<int>(m_row_map.size()) - 1);

    //subtract the row values of all entries in uuid map if it is greater
    //than the row we just deleted
    std::for_each(m_uuid_map.begin(), m_uuid_map.end(), std::bind2nd(moveup(), row));

    //delete the item itself
    m_uuid_map.erase(iter);
  }
}

/*!
 * Returns the number of elements in its own books
 */
size_t PWSGrid::GetNumItems() const
{
  //These vars help with debugging.  Declared volatile to keep the compiler
  //from optimizing them away
  volatile size_t uuid_map_size = m_uuid_map.size();
  volatile size_t row_map_size = m_row_map.size();
  volatile size_t numEntries = m_core.GetNumEntries();

  //asserts below are commented out since it is unavoidable that the grid
  //receives a paint event between the time an entry is erased from
  //its maps and the corresponding row is erased from the grid UI
  if(!m_uuid_map.empty() && uuid_map_size != numEntries) {
    //wxFAIL_MSG(wxString() << wxT("uuid map has ") << uuid_map_size
    //                      << wxT(" entries, but core has ") << numEntries);
  }

  if (!m_row_map.empty() && row_map_size != numEntries) {
    //wxFAIL_MSG(wxString() << wxT("row map has ") << row_map_size
    //                      << wxT(" entries, but core has ") << numEntries);
  }
  //this is the most meaningful, to keep wxGrid from asking us
  //about rows that we don't have
  return row_map_size;
}

/*!
 * callback from PWSGridTable.  Removes the item(s) from its own books
 * as well as from PWSCore.  The display will be updated by an event
 * generated by PWSGridTable once this function returns.
 */
void PWSGrid::DeleteItems(int row, size_t numItems)
{
  for (size_t N = 0; N < numItems; ++N) {
    RowUUIDMapT::iterator iter = m_row_map.find(row);
    if (iter != m_row_map.end()) {
      UUIDRowMapT::iterator iter_uuid = m_uuid_map.find(iter->second);
      m_row_map.erase(iter);
      if (iter_uuid != m_uuid_map.end()) {
        uuid_array_t uuid;
        iter_uuid->first.GetARep(uuid);
        m_uuid_map.erase(iter_uuid);
        ItemListIter citer = m_core.Find(uuid);
        if (citer != m_core.GetEntryEndIter()){
          m_core.SuspendOnDBNotification();
          m_core.Execute(DeleteEntryCommand::Create(&m_core,
                                                    m_core.GetEntry(citer)));
          m_core.ResumeOnDBNotification();
        }
      }
    }
  }
  if (m_core.HasDBChanged())
    OnPasswordListModified();
}

/*!
 * callback from PWSGridTable.  Removes all items from its own books
 * as well as from PWSCore.  The items from display grid will be
 * removed by an event generated by PWSGridTable once this function
 * returns
 */
void PWSGrid::DeleteAllItems()
{
  m_uuid_map.clear();
  m_row_map.clear();
}

/*!
 * wxEVT_GRID_CELL_RIGHT_CLICK event handler for ID_LISTBOX
 */

void PWSGrid::OnCellRightClick( wxGridEvent& evt )
{
  // We need this function because wxGrid doesn't convert unprocessed
  // right-mouse-down events to contextmenu events, so we need to do
  // handle it ourselves
  //
  // This is what we should ideally do, but in the ContextMenu handler below,
  // I can't convert the mouse position to logical(x,y) to grid's row, column.
  // The row is always 1 more than where I click
  //
  // wxContextMenuEvent cme(wxEVT_CONTEXT_MENU, GetId(), event.GetPosition());
  // cme.SetEventObject(event.GetEventObject());
  // ProcessEvent(cme);
  //
  SetGridCursor(evt.GetRow(), evt.GetCol());
  SelectRow(evt.GetRow());
  dynamic_cast<PasswordSafeFrame *>(GetParent())->OnContextMenu(GetItem(evt.GetRow()));
}

/*!
 * wxEVT_GRID_CELL_ITEM_MENU event handler for ID_LISTBOX
 */

void PWSGrid::OnContextMenu( wxContextMenuEvent& evt )
{
  wxPoint pos = evt.GetPosition();
  if ( pos == wxDefaultPosition ) { //sent from keyboard?
    const int row = GetGridCursorRow();
    SelectRow(row);
    dynamic_cast<PasswordSafeFrame *>(GetParent())->OnContextMenu(GetItem(row));
  }
  else { //sent from mouse.  I don't know how to convert the mouse coords to grid's row,column
    evt.Skip();
  }
}

CItemData *PWSGrid::GetItem(int row) const
{
  if (row < 0 || row > const_cast<PWSGrid *>(this)->GetNumberRows())
    return NULL;
  RowUUIDMapT::const_iterator iter = m_row_map.find(row);
  if (iter != m_row_map.end()) {
    uuid_array_t uuid;
    iter->second.GetARep(uuid);
    ItemListIter itemiter = m_core.Find(uuid);
    if (itemiter == m_core.GetEntryEndIter())
      return NULL;
    return &itemiter->second;
  }
  return NULL;
}

/*!
 * wxEVT_GRID_CELL_LEFT_DCLICK event handler for ID_LISTBOX
 */

void PWSGrid::OnLeftDClick( wxGridEvent& evt )
{
  CItemData *item = GetItem(evt.GetRow());
  if (item != NULL)
    dynamic_cast<PasswordSafeFrame *>(GetParent())->
      DispatchDblClickAction(*item);
}

 void PWSGrid::SelectItem(const CUUID & uuid)
 {
     UUIDRowMapT::const_iterator itr = m_uuid_map.find(uuid);
     if (itr != m_uuid_map.end()) {
         MakeCellVisible(itr->second, 0);
         wxGrid::SelectRow(itr->second);
     }
 }

int  PWSGrid::FindItemRow(const CUUID& uu)
{
     UUIDRowMapT::const_iterator itr = m_uuid_map.find(uu);
     if (itr != m_uuid_map.end()) {
       return itr->second;
     }
     return wxNOT_FOUND;
}

/*!
 * wxEVT_CHAR event handler for ID_LISTBOX
 */

void PWSGrid::OnChar( wxKeyEvent& evt )
{
  if (evt.GetKeyCode() == WXK_ESCAPE &&
      PWSprefs::GetInstance()->GetPref(PWSprefs::EscExits)) {
    GetParent()->Close(true);
  }
  evt.Skip();
}

void PWSGrid::SaveSettings(void) const
{
  PWSGridTable* table = dynamic_cast<PWSGridTable*>(GetTable());
  if (table)  //may not have been created/assigned
    table->SaveSettings();
}

void PWSGrid::OnDBGUIPrefsChange(wxEvent& evt)
{
  UNREFERENCED_PARAMETER(evt);
  EnableGridLines(PWSprefs::GetInstance()->GetPref(PWSprefs::ListViewGridLines));
}

void PWSGrid::Clear()
{
  if (GetNumberRows() > 0) {
    BeginBatch();
    DeleteRows(0, GetNumberRows());
    EndBatch();
  }
  DeleteAllItems();
}

/*!
 * wxEVT_GRID_SELECT_CELL event handler for ID_LISTBOX
 */

void PWSGrid::OnSelectCell( wxGridEvent& evt )
{
  CItemData *pci = GetItem(evt.GetRow());

  dynamic_cast<PasswordSafeFrame *>(GetParent())->UpdateSelChanged(pci);
}

void PWSGrid::SetFilterState(bool state)
{
  const wxColour *colour = state ? wxRED : wxBLACK;
  SetDefaultCellTextColour(*colour);
  ForceRefresh();
}
