/// DicomViewer.cpp
/** Displays DICOM header information in a wxFrame.
    todo: 20130301cdt - add ability to search
*/

#if defined(__WXGTK__) || defined(__WXMOTIF__)
  #include <wx/wx.h>
#endif

#include <wx/wxprec.h>

#include <wx/listctrl.h>
#include <wx/types.h>
#include <wx/filename.h>
#include <wx/fdrepdlg.h>
//20060823cdt
#include <wx/frame.h>
#include <wx/msgdlg.h>
#include <wx/sizer.h>
#include <wx/filedlg.h>
#include <wx/menu.h>
#include <wx/textctrl.h>

#include <vector>
#include <algorithm>

#include "DicomFile.h"
#include "DicomViewer.h"
#include "StringConvert.h"

using namespace jcs;

enum {
  MENU_FILE_EXIT = 1,
  MENU_FILE_SAVE,
  MENU_FILE_FIND,
  MENU_FILE_CSA_IMAGE,
  MENU_FILE_CSA_SERIES
};


BEGIN_EVENT_TABLE(DicomViewer, wxFrame)

EVT_MENU(MENU_FILE_SAVE, DicomViewer::OnSave)
EVT_MENU(MENU_FILE_EXIT, DicomViewer::OnQuit)
EVT_MENU(MENU_FILE_FIND, DicomViewer::ShowFindDialog)
EVT_MENU(MENU_FILE_CSA_IMAGE, DicomViewer::OnCsaImage)
EVT_MENU(MENU_FILE_CSA_SERIES, DicomViewer::OnCsaSeries)

EVT_FIND(-1, DicomViewer::OnFindDialog)
EVT_FIND_NEXT(-1, DicomViewer::OnFindDialog)
EVT_FIND_CLOSE(-1, DicomViewer::OnCloseFindDialog)

END_EVENT_TABLE()

DicomViewer::DicomViewer(wxWindow* parent, const char* filename) 
: wxFrame(parent, -1, ""), mDicomFile(filename), mDescLength(0)
{
  CreateStatusBar();

  wxMenuBar* pMenuBar = new wxMenuBar();

  wxMenu* pFileMenu = new wxMenu();
  pFileMenu->Append(MENU_FILE_SAVE, "&Save", "Save as text file");
  pFileMenu->Append(MENU_FILE_EXIT, "E&xit", "Close this window");
  pMenuBar->Append(pFileMenu, _T("&File"));
  
  std::string manufacturer;
  mDicomFile.Find("Manufacturer", manufacturer);
  std::string software;
  //mDicomFile.Find("SoftwareVersion", software);
  mDicomFile.Find("SoftwareVersions", software);
  if ((manufacturer.find("SIEMENS") != std::string::npos) &&
      (software.find("syngo") != std::string::npos)) {
    pFileMenu->Append(MENU_FILE_CSA_IMAGE, _T("&CSA Image Header"),
    _T("View CSA Image header"));
    pFileMenu->Append(MENU_FILE_CSA_SERIES, _T("CSA Series &Header"),
    _T("View CSA Series header"));
  }

  SetMenuBar(pMenuBar);

  wxBoxSizer* frameSizer = new wxBoxSizer(wxVERTICAL);

  wxString fname;
  wxString ext;
  wxFileName::SplitPath(wxString(filename, wxConvLocal), NULL, &fname, &ext);
  if (!ext.IsEmpty()) {
    fname += _T(".") + ext;
  }
  SetTitle(fname);

  mpDicomList = new wxListView(this, -1,
  wxDefaultPosition, wxSize(600, 400), wxLC_REPORT);

  InitList();

  frameSizer->Add(mpDicomList, 1, wxEXPAND);

  SetSizer(frameSizer);

  frameSizer->Fit(this);
}


///
/**
*/
void
DicomViewer::OnSave(wxCommandEvent& event)
{
  wxFileDialog* dlg = new wxFileDialog(this,
  _T("Choose a file"), _T(""), _T(""), _T("*.txt"),
  wxFD_SAVE);

  if (dlg->ShowModal() == wxID_OK) {
    wxString path = dlg->GetPath();
    std::ofstream output(path.mb_str(wxConvLocal));

    for (int i = 0; i < mpDicomList->GetItemCount(); ++i) {
      output << mGetItemText(i, 0).mb_str(wxConvLocal);
      output << "  ";
      wxString desc = mGetItemText(i, 1);
      desc.Pad(mDescLength - desc.Length());
      output << desc.mb_str(wxConvLocal);
      output << "  ";
      output << mGetItemText(i, 2).mb_str(wxConvLocal);
      output << "  ";
      wxString vl = mGetItemText(i, 3);
      vl.Pad(12 - vl.Length(), ' ', false);
      output << vl.mb_str(wxConvLocal);
      output << "  ";
      output << mGetItemText(i, 4).mb_str(wxConvLocal);
      output << std::endl;
    }

    // Could save CSA headers too if we really wanted

    output.close();
  }
  dlg->Destroy();
}


