/*=========================================================================

  Program:   WXDialog - wxWidgets X-platform GUI Front-End for CMake
  Module:    $RCSfile: PropertyList.cpp,v $
  Language:  C++
  Date:      $Date: 2005/06/30 19:54:14 $
  Version:   $Revision: 1.1 $

  Author:    Jorgen Bodde

  Copyright (c) 2002 Kitware, Inc., Insight Consortium.  All rights reserved.
  See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even 
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/

/* for compilers that support precompilation
   includes "wx/wx.h" */

#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#include "PropertyList.h"
#include "app_resources.h"

#include "../cmCacheManager.h"
#include "../cmSystemTools.h"
#include "../cmake.h"

BEGIN_EVENT_TABLE( wxPropertyList, wxGrid )
    EVT_GRID_CELL_LEFT_CLICK( wxPropertyList::OnSelectCell )
    EVT_GRID_CELL_CHANGE( wxPropertyList::OnCellChange )
    EVT_GRID_CMD_CELL_RIGHT_CLICK( wxID_ANY, wxPropertyList::OnCellPopup )
    EVT_MENU ( ID_CACHE_DELETE, wxPropertyList::OnDeleteCache )
    EVT_MENU ( ID_CACHE_IGNORE, wxPropertyList::OnIgnoreCache )
    EVT_MENU ( ID_CACHE_BROWSE, wxPropertyList::OnBrowseItem )
    EVT_SIZE ( wxPropertyList::OnSizeGrid )
    EVT_CHAR ( wxPropertyList::OnKeyPressed )
END_EVENT_TABLE()

#if 0

// ----------------------------------------------------------------------------
// wxGridCellPathEditor
// ----------------------------------------------------------------------------

wxGridCellPathEditor::wxGridCellPathEditor()
{
    m_maxChars = 0;
}

void wxGridCellPathEditor::Create(wxWindow* parent,
                                  wxWindowID id,
                                  wxEvtHandler* evtHandler)
{   
    m_control = new wxTextCtrl(parent, id, wxEmptyString,
                               wxDefaultPosition, wxDefaultSize
#if defined(__WXMSW__)
                               , wxTE_PROCESS_TAB | wxTE_AUTO_SCROLL
#endif
                              );
//
//  m_button = new wxButton(parent, id+1, _("..."), 
//                          wxDefaultPosition, wxDefaultSize);
//

    // set max length allowed in the textctrl, if the parameter was set
    if (m_maxChars != 0)
    {
        ((wxTextCtrl*)m_control)->SetMaxLength(m_maxChars);
    }

    wxGridCellEditor::Create(parent, id, evtHandler);
}

void wxGridCellPathEditor::PaintBackground(const wxRect& WXUNUSED(rectCell),
                                           wxGridCellAttr * WXUNUSED(attr))
{
    // as we fill the entire client area, don't do anything here to minimize
    // flicker
}

void wxGridCellPathEditor::SetSize(const wxRect& rectOrig)
{
    wxRect rect(rectOrig);

    // Make the edit control large enough to allow for internal
    // margins
    //
    // TODO: remove this if the text ctrl sizing is improved esp. for
    // unix
    //
#if defined(__WXGTK__)
    if (rect.x != 0)
    {
        rect.x += 1;
        rect.y += 1;
        rect.width -= 1;
        rect.height -= 1;
    }
#else // !GTK
    int extra_x = ( rect.x > 2 )? 2 : 1;

// MB: treat MSW separately here otherwise the caret doesn't show
// when the editor is in the first row.
#if defined(__WXMSW__)
    int extra_y = 2;
#else
    int extra_y = ( rect.y > 2 )? 2 : 1;
#endif // MSW

#if defined(__WXMOTIF__)
    extra_x *= 2;
    extra_y *= 2;
#endif
    rect.SetLeft( wxMax(0, rect.x - extra_x) );
    rect.SetTop( wxMax(0, rect.y - extra_y) );
    rect.SetRight( rect.GetRight() + 2*extra_x );
    rect.SetBottom( rect.GetBottom() + 2*extra_y );
#endif // GTK/!GTK

    wxGridCellEditor::SetSize(rect);
}

