/*

  xmunipack - preview icon list


  Copyright © 2009 - 2013, 2019 F.Hroch (hroch@physics.muni.cz)

  This file is part of Munipack.

  Munipack 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 3 of the License, or
  (at your option) any later version.

  Munipack 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 Munipack.  If not, see <http://www.gnu.org/licenses/>.



  Notes. -- outdated?

  wxListCtrl implementation has some strange features:

  * use wxListView as a base class does not works (failed indexes
      in AddThumb)

  * use of DeleteItem take down full application

  * compilation fails when MuniItemList is derived from wxItemList

*/

#include "xmunipack.h"
#include <algorithm>
#include <vector>
#include <wx/wx.h>
#include <wx/utils.h>
#include <wx/list.h>
#include <wx/listctrl.h>
#include <wx/thread.h>
#include <wx/imaglist.h>
#include <wx/filename.h>
#include <wx/wupdlock.h>
#include <wx/clipbrd.h>
#include <wx/dnd.h>
#include <queue>

using namespace std;

// shared variables for threads
static std::queue<wxString> s_namelist;
static wxMutex s_mutex(wxMUTEX_DEFAULT);
// the shared variables are not too elegant, but its works
// (passing of boths as argumenst to classes causes some crashes


// keys for sorting
static wxString sort_key, sort_dateobs, sort_object, sort_filter,
  sort_exposure;


static bool CmpName(const FitsMeta& meta1, const FitsMeta& meta2)
{
  wxString name1 = meta1.GetName();
  wxString name2 = meta2.GetName();
  return wxStrcmp(name1,name2) < 0;
}

static bool CmpObject(const FitsMeta& meta1, const FitsMeta& meta2)
{
  wxString key1 = meta1.GetKeys(sort_object);
  wxString key2 = meta2.GetKeys(sort_object);
  return wxStrcmp(key1,key2) < 0;
}

static bool CmpFilter(const FitsMeta& meta1, const FitsMeta& meta2)
{
  wxString key1 = meta1.GetFilter(sort_filter);
  wxString key2 = meta2.GetFilter(sort_filter);
  return wxStrcmp(key1,key2) < 0;
}

static bool CmpKey(const FitsMeta& meta1, const FitsMeta& meta2)
{
  wxString key1 = meta1.GetKeys(sort_key);
  wxString key2 = meta2.GetKeys(sort_key);
  return wxStrcmp(key1,key2) < 0;
}

static bool CmpExposure(const FitsMeta& meta1, const FitsMeta& meta2)
{
  wxString key1 = meta1.GetKeys(sort_exposure);
  wxString key2 = meta2.GetKeys(sort_exposure);
  double e1,e2;
  key1.ToDouble(&e1);
  key2.ToDouble(&e2);
  return e1 < e2;
}

static bool CmpSize(const FitsMeta& meta1, const FitsMeta& meta2)
{
  wxULongLong s1 = meta1.GetSize();
  wxULongLong s2 = meta2.GetSize();
  return s1 < s2;
}

static bool CmpJulday(const FitsMeta& meta1, const FitsMeta& meta2)
{
  FitsTime t1(meta1.GetDateobs(sort_dateobs));
  FitsTime t2(meta2.GetDateobs(sort_dateobs));
  return t1.Jd() < t2.Jd();
}



// -- DropClass

// add files drop

class xDropTarget: public wxDropTarget
{
public:
  xDropTarget(wxWindow *w): target(w) {
    SetDataObject(new MuniDataObjectMeta);
  }

  wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def)
  {
    if ( !GetData() )
      return wxDragNone;

    MuniDataObjectMeta *dobj = (MuniDataObjectMeta *) GetDataObject();
    wxASSERT(dobj);
    vector<FitsMeta> slist = dobj->GetMetafitses();
    if( slist.size() > 0 )
      static_cast<MuniListCtrl *>(target)->PasteMeta(slist);

    return wxDragCopy;
  }

private:

  wxWindow *target;
};




// ---- MuniListIcon

MuniListIcon::MuniListIcon(wxWindow *w, MuniConfig *c):
  MuniListCtrl(w,wxLC_ICON,c),hitem(-1)
{
  int size = iSize();
  thumbs = new wxImageList(size,size,false,0);
  SetImageList(thumbs,wxIMAGE_LIST_NORMAL);

  Bind(wxEVT_MOTION,&MuniListIcon::OnMouse,this);
  Bind(wxEVT_IDLE,&MuniListIcon::OnIdle,this);
}

