/***************************************************************************
                          dub.cpp  -  description
                             -------------------
    begin                : Tue Oct 23 01:44:51 EEST 2001
    copyright            : (C) 2001 by Eray Ozkural (exa)
    email                : erayo@cs.bilkent.edu.tr
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

// include files for QT
#include <tqdir.h>
#include <tqprinter.h>
#include <tqpainter.h>

// include files for KDE
#include <kiconloader.h>
#include <kdebug.h>
#include <tdefiledialog.h>
#include <tdemenubar.h>
#include <kstatusbar.h>
#include <tdelocale.h>
#include <tdeconfig.h>
#include <kstdaction.h>
#include <kurl.h>
#include <kurlrequester.h>
#include <noatun/playlist.h>

#include <vector>
#include <algorithm>
#include <random>
using std::vector;
using std::iterator;

// application specific includes
#include "dub.h"
#include "dub.moc"
#include "dubview.h"
#include "dubplaylist.h"
#include "dubprefs.h"
#include "dubconfigmodule.h"

#include "random.h"
int Random::seed;

#define ID_STATUS_MSG 1

Dub::Dub(DubPlaylist* plist)
  : DubApp(0)
  , playlist(*plist)
  , dubconfig(*plist->dubconfig)
  , activeFile(0)
  , linear_onedir(this)
  , linear_recursive(this)
  , shuffle_onedir(this)
  , shuffle_recursive(this)
{
  connect( view->dirOperator(),
           TQ_SIGNAL(fileSelected(const KFileItem*)),
           this,
           TQ_SLOT(fileSelected(const KFileItem*)) );
  connect( dubconfig.prefs->mediaDirectory,
           TQ_SIGNAL( urlSelected (const TQString &) ),
           this,
           TQ_SLOT( mediaHomeSelected (const TQString &) ) );
  connect( this,
           TQ_SIGNAL(setMediaHome(KURL)),
           view,
           TQ_SLOT(setDir(KURL)) );
  configure_sequencing();
  emit setMediaHome(dubconfig.mediaDirectory);
}

/** File selected */
void Dub::fileSelected(const KFileItem * file) {
  kdDebug(90010) << "dub: file selected " << file << endl;
  activeFile = const_cast<KFileItem *>(file);
  playlist.setCurrent(file, true);
}

void Dub::mediaHomeSelected(const TQString& url) {
  kdDebug(90010) << "media home selected:" << endl;
  emit setMediaHome( KURL(url) );
}

/** changes the active file to the next item
 */
void Dub::selectNextFile() {
  configure_sequencing();
  sequencer->next();
}

/** No descriptions */
KFileItem* Dub::queryRoot() {
  return view->dirLister()->rootItem();
}

/** First file in the directory */
const KFileItem* Dub::queryFirstFile() {
  return sequencer->first();
}

/** Select previous file */
void Dub::selectPreviousFile() {
  configure_sequencing();
  sequencer->prev();
}

void Dub::configure_sequencing()
{
  switch (dubconfig.playMode) {
  case DubConfigModule::allFiles:
    if (dubconfig.playOrder==DubConfigModule::normal) {
      linear_recursive.init(dubconfig.mediaDirectory);
      sequencer = &linear_recursive;
    }
    else if (dubconfig.playOrder==DubConfigModule::shuffle) {
      shuffle_recursive.init(dubconfig.mediaDirectory);
      sequencer = &shuffle_recursive;
    }
    break;
  case DubConfigModule::recursiveDir:
    linear_recursive.init(view->currentDirectory().path());
    sequencer = &linear_recursive;
    break;
  case DubConfigModule::oneDir:
    if (dubconfig.playOrder==DubConfigModule::normal)
      sequencer = &linear_onedir;
    else if (dubconfig.playOrder==DubConfigModule::shuffle) {
      shuffle_onedir.init(view->currentDirectory().path());
      sequencer = &shuffle_onedir;
    }
    break;
  }
}