void wxGridCellPathEditor::BeginEdit(int row, int col, wxGrid* grid)
{
    wxASSERT_MSG(m_control,
                 wxT("The wxGridCellEditor must be Created first!"));

    m_startValue = grid->GetTable()->GetValue(row, col);

    DoBeginEdit(m_startValue);
}

void wxGridCellPathEditor::DoBeginEdit(const wxString& startValue)
{
    Text()->SetValue(startValue);
    Text()->SetInsertionPointEnd();
    Text()->SetSelection(-1,-1);
    Text()->SetFocus();
}

bool wxGridCellPathEditor::EndEdit(int row, int col,
                                   wxGrid* grid)
{
    wxASSERT_MSG(m_control,
                 wxT("The wxGridCellEditor must be Created first!"));

    bool changed = false;
    wxString value = Text()->GetValue();
    if (value != m_startValue)
        changed = true;

    if (changed)
        grid->GetTable()->SetValue(row, col, value);

    m_startValue = wxEmptyString;
    // No point in setting the text of the hidden control
    //Text()->SetValue(m_startValue);

    return changed;
}


void wxGridCellPathEditor::Reset()
{
    wxASSERT_MSG(m_control,
                 wxT("The wxGridCellEditor must be Created first!"));

    DoReset(m_startValue);
}

void wxGridCellPathEditor::DoReset(const wxString& startValue)
{
    Text()->SetValue(startValue);
    Text()->SetInsertionPointEnd();
}

bool wxGridCellPathEditor::IsAcceptedKey(wxKeyEvent& event)
{
    return wxGridCellEditor::IsAcceptedKey(event);
}

void wxGridCellPathEditor::StartingKey(wxKeyEvent& event)
{
    // Since this is now happening in the EVT_CHAR event EmulateKeyPress is no
    // longer an appropriate way to get the character into the text control.
    // Do it ourselves instead.  We know that if we get this far that we have
    // a valid character, so not a whole lot of testing needs to be done.

    wxTextCtrl* tc = Text();
    wxChar ch;
    long pos;
    
#if wxUSE_UNICODE
    ch = event.GetUnicodeKey();
    if (ch <= 127)
        ch = (wxChar)event.GetKeyCode();
#else
    ch = (wxChar)event.GetKeyCode();
#endif
    switch (ch)
    {
        case WXK_DELETE:
            // delete the character at the cursor
            pos = tc->GetInsertionPoint();
            if (pos < tc->GetLastPosition())
                tc->Remove(pos, pos+1);
            break;

        case WXK_BACK:
            // delete the character before the cursor
            pos = tc->GetInsertionPoint();
            if (pos > 0)
                tc->Remove(pos-1, pos);
            break;

        default:
            tc->WriteText(ch);
            break;
    }
}

void wxGridCellPathEditor::HandleReturn( wxKeyEvent &event )
{
#if defined(__WXMOTIF__) || defined(__WXGTK__)
    // wxMotif needs a little extra help...
    size_t pos = (size_t)( Text()->GetInsertionPoint() );
    wxString s( Text()->GetValue() );
    s = s.Left(pos) + wxT("\n") + s.Mid(pos);
    Text()->SetValue(s);
    Text()->SetInsertionPoint( pos );
#else
    // the other ports can handle a Return key press
    //
    event.Skip();
#endif
}

void wxGridCellPathEditor::SetParameters(const wxString& params)
{
    if ( !params )
    {
        // reset to default
        m_maxChars = 0;
    }
    else
    {
        long tmp;
        if ( !params.ToLong(&tmp) )
        {
            wxLogDebug(_T("Invalid wxGridCellPathEditor parameter string '%s' ignored"), params.c_str());
        }
        else
        {
            m_maxChars = (size_t)tmp;
        }
    }
}

// return the value in the text control
wxString wxGridCellPathEditor::GetValue() const
{
  return Text()->GetValue();
}

#endif

/////////////////////////////////////////////////////////////////////////////
// wxPropertyItem