// All items are added during the Idle time.
void MuniListIcon::OnIdle(wxIdleEvent& event)
{
  if( ! metas.empty() ) {

    //    if( ! wxIsBusy() )
    //    wxBeginBusyCursor();

    const FitsMeta meta = metas.front();

    wxString l(LabelFits(meta,config->browser_labeltype));
    int size = iSize();
    wxImage ic(meta.GetIcon());
    double r = iRatio();
    ic.Rescale(ic.GetWidth()*r,ic.GetHeight()*r);
    wxImage thumb(MuniIcon::BrowserIcon(ic,size,size,l,GetBackgroundColour()));
    // prepare hightlighted items ??

    int j = thumbs->GetImageCount();
    thumbs->Add(wxBitmap(thumb));
    InsertItem(j,j);

    metas.pop();
    EnsureVisible(j);
    event.RequestMore();
  }
  else {

    //  if( wxIsBusy() )
    //    wxEndBusyCursor();
  }
}

int MuniListIcon::iSize() const
{
  return int(double(config->icon_size)*iRatio());
}

double MuniListIcon::iRatio() const
{
  return pow(0.75,double(config->icon_zoom));
}


void MuniListIcon::Update()
{
  DeleteAllMeta();

  int s = iSize();
  wxImageList *th = new wxImageList(s,s,false,0);
  SetImageList(th,wxIMAGE_LIST_NORMAL);
  delete thumbs;
  thumbs = th;

  hitem = -1;

  for(vector<FitsMeta>::const_iterator i=flist.begin();i!=flist.end();++i)
    AddItem(*i);
}


wxString MuniListIcon::LabelFits(const FitsMeta& f, int type)
{
  switch (type) {
  case ID_LABEL_FILENAME: return f.GetName();
  case ID_LABEL_OBJECT:   return f.GetKeys(config->fits_key_object);
  case ID_LABEL_DATEOBS:  return f.GetDateobs(config->fits_key_dateobs);
  case ID_LABEL_FILTER:   return f.GetFilter(config->fits_key_filter);
  case ID_LABEL_EXPOSURE: return f.GetExposure(config->fits_key_exptime);
  case ID_LABEL_KEY:      return f.GetKeys(config->browser_labelkey);
  case ID_LABEL_NO:       return wxEmptyString;
  default:                return wxEmptyString;
  }
}

void MuniListIcon::OnMouse(wxMouseEvent& event)
{
  // highlight focused item

  wxPoint p = event.GetPosition();
  bool found = false;

  for(long i = 0; i < GetItemCount(); i++) {

    wxRect r;
    if( GetItemRect(i,r) && r.Contains(p) ) {
      found = true;
      if( hitem != i ) {
	if( hitem >= 0 )
	  HightLightItem(hitem,false);
	hitem = i;
	HightLightItem(hitem,true);
      }
      break;
    }
  }
  if( ! found && hitem > 0 )
    HightLightItem(hitem,false);

  event.Skip();
}


void MuniListIcon::HightLightItem(long j, bool enable)
{
  int i = j;
  if( enable ) {

    himage = thumbs->GetBitmap(i).ConvertToImage();

    //    if( !( 0 <= j && j < flist.size()) ) return;
    //    wxLogDebug("%d %d",(int)j,(int)flist.size());
    wxASSERT(0 <= j && j < (long) flist.size());

    FitsMeta meta(flist[j]);
    wxString l(LabelFits(meta,config->browser_labeltype));
    int size = iSize();
    wxImage image(meta.GetIcon());
    double r = iRatio();
    image.Rescale(image.GetWidth()*r,image.GetHeight()*r);

    int npix = 3*image.GetWidth()*image.GetHeight();
    unsigned char *rgb = (unsigned char *) malloc(npix);
    const unsigned char *orig = image.GetData();
    for(int l = 0; l < npix; l++) {
      int x = 32 + orig[l];
      rgb[l] = x < 255 ? x : 255;
    }
    image.SetData(rgb);

    thumbs->Replace(i,wxBitmap(MuniIcon::BrowserIcon(image,size,size,l,GetBackgroundColour())));
  }
  else
    thumbs->Replace(i,wxBitmap(himage));

  RefreshItem(j);
}


