/* DaResView.cpp */
/* part of DAta REScue dares */

/* DAta REScue (C) 2002 Oliver Diedrich, odi@ct.heise.de */
/* (C) 2005 c't magazine for computer technology, Heise Zeitschriften Verlag */
/* GNU Public License (GPL), see www.gnu.org */

extern "C" {
#include "dares.h"
#include "global.h"
}

extern char image[];                    // name of image to work on
extern char save_path[];                // path for recovered files
extern struct one_type all_types[];     // all file types
extern int n_types;                     // # different file types
extern unsigned long block_nr;          // # of read blocks
extern unsigned long block_size;        // block size, from superblock


#include <sys/types.h>

#if defined(DOS)
#include <io.h>
//#include <sys\stat.h>
//#ifndef S_ISDIR
// #define S_ISREG(m)  (((m)&_S_IFMT) == _S_IFREG)
// #define S_ISDIR(x) ((x) & _S_IFDIR )
//#endif
#include <mallocx.h>
#include <stdlibx.h>
#include <stdiox.h>
#include <unistdx.h>
#else
#include <stdio.h>
#include <unistd.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>


#include "daresview.h"

#include <qapplication.h>
#include <qeventloop.h>
#include <qmessagebox.h>
#include <qinputdialog.h>
#include <qvbox.h>
#include <qlabel.h>
#include <qtextedit.h>
#include <qfont.h>
#include <qfile.h>
#include <qprocess.h>
#include <qobjectlist.h>
#include <qpopupmenu.h>
#include <qheader.h>


// -----------------------------------------------------------------