// returns true when this property item is a filepath
bool wxPropertyItem::IsFilePath()
{
    return m_nItemType == wxPropertyList::FILE;
}

// returns true when this property item is a dir path
bool wxPropertyItem::IsDirPath()
{
    return m_nItemType == wxPropertyList::PATH;
}

/////////////////////////////////////////////////////////////////////////////
// wxPropertyList

wxPropertyList::~wxPropertyList()
{
    WX_CLEAR_ARRAY(m_PropertyItems);
}

int wxPropertyList::AddItem(const wxString &txt)
{
    // TODO: Add the item to the grid!
    
    //int nIndex = AddString(txt);
    //return nIndex;

    return 0;
}

// order = 0 sorted (not supported yet)
// order = 1 add to top
// order = 2 add to bottom
int wxPropertyList::AddPropItem(wxPropertyItem* pItem, int order)
{
    m_PropertyItems.Add(pItem);
    if(pItem->GetAdvanced() && ! m_ShowAdvanced)
        return 0;

    // disable in progress editing
    HideControls();
        
    return AddPropertyToGrid(pItem, order);
}

int wxPropertyList::AddPropertyToGrid(wxPropertyItem *pItem, int order)
{
    int row = 0;
    if(order == 1)
        InsertRows(0,1);
    else 
    {
        AppendRows(1);    
        row = GetNumberRows() - 1;
    }
    
    // initialise the type of renderer
    if(pItem->GetItemType() == wxPropertyList::CHECKBOX)
    {
        SetCellRenderer(row, 1, new wxGridCellBoolRenderer);
        SetCellEditor(row, 1, new wxGridCellBoolEditor);
    }

#ifdef __LINUX__
    // fix to make sure scrollbars are drawn properly
    wxGrid::AdjustScrollbars();
#endif
    
    // the property display is read only
    UpdatePropertyItem(pItem, row);
    return row;
}

void wxPropertyList::AddProperty(const char* name, const char* value, const char* helpString,
                                int type, const char* comboItems, bool reverseOrder, bool advanced)
{ 
    wxPropertyItem* pItem = 0; 
   
    // add or update the property item
    for(size_t i = 0; i < m_PropertyItems.Count(); i++)
    {
        if(m_PropertyItems[i]->GetPropName().IsSameAs(name))
        {
            pItem = m_PropertyItems[i];
            if(!pItem->GetCurValue().IsSameAs(value))
            {
                pItem->SetCurValue(value);
                pItem->SetHelpString(helpString);
                pItem->SetAdvanced(advanced);
            
                // update the property item
                int row = FindProperty(pItem);
                if(row != -1)
                    UpdatePropertyItem(pItem, row);         
            }
            return;
        }
    }

    // if it is not found, then create a new one
    if(!pItem)
    {
        pItem = new wxPropertyItem(name, value, helpString, type, comboItems);
        pItem->SetAdvanced(advanced);
  
        AddPropItem(pItem, 1);
    }
}

void wxPropertyList::UpdateGridView()
{
    // make sure all items are shown, remove items that should not be shown
    bool keepItem;
    int row;
    for(size_t i = 0; i < m_PropertyItems.Count(); i++)
    {
        // to begin with, does this item fit the query?
        keepItem = m_strQuery.IsEmpty() || (m_PropertyItems[i]->GetPropName().Find(m_strQuery) != -1);
        if(keepItem)
        {
            // when advanced items are allowed to be shown, keep when ok
            if(!m_ShowAdvanced)
                keepItem = !m_PropertyItems[i]->GetAdvanced();
        }

        // find the item, if not present but keep is true, add, if 
        // present but keep is false, remove
        row = -1;
        for(size_t j = 0; j < (size_t)GetNumberRows(); j++)
        {
            if(m_PropertyItems[i]->GetPropName().IsSameAs(GetCellValue(j, 0)))
            {
                row = j;
                break;
            }
        }

        if(row == -1 && keepItem)
            AddPropertyToGrid(m_PropertyItems[i], (m_ShowAdvanced ? 2 : 0));                
        else if(row != -1 && !keepItem)
            DeleteRows(row, 1);
    }

#ifdef __LINUX__
    // fix to make sure scrollbars are drawn properly
    wxGrid::AdjustScrollbars();
#endif
}