///
/**
*/
void
DicomViewer::OnCsaImage(wxCommandEvent& event)
{
  std::string header;
  mDicomFile.GetCSAImageHeader(header);
  CSAViewer* viewer = new CSAViewer(this, this->GetTitle(), header);
  viewer->Show();
}


///
/**
*/
void
DicomViewer::OnCsaSeries(wxCommandEvent& event)
{
  std::string header;
  mDicomFile.GetCSASeriesHeader(header);
  CSAViewer* viewer = new CSAViewer(this, this->GetTitle(), header);
  viewer->Show();
}


///
/**
*/
void
DicomViewer::OnQuit(wxCommandEvent& event)
{
  Close();
}


///
/**
*/
wxString
DicomViewer::mGetItemText(long index, int column) const
{
  wxListItem item;
  item.SetId(index);
  item.SetColumn(column);
  item.SetMask(wxLIST_MASK_TEXT);
  mpDicomList->GetItem(item);
  return item.GetText();
}


///
/**
*/
void
DicomViewer::InitList()
{
  wxBusyCursor busy;
  mpDicomList->InsertColumn(0, _T("Tag"));
  mpDicomList->InsertColumn(1, _T("Description"));
  mpDicomList->InsertColumn(2, _T("VR"));
  mpDicomList->InsertColumn(3, _T("VL"));
  mpDicomList->InsertColumn(4, _T("Value"));

  std::vector<DicomElement> elements = mDicomFile.DataDump();

  std::vector<DicomElement>::iterator it = elements.begin();
  std::vector<DicomElement>::iterator it_end = elements.end();
  for (;it != it_end; it++) {
    AddElement(*it);
  }

  mpDicomList->SetColumnWidth(0, wxLIST_AUTOSIZE);
  mpDicomList->SetColumnWidth(1, wxLIST_AUTOSIZE);
  mpDicomList->SetColumnWidth(2, wxLIST_AUTOSIZE);
  mpDicomList->SetColumnWidth(3, wxLIST_AUTOSIZE);
  mpDicomList->SetColumnWidth(4, wxLIST_AUTOSIZE);
}


///
/**
*/
int
DicomViewer::AddElement(DicomElement& e)
{
  wxString buf;

  int i = mpDicomList->GetItemCount();

  mpDicomList->InsertItem(i, wxString::Format( _T("(%04x, %04x)"), e.tag.group, e.tag.element ));

  mpDicomList->SetItem(i, 1, wxString(e.description.c_str(), wxConvLocal));
  mpDicomList->SetItem(i, 2, wxString(e.vr.c_str(), wxConvLocal));
  mpDicomList->SetItem(i, 3, wxString::Format(_T("%d"), e.value_length));
  // Todo: 20130221cdt - add formatting of value according to VR
  if (e.vr.compare("AT") == 0) { // Formatted as attribute tag.
    char delim[] = "/";
    int loc = e.value.find(delim);
    std::string group = e.value.substr(0, loc);
    std::string element = e.value.substr(loc + 1);
    mpDicomList->SetItem(i, 4, wxString::Format(_T("(%.4x,%.4x)"), jcs::stoi(group), jcs::stoi(element)));
  }
  else {
    // The use of From8BitData presumes Unicode encoding, true in the case of
    // (0x0008,0x0005) being ISO_IR 100 (ISO-8859-1).
    // On OS X, SL, compile fails unless mb_str() is used - 20131127cdt
    mpDicomList->SetItem(i, 4, wxString(wxString::From8BitData(e.value.c_str()).wc_str(), wxConvLocal));
  }
  mDescLength = std::max(mDescLength, e.description.size());

  return 1;
}


///
/**
*/
CSAViewer::CSAViewer(wxWindow* parent, const wxString& title, 
const std::string& header) 
: wxFrame(parent, -1, title)
{
  wxBoxSizer* frameSizer = new wxBoxSizer(wxVERTICAL);

  mTextCtrl = new wxTextCtrl(this, -1, _T(""),
  wxDefaultPosition, wxSize(400,400), wxTE_MULTILINE | wxTE_READONLY |
  wxTE_RICH);
/*
  if (!mTextCtrl->GetFont().IsFixedWidth()) {
    wxFont fixed;
    fixed.SetFamily(wxMODERN);
    mTextCtrl->SetFont(fixed);
  }
*/
  frameSizer->Add(mTextCtrl, 1, wxEXPAND);

  AddHeader(header);

  SetSizer(frameSizer);

  frameSizer->Fit(this);
}


///
/**
*/
void
CSAViewer::AddHeader(const std::string& header)
{
  wxBusyCursor busy;

  std::stringstream ss;
  ss.write(header.c_str(), header.size());
  ss.ignore(16);
  while(ss.good()) AddNext(ss);
}