void Dub::Sequencer::set_file(KFileItem** file, KFileItem* val) {
  assert(val);
  if (*file)
    delete *file;
  *file = new KFileItem(*val);
  kdDebug(90010) << "set_file to " << val->url() << endl;
}

KFileItem* Dub::Linear_Seq::first(TQPtrList<KFileItem> & items)
{
  // find first file
  KFileItem* firstFile = 0;
  for (KFileItem* item = items.first(); item; item = items.next() ) {
    if (item->isFile()) {
      firstFile = item;
      break;
    }
  }
  return firstFile;
}

KFileItem* Dub::Linear_Seq::last(TQPtrList<KFileItem> & items)
{
  // find last file
  KFileItem* lastFile = 0;
  for (KFileItem* item = items.last(); item; item = items.prev() ) {
    if (item->isFile()) {
      lastFile = item;
      break;
    }
  }
  return lastFile;
}

bool Dub::Linear_Seq::find(TQPtrList<KFileItem> & items, KFileItem* a_file)
{
  // find file
   for (KFileItem *file=items.first(); file; file=items.next() )
      if (file->isFile() && file->cmp(*a_file)) {
        kdDebug(90010) << " found " << (file->url()) << endl;
        return true;
      }
  return false;
}

KFileItem* Dub::Linear_Seq::next(TQPtrList<KFileItem> & items,
			      KFileItem** active_file)
{
  KFileItem* ret = 0;
  assert(active_file);
  bool found = false;
  if (*active_file) {
    if (find(items, *active_file)) {
      KFileItem* next = items.next();
      for (; next && !next->isFile(); next = items.next()) ; // find next file
      if (next && next->isFile())
	set_file(active_file, next);
        found = true;
        ret = next;
    }
  }
  if (!found) { // try to get the first one then
     KFileItem *fst = first(items);
     if (fst) {
       set_file(active_file, fst);
       ret = fst;
     }
   }
  return ret;
}

KFileItem* Dub::Linear_Seq::prev(TQPtrList<KFileItem> & items,
			      KFileItem** active_file)
{
  KFileItem* ret = 0;
  assert(active_file);
  bool found = false;
  if (*active_file) {
    // locate current item
    if (find(items, *active_file)) {
      KFileItem* prev = items.prev();
      for (; prev && !prev->isFile(); prev = items.prev()) ; // find prev file
      if (prev && prev->isFile()) {
        set_file(active_file, prev);
        found = true;
        ret = prev;
      }
    }
  }
  if (!found) { // try to get the last one then
     KFileItem *lst = last(items);
     if (lst) {
       set_file(active_file, lst);
       ret = lst;
     }
   }
  return ret;
}

KFileItem* Dub::Linear_OneDir::first()
{
  KFileItem* first = Linear_Seq::first(dub.view->items());
  if (first)
    set_file(&first_file, first);
  else {
    if (first_file) {		// invalidate first
      delete first_file;
      first_file = 0;
    }
  }
  return first_file;
}

//KFileItem* Dub::Linear_OneDir::getAfter(KFileItem* item)
//{

//}

void Dub::Linear_OneDir::next()
{
  KFileItem *f = Linear_Seq::next(dub.view->items(), &dub.activeFile);
  if (f) {
    dub.view->selectFile(f);
  }
}

void Dub::Linear_OneDir::prev()
{
  KFileItem *f = Linear_Seq::prev(dub.view->items(), &dub.activeFile);
  if (f) {
    dub.view->selectFile(f);
  }
}