void wxPropertyList::HideControls()
{
    DisableCellEditControl();
}

void wxPropertyList::RemoveProperty(wxPropertyItem *pItem)
{
    HideControls();
  
    // look for property in grid, delete it when present        
    for(size_t j = 0; j < (size_t)GetNumberRows(); j++)
    {
        if(pItem->GetPropName().IsSameAs(GetCellValue(j, 0), false))
        {
            DeleteRows(j, 1);                   
            
#ifdef __LINUX__
            // fix to make sure scrollbars are drawn properly
            wxGrid::AdjustScrollbars();
#endif
             break;
        }
    }

    // delete the item from the list
    m_PropertyItems.Remove(pItem);
    delete pItem;
}

wxPropertyItem *wxPropertyList::FindPropertyByName(const wxString &name)
{
    for(size_t i = 0; i < m_PropertyItems.Count(); i++)
    {
        // we have an advanced item, go through table and if not present, show it
        if(m_PropertyItems[i]->GetPropName().IsSameAs(name, true))
            return m_PropertyItems[i];
    }

    return 0;   
}

/**
void wxPropertyList::OnIgnore()
{
  if(m_curSel == -1 || this->GetCount() <= 0)
    {
    return;
    }
  wxPropertyItem* pItem = (wxPropertyItem*) GetItemDataPtr(m_curSel);
  pItem->m_curValue = "IGNORE";
  InvalidateList();
}
*/

/**
void wxPropertyList::OnDelete()
{ 
  if(m_curSel == -1 || this->GetCount() <= 0)
    {
    return;
    }
  wxPropertyItem* pItem = (wxPropertyItem*) GetItemDataPtr(m_curSel);
  m_CMakeSetupDialog->GetCMakeInstance()->GetCacheManager()->RemoveCacheEntry(pItem->m_propName);
  m_PropertyItems.erase(pItem);
  delete pItem; 
  this->DeleteString(m_curSel);
  this->HideControls();
  this->SetTopIndex(0);
  InvalidateList();
}
*/

/**
void wxPropertyList::OnHelp()
{ 
  if(m_curSel == -1 || this->GetCount() <= 0)
    {
    return;
    }
  wxPropertyItem* pItem = (wxPropertyItem*) GetItemDataPtr(m_curSel);
  MessageBox(pItem->m_HelpString, pItem->m_propName, MB_OK|MB_ICONINFORMATION);
}
*/

void wxPropertyList::RemoveAll()
{   
    WX_CLEAR_ARRAY(m_PropertyItems);
    m_generatedProjects = false;

    if(GetNumberRows() > 0) 
        DeleteRows(0, GetNumberRows());
    
    m_strQuery.Empty();

#ifdef __LINUX__
    // fix to make sure scrollbars are drawn properly
    wxGrid::AdjustScrollbars();
#endif
}

void wxPropertyList::ShowAdvanced()
{
    // set flag in the control
    m_ShowAdvanced = true; 
    UpdateGridView();
}


void wxPropertyList::HideAdvanced()
{
    // set flag in the control
    m_ShowAdvanced = false; 
    UpdateGridView();   
}

int wxPropertyList::FindProperty(wxPropertyItem *pItem)
{
    if(GetNumberRows() > 0 && pItem != 0) 
    {
        // find the property the traditional way
        for(size_t j = 0; j < (size_t)GetNumberRows(); j++)
        {
            if(pItem->GetPropName().IsSameAs(GetCellValue(j, 0)))
                return j;
        }
    }

    return -1;
}

wxPropertyItem *wxPropertyList::GetPropertyItemFromRow(int row)
{
    if(row < GetNumberRows() && row >= 0) 
    {
        wxString str = GetCellValue(row, 0);
        // find the property the traditional way
        for(size_t i = 0; i < (size_t)m_PropertyItems.Count(); i++)
        {
            if(m_PropertyItems[i]->GetPropName().IsSameAs(str))
                return m_PropertyItems[i];
        }
    }

    return 0;   
}

