/* display.c */
/* part of DAta REScue dares: ncurses frontend */

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

#define _LARGEFILE_SOURCE
#define _LARGEFILE64_SOURCE
#define _FILE_OFFSET_BITS 64

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

#include "../../include/dares.h"
#include "../../include/global.h"

#define WITH_CURSES

#ifdef WITH_CURSES
  #include <curses.h>
#endif

#define START_DUMP LINES/2              // first line of file dump area

/* global variables in dares.c */

extern struct one_type all_types[];     // all file types
extern int n_types;                     // # different file types
extern unsigned long block_size;        // block size, from superblock
extern char image[];                    // name of image to work on
extern char save_path[];                // path for recovered files
extern FILE *log_file;                  // log file
extern char logname[];                  // name of log file (-l); default: LOG_FILE




#ifdef WITH_CURSES
void file_list(struct one_file *f[], int n, int start, int highlight)
/* displays part of the list of files of one type in f[] with n elements */
/* starting with element start, highlighting element highlight */
{
  int i, t, j;
  int len;
  char str[MAX_TYPE];
  
  for (i=1, t=start; i<(LINES/2); i++, t++)
  {
    if (t==highlight) attron(A_REVERSE);
    if (i == (LINES/2)-1) attron(A_UNDERLINE);
    if (t<n) sprintf(str, "%4d %8lu: %6lu %s", t+1, f[t]->ext->start, 
                     f[t]->size >= 100000 ? f[t]->size/1024 : f[t]->size,
                     f[t]->size >= 100000 ? "KByte" : " Byte");
    else sprintf(str, " ");
    len = strlen(str);
    mvaddstr(i, 0, str);
    if (t==highlight) attroff(A_REVERSE);
    if ( (t<n) && (f[t]->trunc) )
    {
      sprintf(str, "  %s", f[t]->trunc);
      addstr(str);
      len += strlen(str);
    }
    if ( (i == (LINES/2)-1) || (t == n-1) )
    {
      for (j=len; j<COLS; j++) addstr(" ");
      attroff(A_UNDERLINE);
    }
    else clrtoeol();
  }  // for i
}   /* file_list() */



int display_asc_file(struct one_file *f)
/* displays the content of a block containing a text file, starting at line */
/* START_DUMP */
/* return: 0: open/seek/read error, 1: ok */
{
  unsigned char str[255];
  FILE *fs;
  int i, j;
  int n = 0;
  unsigned char *c;

  move(START_DUMP, 0);
  clrtobot();
  if (NULL == (fs = fopen(image, "r")))
  {
    sprintf(str, "fopen(%s): %s", image, strerror(errno));
    mvaddstr(START_DUMP, 0, str);
    return 0;
  }
  if (-1 == fseeko(fs, ((off_t) f->ext->start * (off_t) block_size), SEEK_SET))
  {
    fclose(fs);
    sprintf(str, "fseek(%s): %s", image, strerror(errno));
    mvaddstr(START_DUMP, 0, str);
    return 0;
  }
  for (i=START_DUMP; i<LINES; i++)
  {
    memset(str, 0, 80);
    if (NULL == fgets(str, 80, fs))
    {
      fclose(fs);
      if (START_DUMP == i)              // 1st line -> nothing read at all
      {
        sprintf(str, "can't read anything from block %lu", f->ext->start);
        mvaddstr(START_DUMP, 0, str);
        return 0;
      }
      else return 1;
    }
    n += strlen(str);
    if (n > 4096)                       // 1 block read and displayed
    {
      fclose(fs);
      return 1;
    }
    c = str;
    for(j=0; j < (int) strlen(str); j++, c++)
    {
      if ('\r' == *c) *c = '\0';
      if ('\n' == *c) *c = '\0';
      if ( (*c >= 0x80) && (*c < 0xa0) ) *c = '.';  // non-ISO extended ASCII
    }                                               // chars can't be printed
    mvaddstr(i, 0, str);
  }
  fclose(fs);
  return 1;
}   /* display_asc_file() */



