#include <qobject.h>
#include <qlayout.h>
#include <qscrollview.h>
#include <qscrollbar.h>
#include <qtimer.h>
#include <qprinter.h>
#include <qprintdialog.h>
#include <qobjectlist.h>


#include <kinstance.h>
#include <klocale.h>
#include <kiconloader.h>
#include <kaction.h>
#include <kstdaction.h>
#include <kdebug.h>
#include <kaccel.h>
#include <kmessagebox.h>
#include <kglobal.h>
#include <kstddirs.h>
#include <kconfig.h>


#include "marklist.h"
#include "scrollbox.h"
#include "gotodialog.h"
#include "kpagetest.h"
#include "kviewpart.moc"


extern "C"
{
  void *init_libkviewpart()
  {
    return new KViewPartFactory;
  }
};


KInstance *KViewPartFactory::s_instance = 0L;


KViewPartFactory::KViewPartFactory()
{
}


KViewPartFactory::~KViewPartFactory()
{
  if (s_instance)
    delete s_instance;

  s_instance = 0;
}


QObject *KViewPartFactory::create(QObject *parent, const char *name , const char*, const QStringList &args)
{
  QString partname = "";
  if (args.count() >= 1)
    partname = args[0];
  QObject *obj = new KViewPart(partname, (QWidget *)parent, name);
  emit objectCreated(obj);
  return obj;
}


KInstance *KViewPartFactory::instance()
{
  if (!s_instance)
    s_instance = new KInstance("kviewpart");
  return s_instance;
}


class PaperSize 
{
public:

  PaperSize(double w, double h)
    : width(w), height(h) {};

  double width;
  double height;
};