// ---- MuniListList

MuniListList::MuniListList(wxWindow *w, MuniConfig *c):
  MuniListCtrl(w,wxLC_REPORT|wxLC_VRULES,c)
{
  thumbs = new wxImageList(config->icon_small,config->icon_small,false,0);
  SetImageList(thumbs,wxIMAGE_LIST_SMALL);

  InsertColumn(0,"Name");
  InsertColumn(1,"Object");
  InsertColumn(2,"Date of Observation [UT]");
  InsertColumn(3,"Filter");
  InsertColumn(4,"Exposure [sec]");
  InsertColumn(5,"Size");

  Bind(wxEVT_IDLE,&MuniListList::OnIdle,this);
}

void MuniListList::Update()
{
  DeleteAllMeta();

  wxImageList *th = new wxImageList(config->icon_small,config->icon_small,false,0);
  SetImageList(th,wxIMAGE_LIST_SMALL);
  delete thumbs;
  thumbs = th;

  for(vector<FitsMeta>::const_iterator f=flist.begin();f!=flist.end();++f)
    AddItem(*f);

  SetColumnWidth(0,wxLIST_AUTOSIZE);
  SetColumnWidth(1,wxLIST_AUTOSIZE);
  SetColumnWidth(2,wxLIST_AUTOSIZE);
  SetColumnWidth(3,wxLIST_AUTOSIZE);
  SetColumnWidth(4,wxLIST_AUTOSIZE);
  SetColumnWidth(5,wxLIST_AUTOSIZE);

}

void MuniListList::OnIdle(wxIdleEvent& event)
{
  if( ! metas.empty() ) {

    if( ! wxIsBusy() )
      wxBeginBusyCursor();

    const FitsMeta meta = metas.front();

    int index = thumbs->GetImageCount();

    thumbs->Add(wxBitmap(MuniIcon::ListIcon(meta.GetIcon(),config->icon_small)));

    wxColour colour = index % 2 == 0 ? *wxWHITE : *wxLIGHT_GREY;


    long i = InsertItem(index,meta.GetName(),index);


    SetItem(i,1,meta.GetKeys(config->fits_key_object));
    SetItem(i,2,meta.GetDateobs(config->fits_key_dateobs));
    SetItem(i,3,meta.GetFilter(config->fits_key_filter));
    SetItem(i,4,meta.GetExposure(config->fits_key_exptime));
    SetItem(i,5,meta.GetHumanReadableSize());
    SetItemBackgroundColour(i,colour);

    SetColumnWidth(0,wxLIST_AUTOSIZE);
    SetColumnWidth(1,wxLIST_AUTOSIZE);
    SetColumnWidth(2,wxLIST_AUTOSIZE);
    SetColumnWidth(3,wxLIST_AUTOSIZE);
    SetColumnWidth(4,wxLIST_AUTOSIZE);
    SetColumnWidth(5,wxLIST_AUTOSIZE);

    metas.pop();
    EnsureVisible(i);
    event.RequestMore();
  }
  else {

    if( wxIsBusy() )
      wxEndBusyCursor();
  }

}


// ---- MuniListCtrl


MuniListCtrl::MuniListCtrl(wxWindow *w, long style, MuniConfig *c):
  wxListCtrl(w,wxID_ANY,wxDefaultPosition,wxDefaultSize,style),
  config(c),thumbs(0),metarender(0)
{
  SetDropTarget(new xDropTarget(this));

  Bind(wxEVT_MOTION,&MuniListCtrl::OnMouse,this);
  Bind(wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK,&MuniListCtrl::OnRightClick,this);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniListCtrl::OnView,this,ID_VIEW);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniListCtrl::OnCut,this,wxID_CUT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniListCtrl::OnCopy,this,wxID_COPY);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniListCtrl::OnPaste,this,wxID_PASTE);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniListCtrl::OnProperties,this,wxID_PROPERTIES);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniListCtrl::OnLabel,this,ID_LABEL_FILENAME,ID_LABEL_NO);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniListCtrl::OnSort,this,ID_SORT_FILENAME,ID_SORT_KEY);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniListCtrl::OnReverse,this,ID_SORT_REVERSE);
  //  Bind(wxEVT_COMMAND_THREAD,&MuniListCtrl::OnMetaOpenFinish,this,ID_MRENDER_FIN);
  Bind(wxEVT_THREAD,&MuniListCtrl::OnMetaRenderFinish,this,ID_MRENDER_FIN);
  //  Bind(EVT_META_OPEN,&MuniListCtrl::OnMetaLoadFinish,this,ID_ARCHIVE_FINISH);
  Bind(EVT_FITS_OPEN,&MuniListCtrl::OnFitsOpen,this);
  //  Bind(EVT_FITS_OPEN,&MuniListCtrl::OnMetaRender,this,ID_MRENDER);
  //  Bind(EVT_META_OPEN,&MuniListCtrl::OnMetaLoad,this,ID_ARCHIVE);
  //  Bind(EVT_META_OPEN,&MuniListCtrl::OnMetaLoad,this);
}

