/*------------------------------------------------------------------------------
 src/bin/djls/djls.cxx
 
 Copyright (c) 2004-2006 Darby Johnston.
 All rights reserved.
 Use is subject to license terms.
------------------------------------------------------------------------------*/

//#define DJ_DBG
#include <djUser.h>
#include <djDir.h>
#include <djAppBase.h>

namespace djls {
using namespace dj;

class App : public AppBase {
public:

  App(int &, char **);
  ~App();

private:

  void printItem(const FileInfo &, bool path = false);
  bool printDir(const FileInfo &, bool label);
  Str cmdln(StrList &);
  void help();

  StrList _inputList;
  bool _info, _seq, _hidden;
  StrList _filterList;
  bool _recurse;
  int _columns;
  FILE_SORT _sort;
  bool _reverse, _dirsFirst;
};

App::App(int & argc, char ** argv) :
  AppBase("djls", argc, argv),
  _info(true), _seq(true), _hidden(false), _recurse(false),
  _columns(terminalWidth()),
  _sort(FILE_SORT_TIME), _reverse(false), _dirsFirst(true)
{
  DJ_DBG_FNC("djls::App::App");
  
  bool r = true;
  
  // Command line.
  //
  StrList argList;
  for (int i = 1; i < argc; ++i) {
    const Str it(argv[i]);
    if (Str("-help") == it || Str("-h") == it) { help(); return; }
    else if (Str("-about") == it) { about(); return; }
    else argList += it;
  }
  const Str err = cmdln(argList);
  if (! err.isNull()) {
    error(Str("Error parsing command line \"%1\".").arg(err));
    exitValue(1);
    return;
  }
  
  // Setup input.
  //
  DJ_DBG_PRN("inputs = " << _inputList);
  List<FileInfo> itemList;
  for (StrListIt it = _inputList; it.isValid(); ++it) {
    FileInfo fileInfo(it());
    if (! fileInfo.stat()) {
      error(Str("Cannot open \"%1\".").arg(it()));
      r = false;
      continue;
    }
    itemList += fileInfo;
  }
  
  // Sequence, filter, and sort.
  //
  if (_seq) itemList = seq(itemList);
  itemList = filter(
    itemList,
    FILE_FILTER_FILES | FILE_FILTER_DIRS | FILE_FILTER_HIDDEN,
    _filterList
  );
  itemList = sort(itemList, _sort, _reverse);
  if (_dirsFirst) itemList = sortDirsFirst(itemList);
  
  // Use current directory if no other valid input is given.
  //
  if (! itemList.size() && r) {
    FileInfo currentDir(".");
    currentDir.stat();
    itemList += currentDir;
  }
  
  // Work.
  //
  for (ListIt<FileInfo> it = itemList; it.isValid(); ++it)
    if (FileInfo::DIR == it().type())
      r &= printDir(it(), (itemList.size() > 1) || _recurse);
    else
      printItem(it(), true);
  if (! r) exitValue(1);
}

App::~App()
{
  DJ_DBG_FNC("djls::App::~App");
}

void App::printItem(const FileInfo & in, bool path)
{
  Str tmp = in.get(-1, path);
  
  if (_info) {
  
    int columns [] = { 0, 0, 0, 0, 0, 0 };
    if (_columns) {
      columns[0] = _columns - 55;
      if (tmp.size() > columns[0]) {
        tmp = cut(tmp, 0, columns[0]);
        int i = tmp.size(), j = 3;
        while (i-- > 0 && j-- > 0) tmp[i] = '.';
      }
      columns[1] = -4;
      columns[2] = 8;
      columns[3] = -8;
      columns[4] = -3;
      columns[5] = -24;
    }
  
    print(
      Str("%1 %2 %3MB %4 %5 %6").
        arg(tmp, -columns[0]).
        arg(fileTypeStr()[in.type()], columns[1]).
        arg(in.size() / double(megabyte), columns[2], 2).
        arg(userToStr(in.user()), columns[3]).
        arg(filePermStr(in.perm()), columns[4]).
        arg(timeToStr(in.time()), columns[5])
    );
  } else {
  
    print(tmp);
  }
}

bool App::printDir(const FileInfo & in, bool label)
{
  DJ_DBG_FNC("djls::App::printDir");
  DJ_DBG_PRN("in = " << in);
  
  // Setup directory.
  //
  Dir dir(in);
  if (! dir.isValid()) {
    error(Str("Cannot open \"%1\".").arg(in));
    return false;
  }
  List<FileInfo> itemList = dir.itemList();
  
  // Sequence, filter, and sort.
  //
  if (_seq) itemList = seq(itemList);
  itemList = filter(
    itemList,
    FILE_FILTER_FILES | FILE_FILTER_DIRS | (_hidden ? FILE_FILTER_HIDDEN : 0),
    _filterList
  );
  itemList = sort(itemList, _sort, _reverse);
  if (_dirsFirst) itemList = sortDirsFirst(itemList);

  // Print items.
  //
  if (label) print(Str("%1:").arg(in));
  for (ListIt<FileInfo> it = itemList; it.isValid(); ++it)
    printItem(it());
  printSeparator();
  
  // Recurse.
  //
  bool r = true;
  if (_recurse) {
    Dir dir(in);
    const List<FileInfo> itemList = filter(dir.itemList(), FILE_FILTER_DIRS);
    for (ConstListIt<FileInfo> it = itemList; it.isValid(); ++it)
      r &= printDir(it(), label);
  }
  return r;
}

Str App::cmdln(StrList & in)
{
  // Options.
  //
  StrList argList;
  while (in.size()) {
    const Str it = in.pop();
    
    if (Str("-info") == it || Str("-i") == it) {
      if (! inflateStr(in, &_info, boolStr())) return it;
    } else if (Str("-sequence") == it || Str("-q") == it) {
      if (! inflateStr(in, &_seq, boolStr())) return it;
    } else if (Str("-hidden") == it || Str("-n") == it) {
      if (! inflateStr(in, &_hidden, boolStr())) return it;
    } else if (Str("-filter") == it || Str("-f") == it) {
      Str value;
      if (! inflateStr(in, &value)) return it;
      _filterList += value;
    } else if (Str("-recurse") == it || Str("-r") == it) {
      _recurse = true;
    } else if (Str("-columns") == it || Str("-c") == it) {
      if (! inflateStr(in, &_columns)) return it;
    }
    
    else if (Str("-sort") == it || Str("-s") == it) {
      if (! inflateStr(in, &_sort, fileSortStr())) return it;
    } else if (Str("-reverse") == it || Str("-rv") == it) {
      _reverse = true;
    } else if (Str("-dirsFirst") == it || Str("-df") == it) {
      if (! inflateStr(in, &_dirsFirst, boolStr())) return it;
    }
    
    else argList += it;
  }
  
  // Arguments.
  //
  _inputList = argList;
  
  return Str();
}

static const char helpStr [] =
"\n"
"djls\n"
"----\n"
"\n"
"Directory listing utility.\n"
"\n"
"Usage\n"
"-----\n"
"\n"
"djls [input]... [option]...\n"
"\n"
"Arguments\n"
"---------\n"
"\n"
"input - Input file or directory. Multiple inputs may be given.\n"
"\n"
"Options\n"
"-------\n"
"\n"
"-info, -i (value) - File information. Values: %1. Default: %2.\n"
"\n"
"-sequence, -q (value) - File sequencing. Values: %3. Default: %4.\n"
"\n"
"-hidden, -n (value) - Display hidden files. Values: %5. Default: %6.\n"
"\n"
"-filter, -f (value) - Filter items using shell globbing (e.g., *, ?). "
"Multiple filter options may be given.\n"
"\n"
"-recurse, -r - Descend into sub-directories.\n"
"\n"
"-columns, -c (value) - Number of output columns. A value of zero disables "
"formatting. Default: %7.\n"
"\n"
"Sorting Options\n"
"---------------\n"
"\n"
"-sort, -s (value) - Sorting. Values: %8. Default: %9.\n"
"\n"
"-reverse, -rv - Reverse sort order.\n"
"\n"
"-dirsFirst, -df (value) - Sort directories first. Values: %10. Default: %11.\n"
"\n"
"General Options\n"
"---------------\n"
"\n"
"-help, -h - Display help message.\n"
"\n"
"-about - Display about message.\n"
"\n"
"Example Output\n"
"--------------\n"
"\n"
"train.152-500.jpeg       Seq    5.51MB darby    rw  Wed Mar  1 09:29:21 2006\n"
"waterfall.167-199.jpeg   Seq    0.69MB darby    rw  Wed Mar  1 09:29:21 2006\n"
"bart.134-250.jpeg        Seq    1.28MB darby    rw  Wed Mar  1 09:29:18 2006\n"
"glyph.tiff               File   0.88MB darby    rw  Wed Mar  1 09:26:28 2006\n"
"(1)                      (2)    (3)    (4)      (5) (6)\n"
"\n"
"1. Name.\n"
"2. Type: File, Seq (file sequence), Dir (directory).\n"
"3. Size in megabytes.\n"
"4. User name.\n"
"5. Permissions: r (readable), w (writable), x (executable).\n"
"6. Last modification time.\n"
"\n"
"Examples\n"
"--------\n"
"\n"
"List the current directory:\n"
"> djls\n"
"\n"
"List given directories:\n"
"> djls ~/movies ~/pictures\n"
"\n"
"Sort by size, from big to small:\n"
"> djls -sort size -reverse\n"
"\n"
"Show only items ending with \".tiff\" or \".tif\":\n"
"> djls -filter \"*.tiff\" -filter \"*.tif\"\n";

void App::help()
{
  printFormatted(
    Str(helpStr).
      arg(join(boolStr(), Str(", "))).arg(boolStr()[_info]).
      arg(join(boolStr(), Str(", "))).arg(boolStr()[_seq]).
      arg(join(boolStr(), Str(", "))).arg(boolStr()[_hidden]).
      arg(_columns).
      arg(join(fileSortStr(), Str(", "))).arg(fileSortStr()[_sort]).
      arg(join(boolStr(), Str(", "))).arg(boolStr()[_dirsFirst])
  );
}

}

int main(int argc, char ** argv)
{
  int exitValue = 0;
do {
  djls::App app(argc, argv);
  exitValue = app.exitValue();
} while (0);
  return exitValue;
}