bool wxPropertyList::UpdatePropertyItem(wxPropertyItem *pItem, int row)
{
    wxCHECK(row < GetNumberRows(), false);

    // reflect the property's state to match the grid row

    SetReadOnly(row, 0);
    // TODO: Make this a UpdatePropItem where ADVANCED, and new edit values are reflected
    SetCellValue(row,0, pItem->GetPropName());
    
    // boolean renderer
    if(pItem->GetItemType() == wxPropertyList::CHECKBOX)
    {
        // translate ON or TRUE (case insensitive to a checkbox)
        if(pItem->GetCurValue().IsSameAs(wxT("ON"), false) ||
           pItem->GetCurValue().IsSameAs(wxT("TRUE"), false))
            SetCellValue(row, 1, wxT("1"));
        else
            SetCellValue(row, 1, wxT("0"));
    }
    else
    {
        // for normal path values, give bold in cell when
        // the NOTFOUND is present, for emphasis
        wxString str = pItem->GetPropName() + wxT("-NOTFOUND");     
        if(pItem->GetCurValue().IsSameAs(str))
        {
            wxFont fnt = GetCellFont(row, 0);       
            fnt.SetWeight(wxFONTWEIGHT_BOLD);
            SetCellFont(row, 1, fnt);
        }
        else
            SetCellFont(row, 1, GetCellFont(row, 0));

        SetCellValue(row,1, pItem->GetCurValue());
    }

    if(pItem->GetCurValue().IsSameAs("IGNORE"))
    {
        // ignored cell is completely dimmed
        wxColour col(192,192,192);
        SetCellTextColour(row, 1, col);
    }
    else
    {
        // we colour paths blue, filenames green, all else black
        wxColour col;
        if(pItem->IsDirPath())
            col.Set(0,0,255);
        else if(pItem->IsFilePath())
            col.Set(0,128,0);
        else
            col = GetCellTextColour(row, 0);

        SetCellTextColour(row, 1, col);
    }

    if(pItem->GetNewValue())
    {
        // new cell is red
        wxColour col(255,100,100);
        SetCellBackgroundColour(row, 0, col);
    }
    else
    {
        // old cell is grey
        wxColour col(192, 192, 192);
        SetCellBackgroundColour(row, 0, col);
    }

    return true;
}

void wxPropertyList::OnSelectCell( wxGridEvent& event )
{
    this->SetFocus();
    event.Skip();
}

void wxPropertyList::OnCellChange( wxGridEvent& event )
{
    int row = event.GetRow();

    wxPropertyItem *pItem = GetPropertyItemFromRow(row);
    if(pItem && row != wxNOT_FOUND)
    {
        // write propery back, and set as new
        pItem->SetNewValue(true);
        
        // write back bool
        if(pItem->GetItemType() == CHECKBOX)
        {
            if(GetCellValue(row, 1).IsSameAs("1"))
                pItem->SetCurValue("ON");
            else
                pItem->SetCurValue("OFF");
        }
        else
            pItem->SetCurValue(GetCellValue(row, 1));
    
        UpdatePropertyItem(pItem, row);
        event.Skip();
    }
}

void wxPropertyList::OnCellPopup( wxGridEvent& event )
{
    wxPoint pt;
    int row = event.GetRow();
    
    //pt = ::wxGetMousePosition();
    //ScreenToClient(pt);

    //row = YToRow(pt.y);
    if(row != wxNOT_FOUND)
    {
        wxPropertyItem *pItem = GetPropertyItemFromRow(row);
    
        if(pItem)
        {
            // select the row first if already in selection, don't
            // this will clear the previous selection
            if(!IsInSelection(row, 0))
                SelectRow(row);

            // show popup menu
            wxMenu *menu = AppResources::CreatePopupMenu();
            
            // enable when it is browsable, and selected one only
            wxMenuItem *item = menu->FindItem(ID_CACHE_BROWSE);
            if(item)
                item->Enable(IsSelectedItemBrowsable());
            
            PopupMenu(menu);

            delete menu;
        }
    }
}