int display_bin_file(struct one_file *f)
/* displays the content of a block containing a binary file, starting at line */
/* START_DUMP */
/* return: 0: open/seek/read error, 1: ok */
{
  int fs;
  char str[255];
  unsigned char buf[4096];
  int nread = 0;
  int i, j;
  int n = 0;
  unsigned char *c;
  char *s;

  move(START_DUMP, 0);
  clrtobot();
  memset(buf, 0, 4096);
  if (-1 == (fs = open(image, O_RDONLY|O_BINARY)))
  {
    sprintf(str, "open(%s): %s", image, strerror(errno));
    mvaddstr(START_DUMP, 0, str);
    return 0;
  }
  if (-1 == lseek(fs, ((off_t) f->ext->start * (off_t) block_size), SEEK_SET))
  {
    sprintf(str, "lseek(%s, %lu): %s", image, f->ext->start * block_size,
            strerror(errno));
    mvaddstr(START_DUMP, 0, str);
    close(fs);
    return 0;
  }
  nread = f->size > 4096 ? 4096 : f->size;
  if (-1 == read(fs, buf, nread))
  {
    sprintf(str, "read(%s): %s", image, strerror(errno));
    mvaddstr(START_DUMP, 0, str);
    close(fs);
    return 0;
  }
  close(fs);
  c = buf;
  for (i=START_DUMP; i<LINES; i++)
  {
    memset(str, 0, 80);
    s = str;
    sprintf(s, "%04X   ", (i-START_DUMP)*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)                        /* EOF before end of screen: */ 
      {                                      /* characters are not displayed */
        mvaddstr(i, 0, str);
        return 1;
      }
      sprintf(s, "%02X ", *c);
      s+=3;
    }
    sprintf(s, "   ");
    s+=3;
    c -= 16;
    n -= 16;

    for (j=0; j<16; j++, n++, c++)                /* character display */
    {
      if (8 == j)
      {
        sprintf(s, "  ");
        s+=2;
      }
      sprintf(s, "%c", ((*c<32)||(*c>126)) ? '.' : *c);
      s++;
    }
    mvaddstr(i, 0, str);
  } /* for i */
  return 1;     
} // display_bin_file()




int write_file(struct one_file *f, char type[])
/* saves file f to disk, using save_file() from dares.c */
/* prints success or error message to screen */
/* 0: error, 1: ok */
{
  int i;
  char buf[1024];
  char fname[1024];

  move(START_DUMP, 0);
  clrtobot();

  memset(buf, 0, 1024);
  memset(fname, 0, 1024);
  i = save_file(f, type, fname);
  if (i > 0)
  {
    sprintf(buf, "%s successfully saved.", fname);
    mvaddstr(START_DUMP, 0, buf);
    return 1;
  }
  else if (SAVE_FILE_CANNOT_OPEN_IMAGE == i)
  {
    sprintf(buf, "can't open %s\n", image);
    mvaddstr(START_DUMP, 0, buf);
    return 0;
  }
  else if (SAVE_FILE_CANNOT_OPEN_OUTFILE == i)
  {
    sprintf(buf, "can't open %s\n", fname);
    mvaddstr(START_DUMP, 0, buf);
    return 0;
  }
  else if (SAVE_FILE_SEEK_ERROR == i)
  {
    sprintf(buf, "seek error in %s", image);
    mvaddstr(START_DUMP, 0, buf);
    return 0;
  }
  else if (SAVE_FILE_READ_ERROR == i)
  {
    sprintf(buf, "read error in %s", image);
    mvaddstr(START_DUMP, 0, buf);
    return 0;
  }
  else if (SAVE_FILE_WRITE_ERROR == i)
  {
    sprintf(buf, "write error in %s", fname);
    mvaddstr(START_DUMP, 0, buf);
    return 0;
  }
  return 0;         // to avoid compiler warning; should never be reached
} /* write_file() */




int display_file_list(int nr)
/* displays list of files of type nr */
{
  struct one_file **f;   /* array with pointers to the files, so we don't */
                         /* need to use the chained list in all_types[nr] */
  struct one_file *p;
  int i, t, start;
  char str[MAX_TYPE];

  f = malloc(sizeof(void*) * all_types[nr].hash);
  if (NULL == f) return -1;
  p = all_types[nr].first;                    /* copy the list starting with */
  for (i=0; i < (int) all_types[nr].hash; i++) /* all_types[nr].first to f[] */
  {
assert(NULL != p);
    f[i] = p;
    p  = p->next;
  }

  attron(A_UNDERLINE);
  sprintf(str, "%s (%1d)", all_types[nr].name, all_types[nr].hash);
  mvaddstr(0, 0, str);
  addstr("                                                                   ");
  attron(A_BOLD);
  mvaddstr(0, COLS-25, "  <d>isplay <s>ave <q>uit");
  attroff(A_BOLD);
  attroff(A_UNDERLINE);
  file_list(f, all_types[nr].hash, 0, 0);
  clrtobot();
  i = 0;
  t = 0;
  start = 0;
  
  while ( (i != 'q') && (i != KEY_LEFT) )
  {
    i = getch();
    if ( ('d' == i) || ('\n' == i) || (KEY_RIGHT == i) )
    {
      if (ASC == f[t]->filetype) display_asc_file(f[t]);
      else display_bin_file(f[t]);
    }
    else if ('s' == i)
    {
      write_file(f[t], all_types[nr].name);
    }
    else if ('g' == i)
    {
// !!! grep !!!
    }
    else if (KEY_DOWN == i)
    {
      if (t < (int) all_types[nr].hash-1)
      {
        t++;
        if (t-start > (LINES/2)-2) start++;
        file_list(f, all_types[nr].hash, start, t);
      }
    }
    else if (KEY_NPAGE == i)
    {
      if (t < (signed int) all_types[nr].hash-((LINES/2)-2)-1) t+=(LINES/2)-2;
      else t=all_types[nr].hash-1;
      while (t-start > (LINES/2)-2) start++;
      file_list(f, all_types[nr].hash, start, t);
    }
    else if (KEY_UP == i)
    {
      if (t>0)
      {
        if (start==t) start--;
        t--;
        file_list(f, all_types[nr].hash, start, t);
      }
    }
    else if (KEY_PPAGE == i)
    {
      if (t > (LINES/2)-2) t -= (LINES/2)-2;
      else t=0;
      while (start>t) start--;
      file_list(f, all_types[nr].hash, start, t);
    }
  } /* while */
  free(f);
  return -1;
} // display_file_list() */



