/* 
 * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
 *
 * 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; version 2 of the
 * License.
 * 
 * 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 St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.VisualBasic.Devices;

using MySQL.Controls;
using MySQL.GUI.Workbench;
using MySQL.Utilities;
using MySQL.Workbench;

namespace MySQL.Grt.Db
{
  public partial class RecordsetView : Form
  {
    private RecordsetWrapper model;
    private GridView gridView;
    private ColumnFilterDialog columnFilterDialog = new ColumnFilterDialog();
    private int contextColumnIndex;
    private int contextRowIndex;
    private Timer dataSearchTimer;
    private Image columnHeaderFilterImage;

    public RecordsetView()
    {
      InitializeComponent();

      dataSearchTimer = new Timer();
      dataSearchTimer.Interval = 1000;
      dataSearchTimer.Tick += new EventHandler(OnDataSearchApply);

      mainToolStrip.Renderer = new FlatSubToolStripRenderer();
    }

    public void Embed(Control parent, RecordsetWrapper recordset)
    {
      recordsetPanel.Parent = parent;

      gridView = new GridView(recordset);
      gridView.Parent = viewPanel;
      gridView.Dock = DockStyle.Fill;
      gridView.BorderStyle = BorderStyle.None;
      gridView.StandardTab = false; // Let Tab move the cursor to the next cell instead next control in parent tab order.

      gridView.KeyDown += gridView_KeyDown;
      gridView.MouseDown += gridView_MouseDown;
      gridView.ColumnHeaderMouseClick += gridView_ColumnHeaderMouseClick;
      gridView.CellContextMenuStripNeeded += gridView_CellContextMenuStripNeeded;
      gridView.CellStateChanged += gridView_CellStateChanged;
      gridView.CellPainting += gridView_CellPainting;

      gridView.AdditionalColumnWidth += gridView_AdditionalColumnWidth;

      // cache icon used for marking columns with applied filters
      {
        int iconId = recordset.column_filter_icon_id();
        columnHeaderFilterImage = GrtIconManager.get_instance().get_icon(iconId);
      }

      // load toolbar items
      {
        List<ToolStripItem> existingToolStripItems = new List<ToolStripItem>(mainToolStrip.Items.Count);
        List<ToolbarItem> items = recordset.get_toolbar_items();
        foreach (ToolStripItem item in mainToolStrip.Items)
          existingToolStripItems.Add(item);

        WorkbenchToolbarManager.Instance.UpdateToolbar(mainToolStrip, items, TriggerAction);
        
        foreach (ToolStripItem item in existingToolStripItems)
          mainToolStrip.Items.Add(item);
      }

      Model = recordset;
    }

    public bool Dirty
    {
      get { return model.has_pending_changes(); }
    }

    public GridView GridView
    {
      get { return gridView; }
    }

    public RecordsetWrapper Model
    {
      set
      {
        gridView.Model = value;
        model = value;
        model.refresh_ui_cb(ProcessModelChange);
        model.add_tree_refresh_handler(ProcessTreeRefresh);
        model.refresh_ui_status_bar_cb(UpdateStatusBar);
        model.task.msg_cb(ProcessTaskMsg);

        ActionList actionList = model.action_list;
        actionList.register_action("record_first", RecordNavigateFirst);
        actionList.register_action("record_back", RecordNavigateBack);
        actionList.register_action("record_next", RecordNavigateNext);
        actionList.register_action("record_last", RecordNavigateLast);
        actionList.register_action("record_wrap_vertical", ToggleGridWrapMode);
        actionList.register_action("record_sort_asc", SortAscending);
        actionList.register_action("record_sort_desc", SortDescending);
        actionList.register_action("record_edit", EditSelectedRecord);
        actionList.register_action("record_add", AddNewRecord);
        actionList.register_action("record_del", DeleteCurrentRecord);
        actionList.register_action("record_save", SaveChanges);
        actionList.register_action("record_discard", DiscardChanges);

        // init popup menu
        {
          actionList.register_rows_col_action("set_to_null", OnSelectedFieldsNullify);
          actionList.register_rows_col_action("delete_row", DeleteSelectedRecords);

          SqlIdeMenuManager.MenuContext popupMenuContext = new SqlIdeMenuManager.MenuContext();
          popupMenuContext.ActionList = model.action_list;
          popupMenuContext.GetRowsColMenuItems = model.get_popup_menu_items;
          popupMenuContext.GetSelectedRowsCol = gridView.GetSelectedRowsCol;
          popupMenuContext.TriggerRowsColAction = model.activate_popup_menu_item;

          SqlIdeMenuManager.InitMenu(contextMenuStrip, popupMenuContext);
        }
      }
      get { return model; }
    }

    // TODO: seems local message are no longer needed. So this code and the split container can be removed.
    private int ProcessTaskMsg(int msgType, String message, String detail)
    {
      messagesTB.Text = message;
      int splitterDistance = (message.Length == 0) ? splitContainer1.Height : splitContainer1.Height-25-splitContainer1.SplitterWidth;
      if (splitterDistance < 0)
        splitterDistance = 0;
      splitContainer1.SplitterDistance = splitterDistance;
      
      switch ((Msg_type)msgType)
      {
        case Msg_type.MT_error:
          messagesTB.ForeColor = Color.Red;
          break;
        case Msg_type.MT_warning:
        case Msg_type.MT_info:
          messagesTB.ForeColor = SystemColors.WindowText;
          break;
      }

      return 0;
    }

    private int UpdateStatusBar()
    {
      //gridView.Refresh();
      return 0;
    }

    private bool pendingRefresh = false;
    private void ProcessTreeRefresh(NodeId node, int ocount)
    {
      if (!pendingRefresh)
      {
        Application.Idle += new EventHandler(OnIdle);
        pendingRefresh = true;
      }
    }

    private void OnIdle(Object sender, EventArgs e)
    {
      gridView.Refresh();
      gridView.ProcessModelRowsChange();
      pendingRefresh = false;
      Application.Idle -= OnIdle;
    }
    
    public int ProcessModelChange()
    {
      dataSearchTimer.Stop();
      gridView.ProcessModelChange();

      UpdateStatusBar();

      setNullMenuItem.Enabled = !gridView.ReadOnly;
      deleteRowsMenuItem.Enabled = !gridView.ReadOnly;

      return 0;
    }

    private string lastSearchString = "";

    private bool TriggerAction(String name, object data)
    {
      if (name == "filter")
      {
        lastSearchString = data as string;
        dataSearchTimer.Stop();
        dataSearchTimer.Start();
        return true;
      }
      else
        return model.action_list.trigger_action(name);
    }

    #region Message handling

    private void setNullMenuItem_Click(object sender, EventArgs e)
    {
      int column = -1;
      List<int> rows = gridView.GetSelectedRowsCol(ref column);
      OnSelectedFieldsNullify(rows, column);
    }

    private void OnSelectedFieldsNullify(List<int> rows, int column)
    {
      if (gridView.ReadOnly)
        return;
      foreach (DataGridViewCell cell in gridView.SelectedCells)
      {
        model.set_field_null(new NodeId(cell.RowIndex), cell.ColumnIndex);
        gridView.InvalidateCell(cell);
      }
    }

    private void gridView_KeyDown(object sender, KeyEventArgs e)
    {
      switch (e.KeyCode)
      {
        case Keys.Delete:
        case Keys.Back:
          {
            if (1 == gridView.SelectedCells.Count)
              setNullMenuItem_Click(gridView, new EventArgs());
          }
          break;
        default:
          break;
      }
    }

    private void closeButton_Click(object sender, EventArgs e)
    {
      if (model.can_close())
        model.close(); // This will remove the record set from the back with a notification
                       // to remove the UI (handled in SqlIdeForm.cs).
    }

    private void deleteRowsMenuItem_Click(object sender, EventArgs e)
    {
      List<DataGridViewRow> selectedRows = gridView.GetSelectedRows();
      List<NodeId> nodes = new List<NodeId>(selectedRows.Count);
      foreach (DataGridViewRow row in selectedRows)
        nodes.Add(new NodeId(row.Index));
      model.delete_nodes(nodes);
      gridView.ProcessModelRowsChange();
    }

    private void copyRowValuesMenuItem_Click(object sender, EventArgs e)
    {
      List<DataGridViewRow> selectedRows = gridView.GetSelectedRows();
      List<int> selectedRowIndeces = new List<int>(selectedRows.Count);
      foreach (DataGridViewRow row in selectedRows)
        selectedRowIndeces.Add(row.Index);
      model.copy_rows_to_clipboard(selectedRowIndeces);
    }

    private void CopyFieldContentMenuItem_Click(object sender, EventArgs e)
    {
      if (gridView.SelectedCells.Count > 0)
      {
        DataGridViewCell cell = gridView.SelectedCells[0];
        model.copy_field_to_clipboard(cell.RowIndex, cell.ColumnIndex);
      }
    }

    private void gridView_CellContextMenuStripNeeded(object sender, DataGridViewCellContextMenuStripNeededEventArgs e)
    {
      contextColumnIndex = e.ColumnIndex;
      contextRowIndex = e.RowIndex;
      if (e.RowIndex == -1)
      {
        e.ContextMenuStrip = (e.ColumnIndex == -1) ? null : columnHeaderContextMenuStrip;
        resetAllColumnFiltersToolStripMenuItem.Enabled = model.has_column_filters();
        resetColumnFilterToolStripMenuItem.Enabled = model.has_column_filter(contextColumnIndex);
      }
      else
      {
        e.ContextMenuStrip = contextMenuStrip;
      }
    }

    private void gridView_MouseDown(object sender, MouseEventArgs e)
    {
      if (e.Button == MouseButtons.Right)
      {
        DataGridView.HitTestInfo info = gridView.HitTest(e.X, e.Y);
        if (info.ColumnIndex < 0 || info.RowIndex < 0)
          return;

        DataGridViewCell cell = gridView[info.ColumnIndex, info.RowIndex];
        if (!cell.Selected)
        {
          if (Control.ModifierKeys == Keys.None)
            gridView.ClearSelection();
          cell.Selected = true;
        }
      }
    }

    private void gridView_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
    {
      if (MouseButtons.Left == e.Button)
      {
        int direction = (gridView.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection == SortOrder.Ascending ? -1 : 1);
        Keyboard keyboard = new Keyboard();
        direction = (keyboard.AltKeyDown ? 0 : direction);
        bool retaining = (0 == direction) || keyboard.ShiftKeyDown;
        SortByColumn(e.ColumnIndex, direction, retaining);
      }
    }

    private void setColumnFilterToolStripMenuItem_Click(object sender, EventArgs e)
    {
      columnFilterDialog.ColumnName = model.get_column_caption(contextColumnIndex);
      columnFilterDialog.FilterExpression = model.get_column_filter_expr(contextColumnIndex);
      columnFilterDialog.Location = new Point(columnHeaderContextMenuStrip.Left, columnHeaderContextMenuStrip.Top);
      columnFilterDialog.ShowDialog();
      if (columnFilterDialog.DialogResult == DialogResult.OK)
      {
        model.set_column_filter(contextColumnIndex, columnFilterDialog.FilterExpression);
      }
    }

    private void resetColumnFilterToolStripMenuItem_Click(object sender, EventArgs e)
    {
      model.reset_column_filter(contextColumnIndex);
    }

    private void resetAllColumnFiltersToolStripMenuItem_Click(object sender, EventArgs e)
    {
      model.reset_column_filters();
    }

    private void SortByColumn(int columnIndex, int direction, bool retaining)
    {
      SortOrder sortOrder;
      switch (direction)
      {
        case -1: sortOrder = SortOrder.Ascending; break;
        case 1: sortOrder = SortOrder.Descending; break;
        case 0: default: sortOrder = SortOrder.None; break;
      }
      gridView.Columns[columnIndex].HeaderCell.SortGlyphDirection = sortOrder;
      model.sort_by(columnIndex, direction, retaining);
    }

    private void OnDataSearchApply(object sender, EventArgs e)
    {
      dataSearchTimer.Stop();
      if (lastSearchString.Length == 0)
        model.reset_data_search_string();
      else
        model.set_data_search_string(lastSearchString);
    }

    void gridView_CellStateChanged(object sender, DataGridViewCellStateChangedEventArgs e)
    {
      if (e.StateChanged == DataGridViewElementStates.Selected)
      {
        if (e.Cell.Selected)
        {
          contextColumnIndex = e.Cell.ColumnIndex;
          contextRowIndex = e.Cell.RowIndex;
        }
        else if (gridView.SelectedCells.Count > 0)
        {
          DataGridViewCell cell = gridView.SelectedCells[gridView.SelectedCells.Count - 1];
          contextColumnIndex = cell.ColumnIndex;
          contextRowIndex = cell.RowIndex;
        }
        else
        {
          contextColumnIndex = -1;
          contextRowIndex = -1;
        }
      }
    }

    private int gridView_AdditionalColumnWidth(DataGridViewColumn column)
    {
      int additionalColumnWidth = 0;
      if (column.Index >= 0)
        if (model.has_column_filter(column.Index))
          additionalColumnWidth += columnHeaderFilterImage.Size.Width + 2;
      return additionalColumnWidth;
    }

    private void gridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
    {
      if ((0 <= e.RowIndex) || (-1 == e.ColumnIndex))
        return;

      if (!model.has_column_filter(e.ColumnIndex))
        return;

      e.PaintBackground(e.ClipBounds, true);

      Rectangle glyphRect = new Rectangle(new Point(0, 0), columnHeaderFilterImage.Size);
      glyphRect.X = e.CellBounds.Right - glyphRect.Width - 2;
      if (gridView.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection != SortOrder.None)
        glyphRect.X -= glyphRect.Width;
      if (glyphRect.X < 0)
        glyphRect.X = 0;
      glyphRect.Y = e.ClipBounds.Top + (e.ClipBounds.Height - glyphRect.Height) / 2;
      if (glyphRect.Y < 0)
        glyphRect.Y = 0;

      Rectangle contentRect = e.ClipBounds;
      if (contentRect.Width > (e.CellBounds.Width - glyphRect.Width))
        contentRect.Width = e.CellBounds.Width - glyphRect.Width;
      e.PaintContent(contentRect);
      e.Graphics.DrawImageUnscaledAndClipped(columnHeaderFilterImage, glyphRect);

      e.Handled = true;
    }

    #endregion

    #region record set manipulations

    public void RefreshRecords()
    {
      model.refresh();
    }

    public void DiscardChanges()
    {
      gridView.CancelEdit();
      model.rollback();
    }

    public void SaveChanges()
    {
      // Apply any pending changes in the grid before writing them to model.
      gridView.EndEdit();
      model.apply_changes();
    }

    public void ToggleGridWrapMode()
    {
      gridView.WrapMode = !gridView.WrapMode;
    }

    public void RecordNavigateFirst()
    {
      gridView.SetRowSelected(0);
    }

    public void RecordNavigateBack()
    {
      if (contextRowIndex == -1)
        return;
      int rowIndex = contextRowIndex - 1;
      if (rowIndex >= 0)
        gridView.SetRowSelected(rowIndex);
    }

    public void RecordNavigateNext()
    {
      if (contextRowIndex == -1)
        return;
      int rowIndex = contextRowIndex + 1;
      if (rowIndex < gridView.RowCount)
        gridView.SetRowSelected(rowIndex);
    }

    public void RecordNavigateLast()
    {
      gridView.SetRowSelected(gridView.RowCount - 1);
    }

    public void EditSelectedRecord()
    {
      if (gridView.SelectedCells.Count > 0)
        gridView.BeginEdit(false);
    }

    public void AddNewRecord()
    {
      RecordNavigateLast();
      if (gridView.Columns.Count > 0)
        gridView.BeginEdit(false);
    }

    public void DeleteCurrentRecord()
    {
      deleteRowsMenuItem_Click(null, null);
    }

    public void DeleteSelectedRecords(List<int> rows, int column)
    {
      deleteRowsMenuItem_Click(null, null);
    }

    public void SortAscending()
    {
      if (contextColumnIndex == -1)
        return;
      int direction = (gridView.Columns[contextColumnIndex].HeaderCell.SortGlyphDirection == SortOrder.Ascending ? 0 : 1);
      SortByColumn(contextColumnIndex, direction, true);
    }

    public void SortDescending()
    {
      if (contextColumnIndex == -1)
        return;
      int direction = (gridView.Columns[contextColumnIndex].HeaderCell.SortGlyphDirection == SortOrder.Descending ? 0 : -1);
      SortByColumn(contextColumnIndex, direction, true);
    }

    #endregion

  }
}