KViewPart::KViewPart(QString partname, QWidget *parent, const char *name)
  : KParts::ReadOnlyPart(parent, name), _partname(partname), _numberOfPages(0),
  _currentPage(0), _zoom(1.0)
{
  setInstance(KViewPartFactory::instance());

  mainWidget = new QWidget(parent);
  mainWidget->setFocusPolicy(QWidget::StrongFocus);
  setWidget(mainWidget);

  QHBoxLayout *hbox = new QHBoxLayout(mainWidget, 0, 0);
  QVBoxLayout *vbox = new QVBoxLayout(hbox);

  scrollBox = new ScrollBox(mainWidget);
  scrollBox->setFixedWidth(75);
  scrollBox->setMinimumHeight(75);
  vbox->addWidget(scrollBox);

  connect(scrollBox, SIGNAL(valueChanged(QPoint)), this, SLOT(scrollBoxChanged(QPoint)));

  markList = new MarkList(mainWidget);
  markList->setAutoUpdate(true);
  vbox->addWidget(markList);
  vbox->setStretchFactor(markList, 1);

  connect(markList, SIGNAL(selected(int)), this, SLOT(pageSelected(int)));

  // create the displaying part

  // Try to load
  KLibFactory *factory = KLibLoader::self()->factory(QString("libk%1").arg(_partname));
  if (factory)
  {
    multiPage = (KMultiPage*) factory->create(mainWidget, QString("k%1").arg(_partname), "KPart");
  }
  else
    {
      KMessageBox::error(mainWidget, QString("No libk%1 found !").arg(_partname));
      exit(-1);
    }

  hbox->addWidget(multiPage->widget());


  // connect to the multi page view
  connect(multiPage, SIGNAL(numberOfPages(int)), this, SLOT(numberOfPages(int)));
  connect(multiPage->scrollView(), SIGNAL(contentsMoving(int,int)),
	  this, SLOT(contentsMoving(int,int)));
  multiPage->scrollView()->installEventFilter(this);

  connect( multiPage, SIGNAL( started( KIO::Job * ) ),
	   this, SIGNAL( started( KIO::Job * ) ) );
  connect( multiPage, SIGNAL( completed() ),
	   this, SIGNAL( completed() ) );
  connect( multiPage, SIGNAL( canceled( const QString & ) ),
	   this, SIGNAL( canceled( const QString & ) ) );
  connect( multiPage, SIGNAL( previewChanged(bool)), this, SLOT(updatePreview(bool)));


  // settings menu
  showmarklist = new KToggleAction ("Show &Page List", 0, this, SLOT(slotShowMarkList()), actionCollection(), "show_page_list");
  showPreview = new KToggleAction ("Show P&review", 0, this, SLOT(slotPreview()), actionCollection(), "show_preview");
  watchAct = new KToggleAction(i18n("&Watch file"), 0, 0, 0, actionCollection(), "watch_file");


  // view menu
  QStringList orientations;
  orientations.append(i18n("Portrait"));
  orientations.append(i18n("Landscape"));
  orientation = new KSelectAction ("&Orientation", 0, 0, 0, actionCollection(), "view_orientation");
  connect (orientation, SIGNAL(activated (int)), this, SLOT(slotOrientation(int)));
  orientation->setItems(orientations);

  media = new KSelectAction ("Paper &Size", 0, 0, 0, actionCollection(), "view_media");
  connect (media, SIGNAL(activated(int)), this, SLOT(slotMedia(int)));
  fillPaperSizes();

  _paperWidth = 21.0;
  _paperHeight = 29.7;
  setPaperSize(_paperWidth, _paperHeight);

  zoomInAct = KStdAction::zoomIn (this, SLOT(zoomIn()), actionCollection());
  zoomOutAct = KStdAction::zoomOut(this, SLOT(zoomOut()), actionCollection());

  fitAct = KStdAction::actualSize(this, SLOT(fitSize()), actionCollection());
  fitPageAct = KStdAction::fitToPage(this, SLOT(fitToPage()), actionCollection());
  fitWidthAct = KStdAction::fitToWidth(this, SLOT(fitToWidth()), actionCollection());
  fitHeightAct = KStdAction::fitToHeight(this, SLOT(fitToHeight()), actionCollection());

  // go menu
  backAct = KStdAction::prior(this, SLOT(prevPage()), actionCollection());
  forwardAct = KStdAction::next(this, SLOT(nextPage()), actionCollection());
  startAct = KStdAction::firstPage(this, SLOT(firstPage()), actionCollection());
  endAct = KStdAction::lastPage(this, SLOT(lastPage()), actionCollection());
  gotoAct = KStdAction::gotoPage(this, SLOT(goToPage()), actionCollection());

  readDownAct = new KAction(i18n("Read down document"), "next",
			    Key_Space, this, SLOT(readDown() ),
			    actionCollection(), "go_read_down");
  /*
    markAct =
      new KAction(i18n("Toggle this page mark"),
		  QIconSet(BarIcon("flag", KGVFactory::instance())),
		  Key_Enter, w, SLOT(markPage() ), actionCollection(), "markPage");
    */

    //TODO -- disable entry if there aren't any page names
    //Settings menu
    /*
    fancyAct =
      new KToggleAction (i18n("Show Page Names"),
			 0, this, SLOT(slotFancy()), actionCollection(),
			 "fancy_page_labels");
    fancyAct->setChecked (w->areFancyPageLabelsEnabled ());
    */

  printAction = KStdAction::print(this, SLOT(slotPrint()), actionCollection());
  saveAction = KStdAction::saveAs(this, SLOT(slotSave()), actionCollection());

  // keyboard accelerators
  accel = new KAccel(mainWidget);
  accel->insertItem(i18n("Scroll Up"), "Scroll Up", "Up");
  accel->insertItem(i18n("Scroll Down"), "Scroll Down", "Down");
  accel->insertItem(i18n("Scroll Left"), "Scroll Left", "Left");
  accel->insertItem(i18n("Scroll Right"), "Scroll Right", "Right");

  accel->insertItem(i18n("Scroll Up Page"), "Scroll Up Page", "Shift+Up");
  accel->insertItem(i18n("Scroll Down Page"), "Scroll Down Page", "Shift+Down");
  accel->insertItem(i18n("Scroll Left Page"), "Scroll Left Page", "Shift+Left");
  accel->insertItem(i18n("Scroll Right Page"), "Scroll Right Page", "Shift+Right");

  accel->readSettings();

  accel->connectItem("Scroll Up", this, SLOT(scrollUp()));
  accel->connectItem("Scroll Down", this, SLOT(scrollDown()));
  accel->connectItem("Scroll Left", this, SLOT(scrollLeft()));
  accel->connectItem("Scroll Right", this, SLOT(scrollRight()));

  accel->connectItem("Scroll Up Page", this, SLOT(scrollUpPage()));
  accel->connectItem("Scroll Down Page", this, SLOT(scrollDownPage()));
  accel->connectItem("Scroll Left Page", this, SLOT(scrollLeftPage()));
  accel->connectItem("Scroll Right Page", this, SLOT(scrollRightPage()));

  setXMLFile("kviewpart.rc");


  connect(&watch, SIGNAL(dirty(const QString&)), this, SLOT(fileChanged(const QString&)));


  m_extension = new KViewPartExtension(this);

  // create the goto dialog
  _gotoDialog = new GotoDialog(mainWidget);
  _gotoDialog->hide();
  connect(_gotoDialog, SIGNAL(gotoPage(const QString&)), this, SLOT(slotGotoDialog(const QString&)));

  numberOfPages(0);
  checkActions();
  
  // allow parts to have a GUI, too :-)
  // (will be merged automatically)
  insertChildClient( multiPage );

  orientation->setCurrentItem(0);
  media->setCurrentItem(1);

  readSettings();

  // watch mouse events in the viewport
  QWidget *vp = multiPage->scrollView()->viewport();
  vp->installEventFilter(this);
  // we also have to set an filter on the child
  // TODO: solve this more elegant
  if (vp->children())
    {
      QWidget *w = (QWidget*)((QObjectList*)(vp->children()))->first();
      w->installEventFilter(this);
    }
}