MuniListCtrl::~MuniListCtrl()
{
  // stop threads gracefully
  if( s_mutex.Lock() == wxMUTEX_NO_ERROR ) {
    while( ! s_namelist.empty() )
      s_namelist.pop();
    s_mutex.Unlock();
  }

  StopMetaRender();

  DeleteAllMeta();
  delete thumbs;
}



void MuniListCtrl::Update()
{
  // be pure virtual ?
}


vector<FitsMeta> MuniListCtrl::GetClipboard() const
{
  vector<FitsMeta> slist;

  wxDataFormat dfm;
  dfm.SetId("MUNIPACK_METAFITS");

  if( wxTheClipboard->IsOpened() )
    return slist;
  // this test is required by UI updating in browser

  if( wxTheClipboard->Open() ) {

    if( wxTheClipboard->IsSupported(dfm) ) {

      MuniDataObjectMeta data;
      if( wxTheClipboard->GetData(data) )
	slist = data.GetMetafitses();
      else
	wxLogDebug("MuniList::EditPaste: failed to paste ");

    }
    wxTheClipboard->Close();
  }

  /*
  if (wxTheClipboard->Open() ) {
    if( wxTheClipboard->IsSupported(wxDF_FILENAME) ) {

      wxFileDataObject data;
      if( wxTheClipboard->GetData(data) ) {
	wxArrayString f(data.GetFilenames());
	for(size_t i = 0; i < f.GetCount(); i++)
	  wxLogDebug(f[i]);
      }
    }
    wxTheClipboard->Close();
  }
  */
  return slist;
}

void MuniListCtrl::SetClipboard(const vector<FitsMeta>& slist)
{
  if( ! (slist.size() > 0) ) return;

  if( wxTheClipboard->Open() ) {
    MuniDataObjectMeta *fs = new MuniDataObjectMeta(slist);
    wxTheClipboard->SetData(fs);
    wxTheClipboard->Close();
  }
}

void MuniListCtrl::OnFitsOpen(FitsOpenEvent& event)
{
  MuniIcon micon(event.fits,config->display_colorspace,config->cdatafile,
		 config->default_icon,config->table_icon,config->head_icon,
		 config->icon_size);
  // it would be better move it to Idle?
  PasteMeta(FitsMeta(event.fits,micon.GetIcon(),micon.GetList()));
  //  PasteMeta(event.meta);
}

void MuniListCtrl::OnMetaOpenFinish(wxCommandEvent& event)
{
  Sorter();
}

void MuniListCtrl::OnMetaLoad(MetaOpenEvent& event)
{
  AddMeta(event.meta);
}

void MuniListCtrl::OnMetaLoadFinish(MetaOpenEvent& event)
{
  Sorter();
}

void MuniListCtrl::AddFits(const wxArrayString& files)
{
  if( files.GetCount() == 0 ) return;

  for(size_t l = 0; l < files.GetCount(); l++) {
    if( s_mutex.Lock() == wxMUTEX_NO_ERROR ) {
      s_namelist.push(files[l]);
      s_mutex.Unlock();
    }
  }

  if( metarender == 0 ) {
    wxLogDebug("MuniListCtrl::AddFits: Creating thread.......");
    metarender = new MetaRender(this,config);
    wxThreadError code = metarender->Create();
    wxASSERT(code == wxTHREAD_NO_ERROR);
    metarender->Run();
  }
}