Dub::Dir_Node::Dir_Node(TQString d, bool forward)
  : dir(d), past_begin(false)
{
  kdDebug(90010) << "cons dir node " << d << endl;
  // process entry list, form a list of subdirs and normal files
  file_items.setAutoDelete(true);
  TQDir dir_obj(dir);
  TQFileInfoList* entries =
    const_cast<TQFileInfoList*>(dir_obj.entryInfoList());
  for ( TQFileInfo *file = entries->first(); file; file = entries->next() ) {
    if (file->isDir() && file->absFilePath().length()>d.length()) {
      kdDebug(90010) << "dub: dir " << file->absFilePath() << endl;
      subdirs.append(file->absFilePath());
    }
    if (file->isFile()) {
      // price for portability
      kdDebug(90010) << "dub: file " << file->absFilePath() << endl;
      KFileItem* item = new KFileItem(KFileItem::Unknown, KFileItem::Unknown,
				      file->absFilePath(), true);
      file_items.append(item);
    }
  } // for

  init_traversal(forward);

  kdDebug(90010) << "dir node cons end" << endl;
}

void Dub::Dir_Node::init_traversal(bool forward)
{
  kdDebug(90010) << "init traversal" << endl;
  // initialize traversal information
  if (forward) {
    current_subdir = subdirs.begin();
    file_items.first();
  }
  else {
    current_subdir = subdirs.end();
    if (current_subdir!=subdirs.begin())
      current_subdir--; // last item
    else
      past_begin=true;
    file_items.last();
  }
  current_file = file_items.current();
  kdDebug(90010) << "current subdir " << *current_subdir << endl;
  kdDebug(90010) << "current file " << current_file << endl;
}

Dub::Recursive_Seq::Recursive_Seq()
{
  play_stack.setAutoDelete(true);
}

void Dub::Recursive_Seq::init(const KURL & root)
{
  TQString new_root = canonical_path(root.path());
  if (recursion_root != new_root) {
    // change recursion stack
    recursion_root = new_root;
    kdDebug(90010) << "rec: new root is " << recursion_root << endl;
    play_stack.clear();
    push_dir(recursion_root); // start pre-order traversal
  }
}

// get canonical path, we need this
TQString Dub::Recursive_Seq::canonical_path(TQString dir)
{
//   kdDebug(90010) << "canonical_path " << dir << endl;
  //assert(dir.isLocalFile());
  TQDir path(dir);
  return path.canonicalPath();
}

// check if dir is contained in the stack
bool Dub::Recursive_Seq::check_dir(TQString dir)
{
  kdDebug(90010) << "check_dir " << dir << endl;
  bool found = false;
  for ( Dir_Node *cur_dir = play_stack.first();
        !found && cur_dir; cur_dir = play_stack.next() ) {
    if (cur_dir->dir==dir)
      found = true;
  }
  return found;
}

bool Dub::Recursive_Seq::push_dir(TQString dir, bool forward)
{
  kdDebug(90010) << "push_dir " << dir << ", forward?" << forward << endl;
  TQString cpath = canonical_path(dir);
  if (check_dir(cpath))		// is it in stack?
    return false;		// avoid infinite recursion
  else {
    Dir_Node* node = new Dir_Node(cpath, forward);
    play_stack.append(node);
    kdDebug(90010) << "stack after push:" << endl;
    print_stack();
    return true;
  }
}

bool Dub::Recursive_Seq::pop_dir()
{
  assert(!play_stack.isEmpty());
  kdDebug(90010) << "pop_dir" << endl;
  play_stack.removeLast();
  return !play_stack.isEmpty();
}

bool Dub::Recursive_Seq::advance(bool forward)
{
  Dir_Node* top = play_stack.getLast();
  kdDebug(90010) << "first child " << top->subdirs.first() << endl;
  kdDebug(90010) << "current child " << *top->current_subdir << endl;
  kdDebug(90010) << "last child " << top->subdirs.last() << endl;
  if (forward) {
    top->current_subdir++;	// advance dir
    return top->current_subdir!=top->subdirs.end();
  }
  else
    if (top->current_subdir!=top->subdirs.begin()) {
      top->current_subdir--;
      return true;
    }
    else {
      top->past_begin=true;
      return false;
    }
}