KViewPart::~KViewPart()
{
  writeSettings();
  delete multiPage;
}


void KViewPart::fillPaperSizes()
{
  _paperSizes.clear();

  QString fname = KGlobal::dirs()->findResource("data", "kviewshell/paper-formats");
  kdDebug() << "Paper: " << fname << endl;

  KConfig paper(fname, true);
  paper.setGroup("Paper");
  unsigned int count = paper.readNumEntry("Count");

  double w, h;
  for (unsigned int i=0; i<count; i++)
    {
      paper.setGroup(QString("Paper %1").arg(i));
      w = paper.readDoubleNumEntry("Width");
      h = paper.readDoubleNumEntry("Height");
      _paperSizes.append(new PaperSize(w,h));
    }

  QStringList items;
  items << i18n("A3");
  items << i18n("A4");
  items << i18n("A5");
  items << i18n("Letter");
  items << i18n("Legal");
  items << i18n("Other...");
  media->setItems(items);
}


void KViewPart::setPaperSize(double w, double h)
{
  // reflect in scrollbox
  scrollBox->setMinimumHeight((int) 75.0*h/w);

  // forward
  multiPage->setPaperSize(w, h);
  
  updateScrollBox();
}


QStringList KViewPart::fileFormats()
{
  return multiPage->fileFormats();
}


void KViewPart::slotShowMarkList()
{
  if (showmarklist->isChecked())
    {
      markList->show();
      scrollBox->show();
    }
  else
    {
      markList->hide();
      scrollBox->hide();
    }
}

bool KViewPart::openFile()
{
  // Note: KDirWatch is able to watch files, as well.
  // Hope this stays that way...
  watch.addDir(m_file);
  watch.startScan();

  return true;
}

bool KViewPart::openURL(const KURL &url)
{  
  KParts::ReadOnlyPart::openURL(url);

  bool r = multiPage->openURL(url);

  updateScrollBox();
  checkActions();
  markList->select(0);

  if (r)
    {
      // start viewing horizontally centered
      QScrollView *sv = multiPage->scrollView();
      if (sv)
	sv->center(sv->contentsWidth()/2, 0);
    }  

  return r;
}


void KViewPart::fileChanged(const QString &file)
{
  kdDebug() << "File changed: " << file << endl;

  if (file == m_file && watchAct->isChecked())
    multiPage->reload();
}


bool KViewPart::closeURL()
{
  watch.removeDir(m_file);
  watch.stopScan();

  KParts::ReadOnlyPart::closeURL();

  multiPage->closeURL();

  m_url = "";

  numberOfPages(0);
  checkActions();

  emit setWindowCaption("");

  return true;
}


void KViewPart::slotOrientation(int id)
{
  if (id == 0)
    setPaperSize(_paperWidth, _paperHeight);
  else
    setPaperSize(_paperHeight, _paperWidth);
}


void KViewPart::slotMedia(int id)
{
  double w=0.0, h=0.0;
  
  switch (id)
    {
    case 0:
      w = 29.7; h = 42.0;
      break;
    case 1:
      w = 21.0; h = 29.7;
      break;
    case 2:
      w = 14.85; h = 21.0;
      break;
    case 3:
      w = 21.59; h = 27.94;
      break;
    case 4:
      w = 21.50; h = 35.56;
      break;
    case 5:
      // TODO : paper size dialog!
      break;
    }

  if (w != 0.0 && h != 0.0)
    if (orientation->currentItem() == 0)
      setPaperSize(w, h);
    else
      setPaperSize(h, w);
  _paperWidth = w;
  _paperHeight = h;
}


void KViewPart::numberOfPages(int nr)
{
  _numberOfPages = nr;

  markList->clear();

  if (nr == 0)
    {
      // clear scroll box
      scrollBox->setPageSize(QSize(0,0));
      scrollBox->setViewSize(QSize(0,0));

      _currentPage = 0;

      return;
    }

  for (int i=0; i<nr; i++)
    markList->insertItem(QString("%1").arg(i), i);

  setPage(0);
}