void MuniListCtrl::OnMetaRenderFinish(wxThreadEvent& event)
{
  wxLogDebug("MuniListCtrl::OnMetaRenderFinish");

  /*
  menuView->Enable(wxID_STOP,false);
  tstop = tbot->RemoveTool(wxID_STOP);
  if( errmsg.GetCount() > 0 )
    tbot->AddTool(twarn);
  tbot->Realize();
  */
}

void MuniListCtrl::AddMeta(const FitsMeta& meta)
{
  wxASSERT(meta.IsOk());
  flist.push_back(meta);
  AddItem(meta);
}

void MuniListCtrl::AddMeta(const vector<FitsMeta>& slist)
{
  for(vector<FitsMeta>::const_iterator i=slist.begin(); i!=slist.end();++i)
    AddMeta(*i);
}

void MuniListCtrl::AddItem(const FitsMeta& meta)
{
  //  wxLogDebug(meta.GetName());

  if( metas.empty() )
    wxWakeUpIdle();
  metas.push(meta);
}

void MuniListCtrl::SetMeta(const vector<FitsMeta>& slist)
{
  flist = slist;
  Sorter();
}

vector<FitsMeta> MuniListCtrl::GetAllMeta() const
{
  return flist;
}


void MuniListCtrl::DeleteMeta(const vector<FitsMeta>& ls)
{
  for(vector<FitsMeta>::const_iterator l = ls.begin(); l != ls.end(); ++l) {

    vector<FitsMeta>::iterator f = flist.begin();
    while( f != flist.end() ) {
      if( f->GetFullPath() == l->GetFullPath() ) {  // same path removes all !!!, compare all???
	dellist.push_back(*f);
	f = flist.erase(f);
      }
      else
	++f;
    }

    vector<FitsMeta>::iterator a = addlist.begin();
    while( a != addlist.end() ) {
      if( a->GetFullPath() == l->GetFullPath() ) {  // same path removes all !!!
	a = addlist.erase(a);
      }
      else
	++a;
    }
  }

  Update();
}

void MuniListCtrl::OnRightClick(wxListEvent& event)
{
  if( event.GetIndex() >= 0 ) {

    wxMenu *menuMark = new wxMenu;
    menuMark->AppendRadioItem(ID_MARK_SCI,"Scientific (light)");
    menuMark->AppendRadioItem(ID_MARK_FLAT,"Flat field");
    menuMark->AppendRadioItem(ID_MARK_DARK,"Dark frame");
    menuMark->AppendRadioItem(ID_MARK_BIAS,"Bias frame");

    wxMenu popup;
    popup.Append(ID_VIEW,"&View");
    popup.AppendSeparator();
    popup.Append(wxID_CUT);
    popup.Append(wxID_COPY);
    popup.AppendSeparator();
    popup.AppendSubMenu(menuMark,"&Mark as");
    popup.AppendSeparator();
    popup.Append(wxID_PROPERTIES);

    menuMark->Enable(ID_MARK_SCI,false);
    menuMark->Enable(ID_MARK_FLAT,false);
    menuMark->Enable(ID_MARK_DARK,false);
    menuMark->Enable(ID_MARK_BIAS,false);
    popup.Enable(wxID_PROPERTIES,GetSelectedItemCount() == 1);

    PopupMenu(&popup);
  }
  else {

    wxMenu popup;
    popup.Append(wxID_PASTE);
    popup.Enable(wxID_PASTE,GetClipboard().size() > 0);
    PopupMenu(&popup);

  }

}


void MuniListCtrl::OnView(wxCommandEvent& WXUNUSED(event))
{
  wxQueueEvent(this,new wxListEvent(wxEVT_COMMAND_LIST_ITEM_ACTIVATED));
}

void MuniListCtrl::OnProperties(wxCommandEvent& WXUNUSED(event))
{
  vector<FitsMeta> ls = GetSelectedMeta();
  FitsMeta f = ls[0];
  if( f.IsOk() ) {
    MuniFileProperties *w = new MuniFileProperties(this,f,config);
    w->Show();
  }
}

vector<unsigned int> MuniListCtrl::GetSelectedIndex() const
{
  vector<unsigned int> ls;

  if( GetSelectedItemCount() >= 1 ) {
    long selected_item = -1;
    while( (selected_item = GetNextItem(selected_item,wxLIST_NEXT_ALL,wxLIST_STATE_SELECTED)) != -1 ) {
      wxASSERT(0 <= selected_item && selected_item < (long)flist.size());
      ls.push_back(selected_item);
    }
  }
  return ls;
}