void Dub::Recursive_Seq::pop_preorder(bool forward)
{
  if (pop_dir()) {		// pop visited
    advance(forward);		// advance to next node
    Dir_Node* top = play_stack.getLast();
    kdDebug(90010) << "new child " << *top->current_subdir << endl;
    if (forward)
      next_preorder();		// continue processing
    else
      prev_preorder();		// continue processing
  }
  else {
    kdDebug(90010) << "push root" << endl;
    push_dir(recursion_root, forward);	// back to the beginning if at end
  }
}

void Dub::Recursive_Seq::next_preorder()
{
  assert(!play_stack.isEmpty());	// recursion stack cannot be empty
  kdDebug(90010) << "next_preorder, stack:" << endl;
  print_stack();
  Dir_Node* top = play_stack.getLast();
  if (top->subdirs.isEmpty() || top->current_subdir==top->subdirs.end()) {
    kdDebug(90010) << "rec: subtrees done" << endl;
    pop_preorder(true);    // pop if subtrees done
  }
  else {
    TQString subdir = *top->current_subdir; // we have a subdir
    push_dir(subdir, true); // push directory w/ forward iterators
  }
}

void Dub::Recursive_Seq::prev_preorder()
{
  assert(!play_stack.isEmpty());	// recursion stack cannot be empty
  kdDebug(90010) << "prev_preorder, stack:" << endl;
  print_stack();
  Dir_Node* top = play_stack.getLast();
  if (top->subdirs.isEmpty() || top->past_begin) { // subtrees done?
    kdDebug(90010) << "rec: subtrees done" << endl;
    pop_preorder(false);
  }
  else {
    TQString subdir = *top->current_subdir;
    kdDebug(90010) << "we have children, pushing now " << subdir << endl;
    push_dir(subdir, false); // push directory w/ backward iterators
  }
}

void Dub::Recursive_Seq::print_stack() {
  for ( Dir_Node *cur_dir = play_stack.first();
	cur_dir; cur_dir = play_stack.next() ) {
    kdDebug(90010) <<  cur_dir->dir << endl;
  }
}

Dub::Linear_Recursive::Linear_Recursive(Dub* d)
  : Sequencer(d) {
  kdDebug(90010) << "cons linear/recursive" << endl;
}

KFileItem* Dub::Linear_Recursive::first()
{
  KFileItem* first = bottom_dir()->file_items.getFirst();
  return first;
}

void Dub::Linear_Recursive::next()
{
  assert(!play_stack.isEmpty());
  Dir_Node* top = top_dir();
  TQString dir = top->dir;
  top->current_file = top->file_items.next();
  kdDebug(90010) << "dub current dir: " << dir << endl;
  kdDebug(90010) << "dub current file: " << top->current_file << endl;
  bool cycle = false;
  while (!top_dir()->current_file && !cycle) {
    next_preorder();  // traverse until a non-empty dir or cycle
   if (top_dir()->dir==dir) {
     kdDebug(90010) << "we got a cycle" << endl;
     cycle = true;
     top_dir()->init_traversal(true);
   }
  }
  top = play_stack.getLast();
  kdDebug(90010) << "dub new dir: " << *top->current_subdir << endl;
  kdDebug(90010) << "dub new file: " << top->current_file << endl;
  if (top->current_file) {
    kdDebug(90010) << "dub new file: " << top->current_file->url() << endl;
    dub.activeFile = top->current_file;
    dub.fileSelected(dub.activeFile);
  }
}

void Dub::Linear_Recursive::prev()
{
  assert(!play_stack.isEmpty());
  Dir_Node* top = top_dir();
  TQString dir = top->dir;
  top->current_file = top->file_items.prev();
  kdDebug(90010) << "dub current dir: " << dir << endl;
  kdDebug(90010) << "dub current file: " << top->current_file << endl;
  bool cycle = false;
  while (!top_dir()->current_file && !cycle) {
   prev_preorder();  // traverse until a non-empty dir or cycle
   if (top_dir()->dir==dir) {
     kdDebug(90010) << "we got a cycle" << endl;
     cycle = true;
     top_dir()->init_traversal(false);
   }
  }
  top = play_stack.getLast();
  kdDebug(90010) << "dub new dir: " << *top->current_subdir << endl;
  kdDebug(90010) << "dub new file: " << top->current_file << endl;
  if (top->current_file) {
    kdDebug(90010) << "dub new file: " << top->current_file->url() << endl;
    dub.activeFile = top->current_file;
    dub.fileSelected(dub.activeFile);
  }
}