void type_list(int start, int highlight, off_t size[])
/* displays part of type list, starting with element start; element highlight */
/* is highlighted */
{
  int i, t;
  char str[MAX_TYPE+50];
  
  for (i=1, t=start; i<LINES && t<n_types; i++, t++)
  {
assert(all_types[t].first != NULL);
    if (t==highlight) attron(A_REVERSE);
    sprintf(str, "%s %s (%1d/%1d; %lld %s)",
            ASC == all_types[t].first->filetype ? "<text>" : "<bin> ",
            all_types[t].name, all_types[t].hash - all_types[t].trunc,
            all_types[t].hash,
            (long long) size[t] > 2000000 ? size[t]/(1024*1024)
                              : ( size[t] > 2000 ? size[t]/1024 : size[t] ),
            size[t] > 2000000 ? "MByte"
                              : ( size[t] > 2000 ? "KByte" : "Byte") );
    mvaddstr(i, 0, str);
    if (t==highlight) attroff(A_REVERSE);
    clrtoeol();
  }  // for i
}   /* type_list */



void header(int n_files, int n_types)
/* displays header line and menu of file type list */
{
  char str[80];

  attron(A_UNDERLINE);
  sprintf(str, "found %1d files of %1d different types", n_files, n_types);
  mvaddstr(0, 0, str);
  addstr("                                                                   ");
  attron(A_BOLD);
  mvaddstr(0, COLS-40, "                 <d>isplay <s>ave <q>uit");
  attroff(A_BOLD);
  attroff(A_UNDERLINE);
}   /* header() */



void display_type_list(int n_files)
/* displays file type list with curses */
/* return: -1: quit; else: number of selected type */
{
  int i = 0;
  static int t = 0;
  static int start = 0;
  off_t size[MAXMAGIS];
  struct one_file *f;

  for (i=0; i<n_types; i++)
  {
    size[i] = 0;
    f = all_types[i].first;
    while (f)
    {
      size[i] += f->size;
      f = f->next;
    }
  }

  initscr();                /* initializes curses library */
  keypad(stdscr, TRUE);     /* enable keyboard mapping */
  cbreak();                 /* take input chars one at a time, no wait for \n */
  noecho();                 /* don't echo input */
  curs_set(0);              /* cursor invisible */

  header(n_files, n_types);   /* header line and menu */
  type_list(start, t, size);  /* first part of the type list */

  i = 0;
  while ((char) i != 'q')
  {
    i = getch();
    if ( ('\n'==i) || (KEY_RIGHT==i) || ('d'==i) )     /* display file list */
    {
      display_file_list(t);
      move(0, 0);
      clrtobot();
      header(n_files, n_types);
      type_list(start, t, size);
    }
    else
    {
      if (KEY_DOWN == i)
      {
        if (t<n_types-1)
        {
          t++;
          if (t-start > LINES-2) start++;
          type_list(start, t, size);
        }
      }
      else if (KEY_NPAGE == i)
      {
        if (t<n_types-(LINES-2)-1) t+=LINES-2;
        else t=n_types-1;
        while (t-start > LINES-2) start++;
        type_list(start, t, size);
      }
      else if (KEY_UP == i)
      {
        if (t>0)
        {
          if (start==t) start--;
          t--;
          type_list(start, t, size);
        }
      }
      else if (KEY_PPAGE == i)
      {
        if (t>LINES-2) t-=LINES-2;
        else t = 0;
        while (start>t) start--;
        type_list(start, t, size);
      }
      else if ('s' == i)
      {
// save all files of a type
      }
    }
  }
  endwin();
}   /* display_type_list() */

#else

int display_type_list(int n_files)
{
  int i;

  printf("\nFound %1d files of %1d different types:\n", n_files, n_types);
  for (i=0; i<n_types; i++)
    printf("%3d: %s \t(%1d / %1d truncated)\n", i, all_types[i].name,
           all_types[i].hash, all_types[i].trunc);
  return 1;
}   /* print_type_list() */

#endif



int main(int argc, char *argv[])
{
  int n_found;  // # found types

  n_found = daresOpen(argc, argv);
  if (n_found > 0) display_type_list( n_found );

  const int res = daresClose();
  return res;
}