void wxPropertyList::OnIgnoreCache( wxCommandEvent& event )
{
    HideControls();
  
    // ignore all selected items
    for(size_t i = 0; i < (size_t)GetNumberRows(); i++)
    {
        if(IsInSelection(i, 0))
        {
            wxPropertyItem *pItem = GetPropertyItemFromRow(i);      
            if(pItem)
            {
                pItem->SetCurValue("IGNORE");
                UpdatePropertyItem(pItem, i);
            }
        }
    }
}

void wxPropertyList::OnDeleteCache( wxCommandEvent& event )
{
    HideControls();
  
    // convert selections to prop items
    wxArrayPtrVoid items;
    for(size_t i = 0; i < (size_t)GetNumberRows(); i++)
    {
        // if selected, query for removal
        if(IsInSelection(i, 0))
        {
            wxPropertyItem *pItem = GetPropertyItemFromRow(i);      
            if(pItem)
                items.Add((void *)pItem);
        }
    }

    // now delete all prop items in cells
    for(size_t i = 0; i < items.Count(); i++)
        RemoveProperty((wxPropertyItem *)items[i]);
}

void wxPropertyList::OnBrowseItem( wxCommandEvent& event )
{
    BrowseSelectedItem();
}

bool wxPropertyList::IsSelectedItemBrowsable(int row)
{
    // when there is only one selection, and our current item
    // is browsable, make sure it can be selected.
    wxPropertyItem *pItem = 0;
    
    size_t count = 0;
    for(size_t i = 0; i < (size_t)GetNumberRows() && (count < 2); i++)
    {
        if(IsInSelection(i, 0))
        {
            if(!pItem)
                pItem = GetPropertyItemFromRow(i);
            count ++;
        }
    }

    // if we found nothing, take row (because the event EVT_GRID_CELL_SELECTED 
    // deselects the cells first before selecting the new one again
    if(row != -1 && !pItem)
    {
        pItem = GetPropertyItemFromRow(row);
        count ++; // needed because of next loop
    }

    // only one item allowed to select
    if(pItem && count == 1)
    {
        if(pItem)
            return pItem->IsDirPath() || pItem->IsFilePath();   
    }
    
    return false;
}


void wxPropertyList::BrowseSelectedItem()
{
    HideControls();
  
    for(size_t i = 0; i < (size_t)GetNumberRows(); i++)
    {
        if(IsInSelection(i, 0))
        {
            // browse for file or directory
            wxPropertyItem *pItem = GetPropertyItemFromRow(i);      
            if(pItem)
            {
                wxString title;
                wxString str = pItem->GetPropName() + _("-NOTFOUND");       
                if(pItem->GetCurValue().IsSameAs(str, true))
                    str.Empty();
                else
                    str = pItem->GetCurValue();

                // browse the directory path
                
                if(pItem->IsDirPath())
                {
                    title = _("Select path for ") + pItem->GetPropName();
                    str = ::wxDirSelector(title, str, 0, wxDefaultPosition, this);
                }
                else if(pItem->IsFilePath())
                {
                    title = _("Select file for ") + pItem->GetPropName();
                    str = ::wxFileSelector(title, str, _(""), _(""), _(MC_DEFAULT_WILDCARD), wxFILE_MUST_EXIST, this);
                }
                else
                    str.Empty();
                
                if(!str.IsEmpty())
                {
                    pItem->SetCurValue(str.c_str());
                    UpdatePropertyItem(pItem, i);
                }
            }
            
            // only allow one item to browse
            break;
        }
    }
}

void wxPropertyList::OnSizeGrid( wxSizeEvent &event )
{
    int width, height;
    
    // make sure the grid's cells are equally adjusted
    GetClientSize(&width, &height);
    SetDefaultColSize(width / 2, true);

    wxGrid::AdjustScrollbars();
}

void wxPropertyList::OnKeyPressed( wxKeyEvent &event )
{
    event.Skip();
}