vector<FitsMeta> MuniListCtrl::GetSelectedMeta() const
{
  vector<FitsMeta> ls;

  if( GetSelectedItemCount() >= 1 ) {
    long selected_item = -1;
    while( (selected_item = GetNextItem(selected_item,wxLIST_NEXT_ALL,wxLIST_STATE_SELECTED)) != -1 ) {
      wxASSERT(0 <= selected_item && selected_item < (long)flist.size());
      ls.push_back(flist[selected_item]);
    }
  }
  return ls;

}

vector<FitsMeta> MuniListCtrl::GetAddedMeta() const
{
  return addlist;
}

vector<FitsMeta> MuniListCtrl::GetDeletedMeta() const
{
  return dellist;
}

void MuniListCtrl::SelectAll()
{
  for(long i = 0; i < GetItemCount(); i++)
    SetItemState(i,wxLIST_STATE_SELECTED,wxLIST_STATE_SELECTED);
}

void MuniListCtrl::SelectItemLast()
{
  long i = GetItemCount();
  if( i > 0 )
    SetItemState(i-1,wxLIST_STATE_SELECTED,wxLIST_STATE_SELECTED);
}

void MuniListCtrl::SelectItem(long i)
{
  wxASSERT(0 <= i && i < GetItemCount() && GetItemCount() ==long(flist.size()));
  SetItemState(i,wxLIST_STATE_SELECTED,wxLIST_STATE_SELECTED);
}

void MuniListCtrl::SelectItemRelative(long dir)
{
  long n = GetNextItem(-1,wxLIST_NEXT_ALL,wxLIST_STATE_SELECTED);
  if( n > -1 ) {
    long i = n + dir;
    if( 0 <= i && i < GetItemCount() ) {
      DeSelectAll();
      SetItemState(i,wxLIST_STATE_SELECTED,wxLIST_STATE_SELECTED);
      wxQueueEvent(this,new wxListEvent(wxEVT_COMMAND_LIST_ITEM_ACTIVATED));
    }
  }
}


void MuniListCtrl::DeSelectAll()
{
  for(long i = 0; i < GetItemCount(); i++)
    SetItemState(i,0,wxLIST_STATE_SELECTED);
}


bool MuniListCtrl::DeleteAllMeta()
{
  wxListCtrl::DeleteAllItems();
  wxASSERT(thumbs);
  thumbs->RemoveAll();
  wxASSERT(thumbs->GetImageCount() == 0 && GetItemCount() == 0);
  return true;
}


void MuniListCtrl::OnLabel(wxCommandEvent& event)
{
  Label(event.GetId());
}

void MuniListCtrl::Label(int id)
{
  config->browser_labeltype = id;
  Update();
}

void MuniListCtrl::OnReverse(wxCommandEvent& event)
{
  Reverse(event.IsChecked());
}

void MuniListCtrl::Reverse(bool b)
{
  config->browser_reverse = b;
  Sorter();
}

void MuniListCtrl::OnSort(wxCommandEvent& event)
{
  Sort(event.GetId());
}

void MuniListCtrl::Sort(int id)
{
  config->browser_sorttype = id;

  sort_key = config->browser_labelkey;
  sort_object = config->fits_key_object;
  sort_dateobs = config->fits_key_dateobs;
  sort_filter = config->fits_key_filter;
  sort_exposure = config->fits_key_exptime;

  Sorter();
}

void MuniListCtrl::Sorter()
{
  switch (config->browser_sorttype) {
  case ID_SORT_FILENAME: sort(flist.begin(),flist.end(),CmpName); break;
  case ID_SORT_OBJECT:   sort(flist.begin(),flist.end(),CmpObject); break;
  case ID_SORT_DATEOBS:  sort(flist.begin(),flist.end(),CmpJulday); break;
  case ID_SORT_FILTER:   sort(flist.begin(),flist.end(),CmpFilter); break;
  case ID_SORT_EXPOSURE: sort(flist.begin(),flist.end(),CmpExposure); break;
  case ID_SORT_SIZE:     sort(flist.begin(),flist.end(),CmpSize);  break;
  case ID_SORT_KEY:      sort(flist.begin(),flist.end(),CmpKey);  break;
  }

  if( config->browser_reverse )
    reverse(flist.begin(),flist.end());

  Update();
}