void Dub::Shuffle_OneDir::init(const TQString& dir)
{
  if (shuffle_dir != dir) {
    kdDebug(90010) << "shuffle/onedir init" << endl;
    shuffle_dir = dir;
    play_index = 0;

    // make a deep copy
    items.clear();
    TQPtrList<KFileItem> & view_items = dub.view->items(); //
    for (KFileItem *file=view_items.first(); file; file=view_items.next() )
      if (file->isFile())	// add only files
	items.append(new KFileItem(*file));

    int num_items = items.count();
    play_order.resize(num_items);
    if (num_items) {		// generate shuffled order
      kdDebug(90010) << num_items << " file items" << endl;
      for (int i=0; i<num_items; i++)
	play_order[i] = i;
      std::random_device randomDev;
      std::mt19937       randomGenerator(randomDev());
      std::shuffle(play_order.begin(), play_order.end(), randomGenerator);
    }
  }
}

KFileItem* Dub::Shuffle_OneDir::first()
{
  return 0;
}

void Dub::Shuffle_OneDir::next()
{
  kdDebug(90010) << "shuffle/onedir next" << endl;
  if (!items.isEmpty()) {
    play_index = ++play_index % play_order.size();
    dub.activeFile = items.at(play_order[play_index]);
    if (dub.activeFile)
      dub.fileSelected(dub.activeFile);
  }
}

void Dub::Shuffle_OneDir::prev()
{
  kdDebug(90010) << "shuffle/onedir prev" << endl;
  if (!items.isEmpty()) {
    play_index = --play_index % play_order.size();
    dub.activeFile = items.at(play_order[play_index]);
    if (dub.activeFile)
      dub.fileSelected(dub.activeFile);
  }
}

KFileItem* Dub::Shuffle_Recursive::random_file()
{
  assert(!play_stack.isEmpty());
  play_stack.clear();
  push_dir(recursion_root); // start pre-order traversal
  KFileItem* selected = 0;
  double file_probability = 0.3;
  Random::init();
  while (!top_dir()->subdirs.isEmpty() && !selected) {
    if (top_dir()->file_items.isEmpty()) {
      int ix = Random::random_int(top_dir()->subdirs.count());
      push_dir(top_dir()->subdirs[ix]);
    }
    else {
      if (Random::random_double(1.0)<file_probability) {
	int ix = Random::random_int(top_dir()->file_items.count());
	selected = top_dir()->file_items.at(ix);
      }
      else {
	int ix = Random::random_int(top_dir()->subdirs.count());
	push_dir(top_dir()->subdirs[ix]);
      }
    }
  }
  if (!selected) {
    if (!top_dir()->file_items.isEmpty()) {
      int ix = Random::random_int(top_dir()->file_items.count());
      selected = top_dir()->file_items.at(ix);
    }
  }
  return selected;
}

KFileItem* Dub::Shuffle_Recursive::first()
{
  return random_file();
}

void Dub::Shuffle_Recursive::next()
{
  KFileItem* file = random_file();
  if (file) {
    kdDebug(90010) << "shuffle/rec: new file: " << file->url() << endl;
    dub.activeFile = file;
    dub.fileSelected(file);
  }
}

void Dub::Shuffle_Recursive::prev()
{
  KFileItem* file = random_file();
  if (file) {
    kdDebug(90010) << "shuffle/rec: new file: " << file->url() << endl;
    dub.activeFile = file;
    dub.fileSelected(file);
  }
}