void KViewPart::setPage(int page)
{
  if (!multiPage->gotoPage(page))
    return;

  _currentPage = page;

  markList->select(page);

  checkActions();
  updateScrollBox();
}


void KViewPart::prevPage()
{
  if (page() > 0)
    setPage(page()-1);
}


void KViewPart::nextPage()
{
  if (page()+1 < pages())
    setPage(page()+1);
}


void KViewPart::firstPage()
{
  setPage(0);
}


void KViewPart::lastPage()
{
  setPage(pages()-1);
}


void KViewPart::goToPage()
{
  _gotoDialog->show();
}


void KViewPart::slotGotoDialog(const QString &page)
{
  bool ok;
  int p = page.toInt(&ok);
  if (ok && p >= 0 && p < pages())
    setPage(p);
}


void KViewPart::pageSelected(int nr)
{
  if ((nr >= 0) && (nr<pages()))
    setPage(nr);
}


void KViewPart::zoomIn()
{
  _zoom = _zoom * 1.41;
  _zoom = multiPage->setZoom(_zoom);
  updateScrollBox();
}


void KViewPart::zoomOut()
{
  _zoom = _zoom / 1.41;
  if (_zoom < 0.1)
    _zoom = 0.1;
  _zoom = multiPage->setZoom(_zoom);
  updateScrollBox();
}


void KViewPart::fitToPage()
{
  double w, h;

  w = multiPage->zoomForWidth(pageSize().width());
  h = multiPage->zoomForHeight(pageSize().height());

  if (w < h)
    _zoom = w;
  else
    _zoom = h;
  if (_zoom < 0.1)
    _zoom = 0.1;
  _zoom = multiPage->setZoom(_zoom);
  updateScrollBox();
}


void KViewPart::fitSize()
{
  _zoom = multiPage->setZoom(1.0);
  updateScrollBox();
}


void KViewPart::fitToHeight()
{
  _zoom = multiPage->zoomForHeight(pageSize().height());
  if (_zoom < 0.1)
    _zoom = 0.1;
  _zoom = multiPage->setZoom(_zoom);
  updateScrollBox();
}


void KViewPart::fitToWidth()
{
  _zoom = multiPage->zoomForWidth(pageSize().width());
  if (_zoom < 0.1)
    _zoom = 0.1;
  _zoom = multiPage->setZoom(_zoom);
  updateScrollBox();
}


QSize KViewPart::pageSize()
{
  QRect r = multiPage->widget()->childrenRect();
  return QSize(r.width(), r.height());
}


void KViewPart::updateScrollBox()
{
  QScrollView *sv = multiPage->scrollView();
  scrollBox->setPageSize(QSize(sv->contentsWidth(), sv->contentsHeight()));
  scrollBox->setViewSize(QSize(sv->visibleWidth(), sv->visibleHeight()));
  scrollBox->setViewPos(QPoint(sv->contentsX(), sv->contentsY()));
}


void KViewPart::checkActions()
{
  bool doc = !url().isEmpty();

  backAct->setEnabled(doc && page() > 0);
  forwardAct->setEnabled(doc && page()+1 < pages());
  startAct->setEnabled(doc && page() > 0);
  endAct->setEnabled(doc && page()+1 < pages());
  gotoAct->setEnabled(doc && pages()>1);

  zoomInAct->setEnabled(doc);
  zoomOutAct->setEnabled(doc);

  fitAct->setEnabled(doc);
  fitPageAct->setEnabled(doc);
  fitHeightAct->setEnabled(doc);
  fitWidthAct->setEnabled(doc);

  media->setEnabled(doc);
  orientation->setEnabled(doc);

  printAction->setEnabled(doc);
  saveAction->setEnabled(doc);
}


void KViewPart::contentsMoving(int x, int y)
{
  QScrollView *sv = multiPage->scrollView();
  scrollBox->setPageSize(QSize(sv->contentsWidth(), sv->contentsHeight()));
  scrollBox->setViewSize(QSize(sv->visibleWidth(), sv->visibleHeight()));
  scrollBox->setViewPos(QPoint(x,y));
}


void KViewPart::scrollBoxChanged(QPoint np)
{
  multiPage->scrollView()->setContentsPos(np.x(), np.y());
}


