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

//#define DJ_DBG
#include "djFileInfo.h"
#include <djMath.h>
#include <sys/stat.h>

namespace dj {

void FileInfo::init()
{
  _type = FILE;
  _size = 0;
  _user = 0;
  _perm = 0;
}

FileInfo::FileInfo() { init(); }

FileInfo::FileInfo(const Str & in)
{
  init();
  set(in);
}

void FileInfo::set(const Str & in)
{
  split(in, &_path, &_base, &_number, &_extension);
  if (! _number.isNull()) {
    _type = SEQ;
    _seq = toSeq(number());
  }
}

const Str FileInfo::get(int frame, bool path) const
{
  return
    (path ? _path : Str()) +
    _base + (
      SEQ == _type ?
        (frame != -1 ? frameToStr(frame, _seq.pad()) : toStr(_seq)) :
        _number
    ) +
    _extension;
}

void FileInfo::path(const Str & in) { _path = in; }
void FileInfo::base(const Str & in) { _base = in; }
void FileInfo::number(const Str & in) { _number = in; }
void FileInfo::extension(const Str & in) { _extension = in; }

bool FileInfo::stat(const Str & path)
{
  struct stat statBuf;
  memZero(&statBuf, sizeof(struct stat));
  
  const bool out = ::stat(
    Str(path.isNull() ? get() : (path + Str("/") + get(-1, false))).toAscii(),
    &statBuf
  ) == 0;
  
  if (out) {
    if (S_ISDIR(statBuf.st_mode)) _type = FileInfo::DIR;
    _size = statBuf.st_size;
    _user = statBuf.st_uid;
    _perm = 0;
    if (statBuf.st_mode & S_IRUSR) _perm |= FileInfo::READ;
    if (statBuf.st_mode & S_IWUSR) _perm |= FileInfo::WRITE;
    if (statBuf.st_mode & S_IXUSR) _perm |= FileInfo::EXEC;
    _time = statBuf.st_mtime;
  }
  
  return out;
}

void FileInfo::type(TYPE in) { _type = in; }
void FileInfo::size(uint64_t in) { _size = in; }
void FileInfo::user(uid_t in) { _user = in; }
void FileInfo::perm(int in) { _perm = in; }
void FileInfo::time(time_t in) { _time = in; }

bool FileInfo::isSeq() const
{
  //return SEQ == _type && _seq.start() != _seq.end();
  return SEQ == _type;
}

bool FileInfo::seqAdd(const FileInfo & in)
{
  DJ_DBG_FNC("dj::FileInfo::seqAdd");
  DJ_DBG_PRN("in = " << in);
  DJ_DBG_PRN("this = " << *this);
  
  // Compare type.
  //
  if (_type != in._type) return false;
  
  // Compare file extension and name.
  //
  if (_extension != in._extension) return false;
  if (_base != in._base) return false;

  _seq.set(
    min(_seq.start(), in._seq.start()),
    max(_seq.end(), in._seq.end()),
    max(_seq.pad(), in._seq.pad())
  );
  _size += in._size;
  _user = max(_user, in._user);
  _time = max(_time, in._time);
  
  DJ_DBG_PRN("seq = " << _seq);
  DJ_DBG_PRN("size = " << _size);

  return true;
}

void FileInfo::split(
  const Str & in,
  Str * path, Str * base, Str * number, Str * extension
) {
  DJ_ASSERT(path);
  DJ_ASSERT(base);
  DJ_ASSERT(number);
  DJ_ASSERT(extension);
  
  DJ_DBG_FNC("dj::FileInfo::split");
  DJ_DBG_PRN("in = " << in);

  //DJ_DBG_PRN("size = " << in.size());
  if (! in.size()) return;

  // Extension.
  //
  ConstStrIt it = in.end();
  for (; it.isValid() && it() != '.' && it() != '/'; --it) ;
  if (it.isValid() && '.' == it()) {
    *extension = cut(it, in.end());
    --it;
  } else {
    extension->del();
    it = in.end();
  }
  DJ_DBG_PRN("extension = " << *extension);

  // Number.
  //
  if (it.isValid() && isDigit(it())) {
    const ConstStrIt tmp = it;
    for (; it.isValid(); --it) {
      ConstStrIt prev = it;
      --prev;
      if (! prev.isValid()) break;
      if ('-' == prev()) {
        --prev;
        if (! prev.isValid()) break;
      }
      if (! isDigit(prev())) break;
    }
    *number = cut(it, tmp);
    --it;
  } else
    number->del();
  DJ_DBG_PRN("number = " << *number);

  // Base.
  //
  if (it.isValid() && it() != '/') {
    const ConstStrIt tmp = it;
    for (; it.isValid(); --it) {
      ConstStrIt prev = it;
      --prev;
      if (! prev.isValid() || '/' == prev()) break;
    }
    *base = cut(it, tmp);
    --it;
  } else
    base->del();
  DJ_DBG_PRN("base = " << *base);
  //DJ_DBG_PRN("index = " << i);

  // Path.
  //
  if (it.isValid())
    *path = cut(in.start(), it);
  else
    path->del();
  DJ_DBG_PRN("path = " << *path);
}

const List<FileInfo> seq(const List<FileInfo> & in)
{
  DJ_DBG_FNC("dj::seq(List<FileInfo>)");
  
  List<FileInfo> out;
  
  FileInfo * current = 0;
  for (ConstListIt<FileInfo> it = in; it.isValid(); ++it) {
    DJ_DBG_PRN("item = " << it());
    
    if (FileInfo::SEQ == it().type()) {
      
      if (current)
        if (current->seqAdd(it())) continue;
      
      current = 0;
      for (ListIt<FileInfo> outIt = out; outIt.isValid(); ++outIt) {
        FileInfo * p = &outIt();
        if (FileInfo::SEQ == p->type())
          if (p->seqAdd(it())) {
            current = p;
            break;
          }
      }
      if (current) continue;
    }
    
    out += it();
  }
  
  return out;
}

const List<FileInfo> filter(
  const List<FileInfo> & in, int filter, const StrList & globList
) {
  DJ_DBG_FNC("dj::filter(List<FileInfo>)");
  DJ_DBG_PRN("filter = " << filter);
  DJ_DBG_PRN("globs = " << globList);
  
  List<FileInfo> out;
  
  const bool filterFiles =
    ! (filter & FILE_FILTER_FILES || filter == FILE_FILTER_NONE);
  const bool filterDirs =
    ! (filter & FILE_FILTER_DIRS || filter == FILE_FILTER_NONE);
  const bool filterHidden =
    ! (filter & FILE_FILTER_HIDDEN || filter == FILE_FILTER_NONE);
  DJ_DBG_PRN("filter files = " << filterFiles);
  DJ_DBG_PRN("filter dirs = " << filterDirs);
  DJ_DBG_PRN("filter hidden = " << filterHidden);
  //const Str dot("."), dotDot("..");
  
  for (ConstListIt<FileInfo> it = in; it.isValid(); ++it) {
    const Str name = it().get(-1, false);
    DJ_DBG_PRN("name = " << name);
    bool filter = false;
    
    //if (dot == name) filter = true;
    //else if (dotDot == name) filter = true;
    if (
      (FileInfo::FILE == it().type() || FileInfo::SEQ == it().type()) &&
      filterFiles
    )
      filter = true;
    else if (FileInfo::DIR == it().type() && filterDirs) filter = true;
    else if ('.' == name[0] && filterHidden) filter = true;
    else {
      if (globList.size()) {
        ConstStrListIt it = globList;
        for (; it.isValid(); ++it)
          if (match(name, it())) break;
        if (! it.isValid()) filter = true;
      }
    }
    DJ_DBG_PRN("filter = " << filter);
    
    if (! filter) out += it();
  }
  return out;
}

static int _nameCompare(const void * a, const void * b)
{
  //DJ_DBG_FNC("dj::_nameCompare");
  const Str _a = (*((FileInfo **)a))->get();
  const Str _b = (*((FileInfo **)b))->get();
  if (_a < _b) return -1;
  else if (_a > _b) return 1;
  return 0;
}
static int _typeCompare(const void * a, const void * b)
{
  //DJ_DBG_FNC("dj::_typeCompare");
  const FileInfo::TYPE _a = (*((FileInfo **)a))->type();
  const FileInfo::TYPE _b = (*((FileInfo **)b))->type();
  if (_a < _b) return -1;
  else if (_a > _b) return 1;
  return 0;
}
static int _sizeCompare(const void * a, const void * b)
{
  //DJ_DBG_FNC("dj::_sizeCompare");
  const uint64_t _a = (*((FileInfo **)a))->size();
  const uint64_t _b = (*((FileInfo **)b))->size();
  if (_a < _b) return -1;
  else if (_a > _b) return 1;
  return 0;
}
static int _userCompare(const void * a, const void * b)
{
  //DJ_DBG_FNC("dj::_userCompare");
  const uid_t _a = (*((FileInfo **)a))->user();
  const uid_t _b = (*((FileInfo **)b))->user();
  if (_a < _b) return -1;
  else if (_a > _b) return 1;
  return 0;
}
static int _permCompare(const void * a, const void * b)
{
  //DJ_DBG_FNC("dj::_permCompare");
  const int _a = (*((FileInfo **)a))->perm();
  const int _b = (*((FileInfo **)b))->perm();
  if (_a < _b) return -1;
  else if (_a > _b) return 1;
  return 0;
}
static int _timeCompare(const void * a, const void * b)
{
  //DJ_DBG_FNC("dj::_timeCompare");
  const time_t _a = (*((FileInfo **)a))->time();
  const time_t _b = (*((FileInfo **)b))->time();
  if (_a < _b) return -1;
  else if (_a > _b) return 1;
  return 0;
}
static int _nameReverseCompare(const void * a, const void * b)
  { return _nameCompare(b, a); }
static int _typeReverseCompare(const void * a, const void * b)
  { return _typeCompare(b, a); }
static int _sizeReverseCompare(const void * a, const void * b)
  { return _sizeCompare(b, a); }
static int _userReverseCompare(const void * a, const void * b)
  { return _userCompare(b, a); }
static int _permReverseCompare(const void * a, const void * b)
  { return _permCompare(b, a); }
static int _timeReverseCompare(const void * a, const void * b)
  { return _timeCompare(b, a); }

const List<FileInfo> sort(
  const List<FileInfo> & in, FILE_SORT sort, bool reverse
) {
  DJ_DBG_FNC("dj::sort(List<FileInfo>)");
  DJ_DBG_PRN("in = " << in.size());
  DJ_DBG_PRN("sort = " << fileSortStr()[sort]);
  DJ_DBG_PRN("reverse = " << reverse);
  
  QSortCompare * compare = 0;
  switch (sort) {
    case FILE_SORT_NAME:
      compare = reverse ? _nameReverseCompare : _nameCompare;
      break;
    case FILE_SORT_TYPE:
      compare = reverse ? _typeReverseCompare : _typeCompare;
      break;
    case FILE_SORT_SIZE:
      compare = reverse ? _sizeReverseCompare : _sizeCompare;
      break;
    case FILE_SORT_USER:
      compare = reverse ? _userReverseCompare : _userCompare;
      break;
    case FILE_SORT_PERM:
      compare = reverse ? _permReverseCompare : _permCompare;
      break;
    case FILE_SORT_TIME:
      compare = reverse ? _timeReverseCompare : _timeCompare;
      break;
  }
  
  return qsort(in, compare);
}

const List<FileInfo> sortDirsFirst(const List<FileInfo> & in)
{
  List<FileInfo> a, b;
  for (ConstListIt<FileInfo> it = in; it.isValid(); ++it)
    if (FileInfo::DIR == it().type()) a += it();
    else b += it();
  return a + b;
}

bool operator == (const FileInfo & a, const FileInfo & b)
  { return a.get() == b.get(); }
bool operator != (const FileInfo & a, const FileInfo & b)
  { return ! (a == b); }

const Str filePermStr(int in)
{
  Str out;
  if (in & FileInfo::READ) out += filePermStr()[0];
  if (in & FileInfo::WRITE) out += filePermStr()[1];
  if (in & FileInfo::EXEC) out += filePermStr()[2];
  return out;
}

Dbg & operator << (Dbg & dbg, const FileInfo & in)
  { return dbg << in.get(); }

}