void MuniListCtrl::OnMouse(wxMouseEvent& event)
{
  if( event.Dragging() ) {
    //    wxLogDebug(_("MuniListCtrls::OnMouse dragging"));

    vector<FitsMeta> slist = GetSelectedMeta();
    if( slist.size() > 0 ) {
      MuniDataObjectMeta fs(slist);

      /*
      FitsMeta f(slist[0]);
      wxImage ico(f.GetIcon());
      wxIconOrCursor ic = wxDROP_ICON(
      */

      wxDropSource dragSource(fs,this);
      dragSource.DoDragDrop();
    }
  }

  event.Skip();
}



void MuniListCtrl::PasteMeta(const FitsMeta& meta)
{
  addlist.push_back(meta);

  // remove from delete list
  /*
  int l = -1;
  for(vector<FitsMeta>::iterator k=dellist.begin(); k != dellist.end(); ++k) {
    if( k->GetFullPath() == meta.GetFullPath() ) {
      l = k - dellist.begin();
      break;
    }
  }
  if( l >= 0 )
    dellist.erase(dellist.begin()+l);
  */

  AddMeta(meta);
}

void MuniListCtrl::PasteMeta(const vector<FitsMeta>& slist)
{
  for(vector<FitsMeta>::const_iterator i = slist.begin(); i != slist.end(); ++i)
    PasteMeta(*i);
}

void MuniListCtrl::OnCopy(wxCommandEvent& event)
{
  Copy();
}

void MuniListCtrl::OnCut(wxCommandEvent& event)
{
  Cut();
}

void MuniListCtrl::OnPaste(wxCommandEvent& event)
{
  Paste();
}

void MuniListCtrl::Cut()
{
  vector<FitsMeta> slist = GetSelectedMeta();
  SetClipboard(slist);
  DeleteMeta(slist);
}

void MuniListCtrl::Copy()
{
  vector<FitsMeta> slist = GetSelectedMeta();
  SetClipboard(slist);
}

void MuniListCtrl::Paste()
{
  PasteMeta(GetClipboard());
}

void MuniListCtrl::StopMetaRender()
{
  {
    wxCriticalSectionLocker enter(metarenderCS);
    if( metarender )
      metarender->Delete();
  }

  while(true) {
    {
      wxCriticalSectionLocker enter(metarenderCS);
      if( ! metarender ) break;
    }
    wxThread::This()->Sleep(1);
  }

}


// ----  MetaRender

MetaRender::MetaRender(wxEvtHandler *eh, MuniConfig *c):
  wxThread(wxTHREAD_DETACHED),handler(eh),config(c)
{
  wxASSERT(handler && config);
}

MetaRender::~MetaRender()
{
  wxASSERT(static_cast<MuniListCtrl *>(handler));
  wxCriticalSectionLocker enter(static_cast<MuniListCtrl *>(handler)->metarenderCS);
  static_cast<MuniListCtrl *>(handler)->metarender = 0;
  wxLogDebug("MetaRender::~MetaRender()");
}

void *MetaRender::Entry()
{
  while( ! s_namelist.empty() ) {

    //    if( TestDestroy() ) return (wxThread::ExitCode) 0;

    if( s_mutex.Lock() == wxMUTEX_NO_ERROR ) {

      if( TestDestroy() ) {
	while( ! s_namelist.empty() )
	  s_namelist.pop();
	break;
      }

      wxString filename = s_namelist.front();
      s_namelist.pop();
      s_mutex.Unlock();

      FitsFile fits(filename);

      /*
      FitsMeta meta;
      if( fits.Status() ) {
	MuniIcon micon(fits,config->display_colorspace,config->cdatafile,
		       config->default_icon,config->table_icon,config->head_icon,
		       config->icon_size);
	meta = FitsMeta(fits,micon.GetIcon(),micon.GetList());
      }
      */
      FitsOpenEvent ev(EVT_FITS_OPEN,ID_MRENDER);
      ev.filename = filename;
      ev.fits = fits;
      //      ev.meta = meta;
      wxQueueEvent(handler,ev.Clone());

    }
  }

  wxQueueEvent(handler,new wxThreadEvent(wxEVT_COMMAND_THREAD,ID_MRENDER_FIN));

  return (wxThread::ExitCode) 0;
}
