/***************************************************************************
    copyright            : (C) 2008 by Robby Stephenson
    email                : robby@periapsis.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of version 2 of the GNU General Public License as  *
 *   published by the Free Software Foundation;                            *
 *                                                                         *
 ***************************************************************************/

//  before tellicosaxmporter.h because of TQT_NO_CAST_ASCII issues
#include "tellicoxmlhandler.h"
#include "tellicosaximporter.h"
#include "tellico_xml.h"
#include "../collectionfactory.h"
#include "../collections/bibtexcollection.h"
#include "../entry.h"
#include "../field.h"
#include "../imagefactory.h"
#include "../image.h"
#include "../isbnvalidator.h"
#include "../latin1literal.h"
#include "../tellico_strings.h"
#include "../tellico_kernel.h"
#include "../tellico_utils.h"
#include "../tellico_debug.h"
#include "../progressmanager.h"

#include <tdelocale.h>
#include <kmdcodec.h>
#include <kzip.h>
#include <tdeapplication.h>

#include <tqbuffer.h>
#include <tqfile.h>
#include <tqtimer.h>

using Tellico::Import::TellicoSaxImporter;

TellicoSaxImporter::TellicoSaxImporter(const KURL& url_, bool loadAllImages_) : DataImporter(url_),
    m_coll(0), m_loadAllImages(loadAllImages_), m_format(Unknown), m_modified(false),
    m_cancelled(false), m_hasImages(false), m_buffer(0), m_zip(0), m_imgDir(0) {
}

TellicoSaxImporter::TellicoSaxImporter(const TQString& text_) : DataImporter(text_),
    m_coll(0), m_loadAllImages(true), m_format(Unknown), m_modified(false),
    m_cancelled(false), m_hasImages(false), m_buffer(0), m_zip(0), m_imgDir(0) {
}

TellicoSaxImporter::~TellicoSaxImporter() {
  if(m_zip) {
    m_zip->close();
  }
  delete m_zip;
  m_zip = 0;
  delete m_buffer;
  m_buffer = 0;
}

Tellico::Data::CollPtr TellicoSaxImporter::collection() {
  if(m_coll) {
    return m_coll;
  }

  TQCString s; // read first 5 characters
  if(source() == URL) {
    if(!fileRef().open()) {
      return 0;
    }
    TQIODevice* f = fileRef().file();
    for(uint i = 0; i < 5; ++i) {
      s += static_cast<char>(f->getch());
    }
    f->reset();
  } else {
    if(data().size() < 5) {
      m_format = Error;
      return 0;
    }
    s = TQCString(data(), 6);
  }

  // need to decide if the data is xml text, or a zip file
  // if the first 5 characters are <?xml then treat it like text
  if(s[0] == '<' && s[1] == '?' && s[2] == 'x' && s[3] == 'm' && s[4] == 'l') {
    m_format = XML;
    loadXMLData(source() == URL ? fileRef().file()->readAll() : data(), true);
  } else {
    m_format = Zip;
    loadZipData();
  }
  return m_coll;
}

void TellicoSaxImporter::loadXMLData(const TQByteArray& data_, bool loadImages_) {
  ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true);
  item.setTotalSteps(data_.size());
  connect(&item, TQ_SIGNAL(signalCancelled(ProgressItem*)), TQ_SLOT(slotCancel()));
  ProgressItem::Done done(this);

  const bool showProgress = options() & ImportProgress;

  TellicoXMLHandler handler;
  handler.setLoadImages(loadImages_);

  TQXmlSimpleReader reader;
  reader.setContentHandler(&handler);

  TQXmlInputSource source;
  bool success = reader.parse(&source, true);

  const uint blockSize = data_.size()/100 + 1;
  uint pos = 0;
  TQByteArray block;

  while(success && !m_cancelled && pos < data_.size()) {
    uint size = TQMIN(blockSize, data_.size() - pos);
    block.setRawData(data_.data() + pos, size);
    source.setData(block);
    success = reader.parseContinue();
    block.resetRawData(data_.data() + pos, size);
    pos += blockSize;
    if(showProgress) {
      ProgressManager::self()->setProgress(this, pos);
      tdeApp->processEvents();
    }
  }

  if(!success) {
    m_format = Error;
    TQString error;
    if(!url().isEmpty()) {
      error = i18n(errorLoad).arg(url().fileName()) + TQChar('\n');
    }
    error += handler.errorString();
    myDebug() << error << endl;
    setStatusMessage(error);
    return;
  }

  if(!m_cancelled) {
    m_hasImages = handler.hasImages();
    m_coll = handler.collection();
  }
}