///
/**
*/
void
CSAViewer::AddNext(std::istream& in)
{
  char char_buffer[64];

  in.read(char_buffer, 64);
  if (!in.good()) {
    return;
  }
  mTextCtrl->AppendText(wxString(char_buffer, wxConvLocal));
  (*mTextCtrl) << _T("\n");

  int n_values;
  in.read(reinterpret_cast<char*> (&n_values), 4);
  in.read(char_buffer, 4);

  wxInt32 intval1;
  wxInt32 intval2;
  wxInt32 intval3;
  wxInt32 intval4;

  in.read(reinterpret_cast<char*> (&intval1), 4);
  in.read(reinterpret_cast<char*> (&intval2), 4);
  in.read(reinterpret_cast<char*> (&intval3), 4);
  if (intval3 == 205) {
    (*mTextCtrl) << _T("\n");
    return;
  }

  for (;;) {
    
    in.read(reinterpret_cast<char*> (&intval1), 4);
    in.read(reinterpret_cast<char*> (&intval2), 4);
    in.read(reinterpret_cast<char*> (&intval3), 4);
    in.read(reinterpret_cast<char*> (&intval4), 4);

    if (intval3 != 77 || !in.good()) { break; }

    unsigned int bytes_read = 0;
    std::stringstream test;
    for(int i = 0; i < intval4; ++i) {
      //(*mTextCtrl) << in.get(); // not sure why this doesn't work
      test.put(in.get());
      bytes_read += in.gcount();
      if (!in.good()) {
        break;
      }
    }
    (*mTextCtrl) << wxString(test.str().c_str(), wxConvLocal);
    mTextCtrl->AppendText(_T("\n"));

    while (bytes_read % 4) {
      in.get();
      bytes_read += in.gcount();
      if (!in.good()) { break; }
    }
  }

  while (intval1 == 0 && intval3 == 205 && in.good()) { 
    if (in.good()) { in.read(reinterpret_cast<char*> (&intval1), 4); }
    if (in.good()) { in.read(reinterpret_cast<char*> (&intval2), 4); }
    if (in.good()) { in.read(reinterpret_cast<char*> (&intval3), 4); }
    if (in.good()) { in.read(reinterpret_cast<char*> (&intval4), 4); }
  } 

  in.seekg(-16, std::ios::cur);
  (*mTextCtrl) << _T("\n");
}


///
/**
*/
void DicomViewer::ShowFindDialog( wxCommandEvent& WXUNUSED(event) )
{

  wxFindReplaceData data(wxFR_DOWN);
  wxFindReplaceDialog* dlgFind = new wxFindReplaceDialog(this, &data, 
  _T("Find text"), wxFR_NOWHOLEWORD);

  dlgFind->Show(TRUE);
}


///
/**
*/
void DicomViewer::OnFindDialog(wxFindDialogEvent& event)
{
  wxString search = event.GetFindString();

  bool down = (event.GetFlags() & wxFR_DOWN);
  bool match_case = ((event.GetFlags() & wxFR_MATCHCASE) != 0);

  int start_index = 0;
  wxEventType type = event.GetEventType();

  int n_rows = mpDicomList->GetItemCount();
  if ( event.GetEventType() == wxEVT_COMMAND_FIND_NEXT ) {
    start_index = mpDicomList->GetFocusedItem();
    if (down) {
      ++start_index;
      if (start_index >= n_rows) {
        wxMessageBox(_T("At end of list"), _T("Message"), wxOK | wxCENTRE, this);
        return;
      }
    }
    else {
      --start_index;
      if (start_index < 0) {
        wxMessageBox(_T("At start of list"), _T("Message"), wxOK | wxCENTRE, this);
        return;
      }
    }
  }

  int n_columns = 5;
  bool found = false;

  wxBusyCursor busy;

  if (down) {
    for (int i = start_index; i < n_rows; ++i) {
      if (found) { break; }
      for (int j = 0; j < n_columns; ++j) {
        wxString itemText = mGetItemText(i, j);

        if (!match_case) {
          found = itemText.Upper().Contains(search.Upper());
        }
        else {
          found = itemText.Contains(search);
        }

        if (found) {
          // deselect all
          for (int k = 0; k < n_rows; ++k) {
            mpDicomList->Select(k, false);
          }

          SetFocus();
          mpDicomList->Focus(i);
          mpDicomList->Select(i);
          mpDicomList->EnsureVisible(i);
          break;
        }
      }
    }
  }
  else {
    for (int i = start_index; i >= n_rows; --i) {
      if (found) { break; }
      for (int j = 0; j < n_columns; ++j) {
        wxString itemText = mGetItemText(i, j);

        if (!match_case) { itemText.MakeUpper(); }

        if (itemText.Contains(search)) {
          
          // deselect all
          for (int k = 0; k < n_rows; ++k) 
          mpDicomList->Select(k, false);

          SetFocus();
          mpDicomList->Focus(i);
          mpDicomList->Select(i);
          mpDicomList->EnsureVisible(i);
          found = true;
          break;
        }
      }
    }
  }

  if (!found) {
    wxMessageBox(wxString::Format(_T("%s not found"), search.c_str()), 
    _T("Message"), wxOK | wxCENTRE, this);
  }
}


///
/**
*/
void
DicomViewer::OnCloseFindDialog(wxFindDialogEvent& event)
{
  wxFindReplaceDialog *dlg = event.GetDialog();
  dlg->Destroy();
}