QString RawFile::content()
{
  QString sContent;
  if( mDaresFileInfo ){
    FILE *fs =NULL;
    int   fsi=0;
    int i, j;
    bool bError = false;

    // open the image
    if( ASC == mDaresFileInfo->filetype )
      bError = NULL == (fs = fopen(image, "r"));
    else
      bError = -1 == (fsi = open(image, O_RDONLY|O_BINARY));

    if ( bError )
    {
      sContent.append(QString("\n\nERROR: Could not open image file\n\nfopen(%1): %2")
                      .arg( image ).arg( strerror(errno) ) );
      return sContent;
    }

    // seek to start of file
    if( ASC == mDaresFileInfo->filetype ){
      if (-1 ==
#if defined(DOS)
      fseek(
#else
      fseeko(
#endif
      fs, ((off_t) mDaresFileInfo->ext->start * (off_t) block_size), SEEK_SET))
      {
        fclose(fs);
        sContent.append(QString("\n\nERROR: Could not seek to start of file\n\nfseek(%1): %2")
                        .arg( image ).arg( strerror(errno) ) );
        return sContent;
      }
    }else{
      if (-1 == lseek(fsi, ((off_t) mDaresFileInfo->ext->start * (off_t) block_size), SEEK_SET))
      {
        sContent.append(QString("\n\nERROR: Could not seek to start of file\n\nlseek(%1, %2): %3")
                        .arg( image ).arg( mDaresFileInfo->ext->start * block_size ).arg( strerror(errno) ) );
        close(fsi);
        return sContent;
      }
    }

    // read file data
    if( ASC == mDaresFileInfo->filetype ){
      unsigned char str[255];
      long n = 0;
      const long nRead = QMIN( mDaresFileInfo->size, 4096 );
      unsigned char *c;
      for( i=0; i<1000 && n<nRead; ++i )
      {
        memset(str, 0, 255);
        if (NULL == fgets((char *)str, 254, fs))
        {
          fclose(fs);
          if ( ! i )      // 1st line -> nothing read at all
          {
            sContent.append(QString("\n\nERROR: Could not read file data from block %1").arg( mDaresFileInfo->ext->start ) );
          }
          return sContent;
        }
        const long nStrLen = strlen((char *)str);
        c = str;
        for(j=0; j<nStrLen && n<nRead; ++j, ++c)
        {
          if ('\r' == *c) *c = '\0';
          if ('\n' == *c) *c = '\0';
          if ( (*c >= 0x80) && (*c < 0xa0) ) *c = '·';  // non-ISO extended ASCII can't be printed
          ++n;
          if( n >= nRead ) // 1 block read and displayed
          {
            *c = '\0';
          }
        }
        sContent.append( QString("%1\n").arg( QCString( (const char*) (&str[0]) ) ) );
      }
    }else{
      const int maxRead = QMIN(mDaresFileInfo->size, 4096);
      int nRead;
      unsigned char buf[4096];
      unsigned char *c;
      char str[255];
      char* s;
      int n = 0;
      nRead = read(fsi, buf, maxRead);
      if (-1 == nRead)
      {
        sContent.append(QString("\n\nERROR: Could not read file data from file.\n\nRead(%1): %2")
                        .arg( image ).arg( strerror(errno) ) );
        close(fsi);
        return sContent;
      }
      close(fsi);
      c = buf;
      for (i=0; n<nRead; ++i)
      {
        memset(str, 0, 80);
        s = str;
        sprintf(s, "%04X   ", i*16);     /* offset */
        s+=7;

        for (j=0; j<16; j++, n++, c++)               /* hex code display */
        {
          if (8 == j)
          {
            sprintf(s, "  ");
            s+=2;
          }
          if( n<nRead )
            sprintf(s, "%02X ", *c);
          else
            sprintf(s, "   ");
          s+=3;
        }
        sprintf(s, "   ");
        s +=  3;
        c -= 16;
        n -= 16;

        for(j=0; j<16 && n<nRead; ++j, ++n, ++c)                    /* character display */
        {
          sprintf(s, "%c", ((*c<32)||(*c>126)) ? '.' : *c);
          ++s;
        }
        sContent.append( QString("%1\n").arg( QCString( (const char*) (&str[0]) ) ) );
      }
    }

    // close file
    if( ASC == mDaresFileInfo->filetype )
      fclose(fs);
    else
      close(fsi);
  }
  return sContent;
}


QString RawFile::info()
{
  QString sInfo;
  if( mDaresFileInfo ){
    unsigned long size = mDaresFileInfo->size;
    sInfo = QString(" %1 file starting at block %2, size is %3 %4.")
              .arg( ASC == mDaresFileInfo->filetype ? "Text" : "Binary" )
              .arg( mDaresFileInfo->ext->start )
              .arg(
#if !defined(DOS)
              (long long)
#endif
              size > 2000000
                    ?   size/(1024*1024)
                    : ( size > 2000
                      ? size/1024
                      : size ) )
              .arg( size > 2000000
                    ?   "MByte"
                    : ( size > 2000
                      ? "KByte"
                      : "Byte" ) );
  }
  return sInfo;
}


struct one_file* RawFile::daresFileInfo()
{
  return mDaresFileInfo;
}

QCString RawFile::savedFileName()
{
  return mSavedFileName;
}


QCString RawFile::daresTypeName()
{
  return mDaresTypeName;
}


bool RawFile::hasFileInfo()
{
  if( !mDaresFileInfo )
    return false;
  return true;
}


bool RawFile::saveToFile(bool bSilentOnSuccess)
{
  if( !mDaresFileInfo )
    return false;

  QString sError;
  int result;

  char fname[1024];
  memset(fname, 0, 1024);
  result = save_file(mDaresFileInfo, mDaresTypeName.data(), fname);
  mSavedFileName = fname;

  switch( result ){
    case SAVE_FILE_CANNOT_OPEN_IMAGE:
    {
      sError = QString("can't open %1").arg(image);
      break;
    }
    case SAVE_FILE_CANNOT_OPEN_OUTFILE:
    {
      sError = QString("can't open %1").arg(mSavedFileName);
      break;
    }
    case SAVE_FILE_SEEK_ERROR:
    {
      sError = QString("seek error in %1").arg(image);
      break;
    }
    case SAVE_FILE_READ_ERROR:
    {
      sError = QString("read error in %1").arg(image);
      break;
    }
    case SAVE_FILE_WRITE_ERROR:
    {
      sError = QString("write error in %1").arg(mSavedFileName);
      break;
    }
  }

  if( 0 < result )
  {
    if( !bSilentOnSuccess )
      QMessageBox::information(qApp->desktop(), "Data Rescue",
        QString( "<u>%1</u><br>"
                "&nbsp;<br>"
                "&nbsp;<br>"
                "<b>%2 successfully saved.</b>" ).arg( VERSION ).arg( mSavedFileName ));
    return true;
  }else{
    if( sError.isEmpty() )
      sError = "(unknown error)";
    QMessageBox::critical(qApp->desktop(), "Data Rescue",
      QString( "<u>%1</u><br>"
              "&nbsp;<br>"
              "&nbsp;<br>"
              "Sorry, file could not be saved:<br>"
              "&nbsp;<br>"
              "<b>%2</b>" ).arg( VERSION ).arg( sError ));
    return false;
  }
}


// -----------------------------------------------------------------

Folder::Folder( Folder *parent, const QString &name, const RawFile& rawFile )
    : QObject( parent, name ), mFName( name ), mRawFile( rawFile )
{
    // no code here yet
}


// -----------------------------------------------------------------

FolderListItem::FolderListItem( QListView *parent, Folder *f )
    : QListViewItem( parent )
{
  myFolder = f;
  setText( 0, f->folderName() );
  if ( myFolder->children() )
    insertSubFolders( myFolder->children() );
}

FolderListItem::FolderListItem( FolderListItem *parent, FolderListItem *prev, Folder *f )
    : QListViewItem( parent, prev )
{
  myFolder = f;
  setText( 0, f->folderName() );
  if ( myFolder->children() )
    insertSubFolders( myFolder->children() );
}

FolderListItem::FolderListItem( FolderListItem *parent, Folder *f )
    : QListViewItem( parent )
{
  myFolder = f;
  setText( 0, f->folderName() );
  if ( myFolder->children() )
    insertSubFolders( myFolder->children() );
}

void FolderListItem::insertSubFolders( const QObjectList *lst )
{
  Folder *f;
  FolderListItem *prev=0;
  for ( f = ( Folder* )( ( QObjectList* )lst )->first(); f; f = ( Folder* )( ( QObjectList* )lst )->next() )
    prev = new FolderListItem( this, prev, f );
}


// -----------------------------------------------------------------

DaResView::DaResView( QWidget *parent, const char *name, int found )
    : QSplitter( Qt::Horizontal, parent, name ), mFound( found )
{
    lstFolders.setAutoDelete( TRUE );
    programs.setAutoDelete( TRUE );

    QVBox* foldersBox = new QVBox(this);

    mFolders = new QListView( foldersBox );
    mFolders->header()->setClickEnabled( FALSE );
    mFolders->setSorting( -1 );
    mFolders->setFont( QFont( "Courier", 10, QFont::Normal ) );

    initFolders();
    setupFolders();

    mFolders->setRootIsDecorated( TRUE );
    mFolders->setSelectionMode( QListView::Extended );

    QLabel* foldersLabel = new QLabel( foldersBox );
    foldersLabel->setText( "<b>Ctrl</b>+<b>V</b> view the current file,<br>"
                           "<b>Ctrl</b>+<b>S</b> saves the current file,<br>"
                           "<b>Ctrl</b>+<b>A</b> saves all selected files,<br>"
                           "<b>Ctrl</b>+<b>Q</b> quits the program." );

    setResizeMode( foldersBox, QSplitter::KeepSize );


    QVBox* fileViewBox = new QVBox(this);

    mPopUpMenu = new QPopupMenu( mFolders );
    mPopUpMenu->insertItem( QString( "View current file"       ), this, SLOT(viewCurrent()),     CTRL+Key_V );
    mPopUpMenu->insertItem( QString( "Save current file"       ), this, SLOT(saveCurrent()),     CTRL+Key_S );
    mPopUpMenu->insertItem( QString( "Save all selected files" ), this, SLOT(saveAllSelected()), CTRL+Key_A );
    mPopUpMenu->insertItem( QString( "Quit the program" ),        this, SLOT(close()),           CTRL+Key_Q );
    connect(mFolders, SIGNAL( contextMenuRequested( QListViewItem *, const QPoint& , int ) ),
            this,     SLOT(   slotRMB( QListViewItem *, const QPoint &, int ) ) );


    //QScrollView* sv = new QScrollView( fileViewBox );
    //mRawFileView = new QTextEdit( sv->viewport() );

    mRawFileView = new QTextEdit( fileViewBox );
    mRawFileView->setTextFormat( Qt::PlainText );
    mRawFileView->setFont( QFont( "Courier", 11, QFont::Normal ) );
    mRawFileView->setAlignment(  Qt::AlignTop );
    mRawFileView->setBackgroundMode( PaletteBase );
    mRawFileView->setReadOnly( true );
    mRawFileView->setWordWrap( QTextEdit::NoWrap );

    //sv->addChild(mRawFileView);

    mRawFileLabel = new QLabel( fileViewBox );
    mRawFileLabel->setText( "" );

    connect( mFolders, SIGNAL( currentChanged(    QListViewItem* ) ),
             this,     SLOT(   slotFolderChanged( QListViewItem* ) ) );

    QValueList<int> lst;
    lst.append( 270 );
    lst.append( 480 );
    setSizes( lst );
}

void DaResView::initFolders()
{
  int i = 0;
  int t = 0;
  off_t size[MAXMAGIS];
  struct one_file *f;

  const bool bFilesFound = all_types[t].first != NULL;

  if( !bFilesFound ){
    mFolders->addColumn( "Sorry, no files found." );
    return;
  }else{
    for(i=0; i<n_types; ++i)
    {
      size[i] = 0;
      f = all_types[i].first;
      while( f )
      {
        size[i] += f->size;
        f = f->next;
      }
    }
    mFolders->addColumn( QString("%1 files of %2 different types" ).arg( mFound ).arg( n_types ) );
  }

  for(t=0; t<n_types; ++t)
  {
    struct one_type& type = all_types[t];
    QString s = QString( "%1 %2 (%3/%4; %5 %6)" )
      .arg( ASC == type.first->filetype ? "<text>" : "<bin> " )
      .arg( type.name )
      .arg( type.hash - type.trunc )
      .arg( type.hash )
      .arg(
#if !defined(DOS)
            (long long)
#endif
            size[t] > 2000000
            ?   size[t]/(1024*1024)
            : ( size[t] > 2000
              ? size[t]/1024
              : size[t] ) )
      .arg( size[t] > 2000000
            ?   "MByte"
            : ( size[t] > 2000
              ? "KByte"
              : "Byte" ) );
    Folder *folder = new Folder( 0, s, RawFile( this ) );

    // add the file entries to this folder
    {
      const int nFiles = type.hash;
      struct one_file *p;
      int i;
      Folder *f2 = 0;

      for(i = 1, p = type.first; i <= nFiles && NULL != p; ++i)
      {
        QString sFile = QString("%1 Block %2: %3 %4" )
          .arg( i,
                4 )
          .arg( p->ext->start,
                8 )
          .arg(   p->size >= 100000
                ? p->size/1024
                : p->size,
                6 )
          .arg(   p->size >= 100000
                ? "KByte"
                : " Byte" );
        if( p->trunc )
          sFile.append( QString("  %1").arg( p->trunc ) );

        f2 = new Folder( folder, sFile, RawFile( this, p, type.name ) );
        p  = p->next;
      }
    }
    lstFolders.prepend( folder );
  }
}


void DaResView::setupFolders()
{
  mFolders->clear();

  for ( Folder* f = lstFolders.first(); f; f = lstFolders.next() )
    (void)new FolderListItem( mFolders, f );

  QMessageBox::information(qApp->desktop(), "Data Rescue",
    QString( "<u>%1</u><br>"
            "&nbsp;<br>"
            "&nbsp;<br>"
            "<b>Scanned %2 %3k blocks, found %4 files</b>" )
    .arg( VERSION ).arg( block_nr ).arg( block_size / 1024 ).arg( mFound ));
}

void DaResView::slotRMB( QListViewItem* i, const QPoint & point, int )
{
  if( i ){
    updateRawFileView( static_cast< FolderListItem * > ( i ) );
    mPopUpMenu->popup( point );
  }
}


bool DaResView::saveItem( QListViewItem* _item,
                          bool bSilentOnSuccess,
                          bool restoreStatusLabel )
{
//  qDebug("saving current");
  bool bOK = true;
  FolderListItem *item = static_cast< FolderListItem * > ( _item );
  if( item && item->folder() && item->folder()->rawFile().hasFileInfo() ){
    const QString sLabel = mRawFileLabel->text();
    RawFile& rawFile = item->folder()->rawFile();
    mRawFileLabel->setText( QString("&nbsp; &nbsp; <b>Saving:</b> %1")
                            .arg( rawFile.info() ) );
    mRawFileLabel->update();
    qApp->eventLoop()->processEvents( QEventLoop::ExcludeUserInput, 1000 );
    bOK = rawFile.saveToFile( bSilentOnSuccess );
    if( restoreStatusLabel )
      mRawFileLabel->setText( sLabel );
  }
  return bOK;
}


bool DaResView::hasFileInfo( QListViewItem *i )
{
  if( ! i )
    return false;
  return static_cast< FolderListItem * > ( i )->folder()->rawFile().hasFileInfo();
}


void DaResView::viewCurrent()
{
  FolderListItem *item = static_cast< FolderListItem * > ( mFolders->currentItem() );
  if( hasFileInfo( item ) ){
    const QString typeName( item->folder()->rawFile().daresTypeName() );
    bool bOk;
    const QString progName
      = QInputDialog::getText( VERSION,
                              "Enter name of application:",
                              QLineEdit::Normal,
                              programs[typeName]
                              ? *programs[typeName]
                              : QString::null,
                              &bOk,
                              this );
    if( bOk && !progName.isEmpty() ){
      if( saveItem( item, true ) ){
        QString fileName = item->folder()->rawFile().savedFileName();
        if( !fileName.isEmpty() ){
          QProcess* proc = new QProcess( this );

          proc->addArgument( progName );
          proc->addArgument( fileName );
          if( proc->start() ){
            if( programs[typeName] )
              programs.replace(typeName, new QString(progName));
            else
              programs.insert( typeName, new QString(progName));
          }else{
            QMessageBox::warning( this, "Warning!", QString("Could not start %1!").arg(progName), 0 );
          }
        }
      }
    }
  }
}


void DaResView::saveCurrent()
{
//  qDebug("saving current");
  saveItem( mFolders->currentItem(), false );
}


void DaResView::saveAllSelected()
{
//  qDebug("saving all selected");
  mRawFileView->setText( "Saving all selected:" );
  mRawFileView->append( "" );
  QListViewItemIterator it( mFolders );
  bool bOK = true;
  int iFiles=0;
  while ( bOK && it.current() ) {
    if ( it.current()->isSelected() && hasFileInfo( it.current() )  ){
      FolderListItem *item = static_cast< FolderListItem * > ( it.current() );
      if( item && item->folder() && item->folder()->rawFile().hasFileInfo() ){
        mRawFileView->append( item->folder()->rawFile().info() );
        bOK = saveItem( it.current(), true, false );
        if( bOK )
          mRawFileView->append( QString( " --> saved to %1" )
                                .arg( item->folder()->rawFile().savedFileName() ) );
        else
          mRawFileView->append( " COULD NOT BE SAVED" );
        ++iFiles;
      }
    }
    ++it;
  }
  if( bOK ){
    mRawFileLabel->setText(QString("<b> &nbsp; %1 files successfully saved.</b>" ).arg( iFiles ));
    QMessageBox::information(qApp->desktop(), "Data Rescue",
      QString( "<u>%1</u><br>"
              "&nbsp;<br>"
              "&nbsp;<br>"
              "<b>%2 files successfully saved.</b>" ).arg( VERSION ).arg( iFiles ));
  }
  slotFolderChanged( mFolders->currentItem() );
}

void DaResView::slotFolderChanged( QListViewItem *i )
{
  updateRawFileView( static_cast< FolderListItem * > ( i ) );
}


void DaResView::updateRawFileView( FolderListItem *i )
{
  FolderListItem *item = static_cast< FolderListItem * > ( i );
  if( item ){
    mRawFileView->setText( item->folder()->rawFile().content() );
    mRawFileLabel->setText( item->folder()->rawFile().info() );
  }
}