bool KViewPart::eventFilter(QObject *obj, QEvent *ev)
{
  if (obj == this && ev->type() == QEvent::Resize)
    QTimer::singleShot(0, this, SLOT(updateScrollBox()));

  if (obj != this)
    {
      if (ev->type() == QEvent::MouseButtonPress)
	{
	  mousePos = ((QMouseEvent*)ev)->globalPos();
	  multiPage->scrollView()->setCursor(Qt::sizeAllCursor);
	}
      if (ev->type() == QEvent::MouseMove)
	{
	  QPoint newPos = ((QMouseEvent*)ev)->globalPos();
	  QPoint delta = mousePos - newPos;
	  multiPage->scrollView()->scrollBy(delta.x(), delta.y());
	  mousePos = newPos;
	}	    
      if (ev->type() == QEvent::MouseButtonRelease)
	multiPage->scrollView()->setCursor(Qt::arrowCursor);
    }

  return false;
}


void KViewPart::scrollUp()
{
  QScrollBar *sb = multiPage->scrollView()->verticalScrollBar();
  if (sb)
    sb->subtractLine();
  updateScrollBox();
}


void KViewPart::scrollDown()
{
  QScrollBar *sb = multiPage->scrollView()->verticalScrollBar();
  if (sb)
    sb->addLine();
  updateScrollBox();
}


void KViewPart::scrollLeft()
{
  QScrollBar *sb = multiPage->scrollView()->horizontalScrollBar();
  if (sb)
    sb->subtractLine();
  updateScrollBox();
}


void KViewPart::scrollRight()
{
  QScrollBar *sb = multiPage->scrollView()->horizontalScrollBar();
  if (sb)
    sb->addLine();
  updateScrollBox();
}


void KViewPart::scrollUpPage()
{
  QScrollBar *sb = multiPage->scrollView()->verticalScrollBar();
  if (sb)
    sb->subtractPage();
  updateScrollBox();
}


void KViewPart::scrollDownPage()
{
  QScrollBar *sb = multiPage->scrollView()->verticalScrollBar();
  if (sb)
    sb->addPage();
  updateScrollBox();
}


void KViewPart::scrollLeftPage()
{
  QScrollBar *sb = multiPage->scrollView()->horizontalScrollBar();
  if (sb)
    sb->subtractPage();
  updateScrollBox();
}


void KViewPart::scrollRightPage()
{
  QScrollBar *sb = multiPage->scrollView()->horizontalScrollBar();
  if (sb)
    sb->addPage();
  updateScrollBox();
}


void KViewPart::scrollTo(int x, int y)
{
  QScrollView *sv = multiPage->scrollView();
  sv->setContentsPos(x, y);
  updateScrollBox();
}


void KViewPart::updatePreview(bool avail)
{
  QPixmap pixmap(scrollBox->width(), scrollBox->height());
  QPainter p(&pixmap);

  if (showPreview->isChecked() && avail 
      && multiPage->preview(&p, scrollBox->width(), scrollBox->height()))
    scrollBox->setBackgroundPixmap(pixmap);
  else
    scrollBox->setBackgroundMode(QFrame::PaletteMid);
}


void KViewPart::slotPreview()
{
  updatePreview(true);
}


void KViewPart::slotPrint()
{
  QStrList pages;
  
  for (int i=0; i<markList->count(); i++)
    if (markList->isSelected(i))
      pages.append(QString("%1").arg(i));

  multiPage->print(pages, page());
}


void KViewPart::readSettings()
{
  KConfig *config = instance()->config();

  config->setGroup("GUI");
  
  showmarklist->setChecked(config->readBoolEntry("PageMarks", true));
  slotShowMarkList();

  watchAct->setChecked(config->readBoolEntry("WatchFile", true));
  showPreview->setChecked(config->readBoolEntry("ShowPreview", true));
}


void KViewPart::writeSettings()
{
  KConfig *config = instance()->config();
  
  config->setGroup("GUI");
  
  config->writeEntry("PageMarks", showmarklist->isChecked());
  config->writeEntry("WatchFile", watchAct->isChecked());
  config->writeEntry("ShowPreview", showPreview->isChecked());

  config->sync();
}


void KViewPart::readDown()
{
  QScrollBar *sb = multiPage->scrollView()->verticalScrollBar();
  if (sb)
    {
      if (sb->value() == sb->maxValue())
	{
	  nextPage();
	  QScrollView *sv = multiPage->scrollView();
	  scrollTo(sv->contentsX(), 0);
	}
      else
	sb->addPage();
    }
}


KViewPartExtension::KViewPartExtension(KViewPart *parent)
  : KParts::BrowserExtension( parent, "KViewPartExtension")
{
}