void TellicoSaxImporter::loadZipData() {
  delete m_buffer;
  delete m_zip;
  if(source() == URL) {
    m_buffer = 0;
    m_zip = new KZip(fileRef().fileName());
  } else {
    m_buffer = new TQBuffer(data());
    m_zip = new KZip(m_buffer);
  }
  if(!m_zip->open(IO_ReadOnly)) {
    setStatusMessage(i18n(errorLoad).arg(url().fileName()));
    m_format = Error;
    delete m_zip;
    m_zip = 0;
    delete m_buffer;
    m_buffer = 0;
    return;
  }

  const KArchiveDirectory* dir = m_zip->directory();
  if(!dir) {
    TQString str = i18n(errorLoad).arg(url().fileName()) + TQChar('\n');
    str += i18n("The file is empty.");
    setStatusMessage(str);
    m_format = Error;
    m_zip->close();
    delete m_zip;
    m_zip = 0;
    delete m_buffer;
    m_buffer = 0;
    return;
  }

  // main file was changed from bookcase.xml to tellico.xml as of version 0.13
  const KArchiveEntry* entry = dir->entry(TQString::fromLatin1("tellico.xml"));
  if(!entry) {
    entry = dir->entry(TQString::fromLatin1("bookcase.xml"));
  }
  if(!entry || !entry->isFile()) {
    TQString str = i18n(errorLoad).arg(url().fileName()) + TQChar('\n');
    str += i18n("The file contains no collection data.");
    setStatusMessage(str);
    m_format = Error;
    m_zip->close();
    delete m_zip;
    m_zip = 0;
    delete m_buffer;
    m_buffer = 0;
    return;
  }

  const TQByteArray xmlData = static_cast<const KArchiveFile*>(entry)->data();
  loadXMLData(xmlData, false);
  if(!m_coll) {
    m_format = Error;
    m_zip->close();
    delete m_zip;
    m_zip = 0;
    delete m_buffer;
    m_buffer = 0;
    return;
  }

  if(m_cancelled) {
    m_zip->close();
    delete m_zip;
    m_zip = 0;
    delete m_buffer;
    m_buffer = 0;
    return;
  }

  const KArchiveEntry* imgDirEntry = dir->entry(TQString::fromLatin1("images"));
  if(!imgDirEntry || !imgDirEntry->isDirectory()) {
    m_zip->close();
    delete m_zip;
    m_zip = 0;
    delete m_buffer;
    m_buffer = 0;
    return;
  }
  m_imgDir = static_cast<const KArchiveDirectory*>(imgDirEntry);
  m_images.clear();
  m_images.add(m_imgDir->entries());
  m_hasImages = !m_images.isEmpty();

  // if all the images are not to be loaded, then we're done
  if(!m_loadAllImages) {
//    myLog() << "TellicoSaxImporter::loadZipData() - delayed loading for " << m_images.count() << " images" << endl;
    return;
  }

  const TQStringList images = static_cast<const KArchiveDirectory*>(imgDirEntry)->entries();
  const uint stepSize = TQMAX(s_stepSize, images.count()/100);

  uint j = 0;
  for(TQStringList::ConstIterator it = images.begin(); !m_cancelled && it != images.end(); ++it, ++j) {
    const KArchiveEntry* file = m_imgDir->entry(*it);
    if(file && file->isFile()) {
      ImageFactory::addImage(static_cast<const KArchiveFile*>(file)->data(),
                             (*it).section('.', -1).upper(), (*it));
      m_images.remove(*it);
    }
    if(j%stepSize == 0) {
      tdeApp->processEvents();
    }
  }

  if(m_images.isEmpty()) {
    // give it some time
    TQTimer::singleShot(3000, this, TQ_SLOT(deleteLater()));
  }
}

bool TellicoSaxImporter::hasImages() const {
  return m_hasImages;
}

bool TellicoSaxImporter::loadImage(const TQString& id_) {
//  myLog() << "TellicoSaxImporter::loadImage() - id =  " << id_ << endl;
  if(m_format != Zip || !m_imgDir) {
    return false;
  }
  const KArchiveEntry* file = m_imgDir->entry(id_);
  if(!file || !file->isFile()) {
    return false;
  }
  TQString newID = ImageFactory::addImage(static_cast<const KArchiveFile*>(file)->data(),
                                         id_.section('.', -1).upper(), id_);
  m_images.remove(id_);
  if(m_images.isEmpty()) {
    // give it some time
    TQTimer::singleShot(3000, this, TQ_SLOT(deleteLater()));
  }
  return !newID.isEmpty();
}

void TellicoSaxImporter::slotCancel() {
  m_cancelled = true;
  m_format = Cancel;
}

#include "tellicosaximporter.moc"
