//C-  -*- C++ -*-
//C- -------------------------------------------------------------------
//C- DjVuLibre-3.5
//C- Copyright (c) 2002  Leon Bottou and Yann Le Cun.
//C- Copyright (c) 2001  AT&T
//C-
//C- This software is subject to, and may be distributed under, the
//C- GNU General Public License, either Version 2 of the license,
//C- or (at your option) any later version. The license should have
//C- accompanied the software or you may obtain a copy of the license
//C- from the Free Software Foundation at http://www.fsf.org .
//C-
//C- This program is distributed in the hope that it will be useful,
//C- but WITHOUT ANY WARRANTY; without even the implied warranty of
//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//C- GNU General Public License for more details.
//C- 
//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library from
//C- Lizardtech Software.  Lizardtech Software has authorized us to
//C- replace the original DjVu(r) Reference Library notice by the following
//C- text (see doc/lizard2002.djvu and doc/lizardtech2007.djvu):
//C-
//C-  ------------------------------------------------------------------
//C- | DjVu (r) Reference Library (v. 3.5)
//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.
//C- | The DjVu Reference Library is protected by U.S. Pat. No.
//C- | 6,058,214 and patents pending.
//C- |
//C- | This software is subject to, and may be distributed under, the
//C- | GNU General Public License, either Version 2 of the license,
//C- | or (at your option) any later version. The license should have
//C- | accompanied the software or you may obtain a copy of the license
//C- | from the Free Software Foundation at http://www.fsf.org .
//C- |
//C- | The computer code originally released by LizardTech under this
//C- | license and unmodified by other parties is deemed "the LIZARDTECH
//C- | ORIGINAL CODE."  Subject to any third party intellectual property
//C- | claims, LizardTech grants recipient a worldwide, royalty-free, 
//C- | non-exclusive license to make, use, sell, or otherwise dispose of 
//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the 
//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU 
//C- | General Public License.   This grant only confers the right to 
//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to 
//C- | the extent such infringement is reasonably necessary to enable 
//C- | recipient to make, have made, practice, sell, or otherwise dispose 
//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to 
//C- | any greater extent that may be necessary to utilize further 
//C- | modifications or combinations.
//C- |
//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//C- +------------------------------------------------------------------

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#if NEED_GNUG_PRAGMAS
# pragma implementation
#endif

#include "DjVuFile.h"
#include "IFFByteStream.h"
#include "GOS.h"
#include "MMRDecoder.h"
#ifdef NEED_JPEG_DECODER
#include "JPEGDecoder.h"
#endif
#include "DjVuAnno.h"
#include "DjVuText.h"
#include "DataPool.h"
#include "JB2Image.h"
#include "IW44Image.h"
#include "DjVuNavDir.h"
#ifndef NEED_DECODER_ONLY
#include "BSByteStream.h"
#endif // NEED_DECODER_ONLY

#include "debug.h"


#ifdef HAVE_NAMESPACES
namespace DJVU {
# ifdef NOT_DEFINED // Just to fool emacs c++ mode
}
#endif
#endif


#define STRINGIFY(x) STRINGIFY_(x)
#define STRINGIFY_(x) #x


#define REPORT_EOF(x) \
  {G_TRY{G_THROW( ByteStream::EndOfFile );}G_CATCH(ex){report_error(ex,(x));}G_ENDCATCH;}

static GP<GPixmap> (*djvu_decode_codec)(ByteStream &bs)=0;

class ProgressByteStream : public ByteStream
{
public:
  ProgressByteStream(const GP<ByteStream> & xstr) : str(xstr),
    last_call_pos(0) {}
  virtual ~ProgressByteStream() {}

  virtual size_t read(void *buffer, size_t size)
  {
    int rc=0;
	   // G_TRY {} CATCH; block here is merely to avoid egcs internal error
    G_TRY {
      int cur_pos=str->tell();
      if (progress_cb && (last_call_pos/256!=cur_pos/256))
      {
        progress_cb(cur_pos, progress_cl_data);
        last_call_pos=cur_pos;
      }
      rc=str->read(buffer, size);
    } G_CATCH_ALL {
      G_RETHROW;
    } G_ENDCATCH;
    return rc;
  }
  virtual size_t write(const void *buffer, size_t size)
  {
    return str->write(buffer, size);
  }
  virtual int seek(long offset, int whence = SEEK_SET, bool nothrow=false)
  {
    return str->seek(offset, whence);
  }
  virtual long tell(void ) const { return str->tell(); }

  void		set_progress_cb(void (* xprogress_cb)(int, void *),
  void * xprogress_cl_data)
  {
    progress_cb=xprogress_cb;
    progress_cl_data=xprogress_cl_data;
  }
private:
  GP<ByteStream> str;
  void		* progress_cl_data;
  void		(* progress_cb)(int pos, void *);
  int		last_call_pos;
  
  // Cancel C++ default stuff
  ProgressByteStream & operator=(const ProgressByteStream &);
};


DjVuFile::DjVuFile()
: file_size(0), recover_errors(ABORT), verbose_eof(false), chunks_number(-1),
initialized(false)
{
}

void
DjVuFile::check() const
{
  if (!initialized)
    G_THROW( ERR_MSG("DjVuFile.not_init") );
}

GP<DjVuFile>
DjVuFile::create(
  const GP<ByteStream> & str, const ErrorRecoveryAction recover_errors,
  const bool verbose_eof )
{
  DjVuFile *file=new DjVuFile();
  GP<DjVuFile> retval=file;
  file->set_recover_errors(recover_errors);
  file->set_verbose_eof(verbose_eof);
  file->init(str);
  return retval;
}

void 
DjVuFile::init(const GP<ByteStream> & str)
{
  DEBUG_MSG("DjVuFile::DjVuFile(): ByteStream constructor\n");
  DEBUG_MAKE_INDENT(3);
  
  if (initialized)
    G_THROW( ERR_MSG("DjVuFile.2nd_init") );
  if (!get_count())
    G_THROW( ERR_MSG("DjVuFile.not_secured") );
  
  file_size=0;
  decode_thread=0;
  
  // Read the data from the stream
  data_pool=DataPool::create(str);
  
  // Construct some dummy URL
  GUTF8String buffer;
  buffer.format("djvufile:/%p.djvu", this);
  DEBUG_MSG("DjVuFile::DjVuFile(): url is "<<(const char *)buffer<<"\n");
  url=GURL::UTF8(buffer);
  
  // Set it here because trigger will call other DjVuFile's functions
  initialized=true;
  
  // Add (basically - call) the trigger
  data_pool->add_trigger(-1, static_trigger_cb, this);
}

GP<DjVuFile>
DjVuFile::create(
  const GURL & xurl, GP<DjVuPort> port, 
  const ErrorRecoveryAction recover_errors, const bool verbose_eof ) 
{
  DjVuFile *file=new DjVuFile();
  GP<DjVuFile> retval=file;
  file->set_recover_errors(recover_errors);
  file->set_verbose_eof(verbose_eof);
  file->init(xurl,port);
  return retval;
}

void
DjVuFile::init(const GURL & xurl, GP<DjVuPort> port) 
{
  DEBUG_MSG("DjVuFile::init(): url='" << xurl << "'\n");
  DEBUG_MAKE_INDENT(3);
  
  if (initialized)
    G_THROW( ERR_MSG("DjVuFile.2nd_init") );
  if (!get_count())
    G_THROW( ERR_MSG("DjVuFile.not_secured") );
  if (xurl.is_empty())
    G_THROW( ERR_MSG("DjVuFile.empty_URL") );
  
  url = xurl;
  DEBUG_MSG("DjVuFile::DjVuFile(): url is "<<(const char *)url<<"\n");
  file_size=0;
  decode_thread=0;
  
  DjVuPortcaster * pcaster=get_portcaster();
  
  // We need it 'cause we're waiting for our own termination in stop_decode()
  pcaster->add_route(this, this);
  if (!port)
    port = simple_port = new DjVuSimplePort();
  pcaster->add_route(this, port);
  
  // Set it here because trigger will call other DjVuFile's functions
  initialized=true;
  
  if (!(data_pool=DataPool::create(pcaster->request_data(this, url))))
    G_THROW( ERR_MSG("DjVuFile.no_data") "\t"+url.get_string());
  data_pool->add_trigger(-1, static_trigger_cb, this);
}

DjVuFile::~DjVuFile(void)
{
  DEBUG_MSG("DjVuFile::~DjVuFile(): destroying...\n");
  DEBUG_MAKE_INDENT(3);
  
  // No more messages. They may result in adding this file to a cache
  // which will be very-very bad as we're being destroyed
  get_portcaster()->del_port(this);
  
  // Unregister the trigger (we don't want it to be called and attempt
  // to access the destroyed object)
  if (data_pool)
    data_pool->del_trigger(static_trigger_cb, this);
  
  // We don't have to wait for decoding to finish here. It's already
  // finished (we know it because there is a "life saver" in the
  // thread function)  -- but we need to delete it
  delete decode_thread; decode_thread=0;
}

void
DjVuFile::reset(void)
{
   flags.enter();
   info = 0; 
   anno = 0; 
   text = 0; 
   meta = 0; 
   bg44 = 0; 
   fgbc = 0;
   fgjb = 0; 
   fgjd = 0;
   fgpm = 0;
   dir  = 0; 
   description = ""; 
   mimetype = "";
   flags=(flags&(ALL_DATA_PRESENT|DECODE_STOPPED|DECODE_FAILED));
   flags.leave();
}

unsigned int
DjVuFile::get_memory_usage(void) const
{
   unsigned int size=sizeof(*this);
   if (info) size+=info->get_memory_usage();
   if (bg44) size+=bg44->get_memory_usage();
   if (fgjb) size+=fgjb->get_memory_usage();
   if (fgpm) size+=fgpm->get_memory_usage();
   if (fgbc) size+=fgbc->size()*sizeof(int);
   if (anno) size+=anno->size();
   if (meta) size+=meta->size();
   if (dir) size+=dir->get_memory_usage();
   return size;
}

GPList<DjVuFile>
DjVuFile::get_included_files(bool only_created)
{
  check();
  if (!only_created && !are_incl_files_created())
    process_incl_chunks();
  
  GCriticalSectionLock lock(&inc_files_lock);
  GPList<DjVuFile> list=inc_files_list;	// Get a copy when locked
  return list;
}

void
DjVuFile::wait_for_chunk(void)
// Will return after a chunk has been decoded
{
  check();
  DEBUG_MSG("DjVuFile::wait_for_chunk() called\n");
  DEBUG_MAKE_INDENT(3);
  chunk_mon.enter();
  chunk_mon.wait();
  chunk_mon.leave();
}

bool
DjVuFile::wait_for_finish(bool self)
// if self==TRUE, will block until decoding of this file is over
// if self==FALSE, will block until decoding of a child (direct
// or indirect) is over.
// Will return FALSE if there is nothing to wait for. TRUE otherwise
{
  DEBUG_MSG("DjVuFile::wait_for_finish():  self=" << self <<"\n");
  DEBUG_MAKE_INDENT(3);
  
  check();
  
  if (self)
  {
    // It's best to check for self termination using flags. The reason
    // is that finish_mon is updated in a DjVuPort function, which
    // will not be called if the object is being destroyed
    GMonitorLock lock(&flags);
    if (is_decoding())
    {
      while(is_decoding()) flags.wait();
      DEBUG_MSG("got it\n");
      return 1;
    }
  } else
  {
    // By locking the monitor, we guarantee that situation doesn't change
    // between the moments when we check for pending finish events
    // and when we actually run wait(). If we don't lock, the last child
    // may terminate in between, and we'll wait forever.
    //
    // Locking is required by GMonitor interface too, btw.
    GMonitorLock lock(&finish_mon);
    GP<DjVuFile> file;
    {
      GCriticalSectionLock lock(&inc_files_lock);
      for(GPosition pos=inc_files_list;pos;++pos)
      {
        GP<DjVuFile> & f=inc_files_list[pos];
        if (f->is_decoding())
        {
          file=f; break;
        }
      }
    }
    if (file)
    {
      finish_mon.wait();
      DEBUG_MSG("got it\n");
      return 1;
    }
  }
  DEBUG_MSG("nothing to wait for\n");
  return 0;
}

void
DjVuFile::notify_chunk_done(const DjVuPort *, const GUTF8String &)
{
  check();
  chunk_mon.enter();
  chunk_mon.broadcast();
  chunk_mon.leave();
}

void
DjVuFile::notify_file_flags_changed(const DjVuFile * src,
                                    long set_mask, long clr_mask)
{
  check();
  if (set_mask & (DECODE_OK | DECODE_FAILED | DECODE_STOPPED))
  {
    // Signal threads waiting for file termination
    finish_mon.enter();
    finish_mon.broadcast();
    finish_mon.leave();
    
    // In case a thread is still waiting for a chunk
    chunk_mon.enter();
    chunk_mon.broadcast();
    chunk_mon.leave();
  }
  
  if ((set_mask & ALL_DATA_PRESENT) && src!=this &&
    are_incl_files_created() && is_data_present())
  {
    if (src!=this && are_incl_files_created() && is_data_present())
    {
      // Check if all children have data
      bool all=true;
      {
        GCriticalSectionLock lock(&inc_files_lock);
        for(GPosition pos=inc_files_list;pos;++pos)
          if (!inc_files_list[pos]->is_all_data_present())
          {
            all=false;
            break;
          }
      }
      if (all)
      {
        DEBUG_MSG("Just got ALL data for '" << url << "'\n");
        flags|=ALL_DATA_PRESENT;
        get_portcaster()->notify_file_flags_changed(this, ALL_DATA_PRESENT, 0);
      }
    }
  }
}

void
DjVuFile::static_decode_func(void * cl_data)
{
  DjVuFile * th=(DjVuFile *) cl_data;
  
  /* Please do not undo this life saver. If you do then try to resolve the
  following conflict first:
  1. Decoding starts and there is only one external reference
  to the DjVuFile.
  2. Decoding proceeds and calls DjVuPortcaster::notify_error(),
  which creates inside a temporary GP<DjVuFile>.
  3. While notify_error() is running, the only external reference
  is lost, but the DjVuFile is still alive (remember the
  temporary GP<>?)
  4. The notify_error() returns, the temporary GP<> gets destroyed
  and the DjVuFile is attempting to destroy right in the middle
  of the decoding thread. This is either a dead block (waiting
  for the termination of the decoding from the ~DjVuFile() called
  from the decoding thread) or coredump. */
  GP<DjVuFile> life_saver=th;
  th->decode_life_saver=0;
  G_TRY {
    th->decode_func();
  } G_CATCH_ALL {
  } G_ENDCATCH;
}

void
DjVuFile::decode_func(void)
{
  check();
  DEBUG_MSG("DjVuFile::decode_func() called, url='" << url << "'\n");
  DEBUG_MAKE_INDENT(3);
  
  DjVuPortcaster * pcaster=get_portcaster();
  
  G_TRY {
    const GP<ByteStream> decode_stream(decode_data_pool->get_stream());
    ProgressByteStream *pstr=new ProgressByteStream(decode_stream);
    const GP<ByteStream> gpstr(pstr);
    pstr->set_progress_cb(progress_cb, this);
    
    decode(gpstr);
    
    // Wait for all child files to finish
    while(wait_for_finish(0))
    	continue;
    
    DEBUG_MSG("waiting for children termination\n");
    // Check for termination status
    GCriticalSectionLock lock(&inc_files_lock);
    for(GPosition pos=inc_files_list;pos;++pos)
    {
      GP<DjVuFile> & f=inc_files_list[pos];
      if (f->is_decode_failed())
	G_THROW( ERR_MSG("DjVuFile.decode_fail") );
      if (f->is_decode_stopped())
	G_THROW( DataPool::Stop );
      if (!f->is_decode_ok())
	{
	  DEBUG_MSG("this_url='" << url << "'\n");
	  DEBUG_MSG("incl_url='" << f->get_url() << "'\n");
	  DEBUG_MSG("decoding=" << f->is_decoding() << "\n");
	  DEBUG_MSG("status='" << f->get_flags() << "\n");
	  G_THROW( ERR_MSG("DjVuFile.not_finished") );
	}
    }
  } G_CATCH(exc) {
    G_TRY {
      if (!exc.cmp_cause(DataPool::Stop))
	{
	  flags.enter();
	  flags = (flags & ~DECODING) | DECODE_STOPPED;
	  flags.leave();
	  pcaster->notify_status(this, GUTF8String(ERR_MSG("DjVuFile.stopped"))
				 + GUTF8String("\t") + GUTF8String(url));
	  pcaster->notify_file_flags_changed(this, DECODE_STOPPED, DECODING);
	} else
	{
	  flags.enter();
	  flags = (flags & ~DECODING) | DECODE_FAILED;
	  flags.leave();
	  pcaster->notify_status(this, GUTF8String(ERR_MSG("DjVuFile.failed"))
				 + GUTF8String("\t") + GUTF8String(url));
	  pcaster->notify_error(this, exc.get_cause());
	  pcaster->notify_file_flags_changed(this, DECODE_FAILED, DECODING);
	}
    } G_CATCH_ALL
	{
	  DEBUG_MSG("******* Oops. Almost missed an exception\n");
	} G_ENDCATCH;
  } G_ENDCATCH;

  decode_data_pool->clear_stream();
  G_TRY {
    if (flags.test_and_modify(DECODING, 0, DECODE_OK | INCL_FILES_CREATED, DECODING))
      pcaster->notify_file_flags_changed(this, DECODE_OK | INCL_FILES_CREATED, 
                                         DECODING);
  } G_CATCH_ALL {} G_ENDCATCH;
  DEBUG_MSG("decoding thread for url='" << url << "' ended\n");
}

GP<DjVuFile>
DjVuFile::process_incl_chunk(ByteStream & str, int file_num)
{
  check();
  DEBUG_MSG("DjVuFile::process_incl_chunk(): processing INCL chunk...\n");
  DEBUG_MAKE_INDENT(3);
  
  DjVuPortcaster * pcaster=get_portcaster();
  
  GUTF8String incl_str;
  char buffer[1024];
  int length;
  while((length=str.read(buffer, 1024)))
    incl_str+=GUTF8String(buffer, length);
  
  // Eat '\n' in the beginning and at the end
  while(incl_str.length() && incl_str[0]=='\n')
  {
    incl_str=incl_str.substr(1,(unsigned int)(-1));
  }
  while(incl_str.length()>0 && incl_str[(int)incl_str.length()-1]=='\n')
  {
    incl_str.setat(incl_str.length()-1, 0);
  }
  
  if (incl_str.length()>0)
  {
    if (strchr(incl_str, '/'))
      G_THROW( ERR_MSG("DjVuFile.malformed") );
    
    DEBUG_MSG("incl_str='" << incl_str << "'\n");
    
    GURL incl_url=pcaster->id_to_url(this, incl_str);
    if (incl_url.is_empty())	// Fallback. Should never be used.
      incl_url=GURL::UTF8(incl_str,url.base());
    
    // Now see if there is already a file with this *name* created
    {
      GCriticalSectionLock lock(&inc_files_lock);
      GPosition pos;
      for(pos=inc_files_list;pos;++pos)
      {
        if (inc_files_list[pos]->url.fname()==incl_url.fname())
           break;
      }
      if (pos)
        return inc_files_list[pos];
    }
    
    // No. We have to request a new file
    GP<DjVuFile> file;
    G_TRY
    {
      file=pcaster->id_to_file(this, incl_str);
    }
    G_CATCH(ex)
    {
      unlink_file(incl_str);
      // In order to keep compatibility with the previous
      // release of the DjVu plugin, we will not interrupt
      // decoding here. We will just report the error.
      // NOTE, that it's now the responsibility of the
      // decoder to resolve all chunk dependencies, and
      // abort decoding if necessary.
      
      get_portcaster()->notify_error(this,ex.get_cause());
      return 0;
    }
    G_ENDCATCH;
    if (!file)
    {
      G_THROW( ERR_MSG("DjVuFile.no_create") "\t"+incl_str);
    }
    if (recover_errors!=ABORT)
      file->set_recover_errors(recover_errors);
    if (verbose_eof)
      file->set_verbose_eof(verbose_eof);
    pcaster->add_route(file, this);
    
    // We may have been stopped. Make sure the child will be stopped too.
    if (flags & STOPPED)
      file->stop(false);
    if (flags & BLOCKED_STOPPED)
      file->stop(true);
    
    // Lock the list again and check if the file has already been
    // added by someone else
    {
      GCriticalSectionLock lock(&inc_files_lock);
      GPosition pos;
      for(pos=inc_files_list;pos;++pos)
      {
        if (inc_files_list[pos]->url.fname()==incl_url.fname())
          break;
      }
      if (pos)
      {
        file=inc_files_list[pos];
      } else if (file_num<0 || !(pos=inc_files_list.nth(file_num)))
      {
        inc_files_list.append(file);
      } else 
      {
        inc_files_list.insert_before(pos, file);
      }
    }
    return file;
  }
  return 0;
}


void
DjVuFile::report_error(const GException &ex,bool throw_errors)
{
  data_pool->clear_stream();
  if((!verbose_eof)|| (ex.cmp_cause(ByteStream::EndOfFile)))
  {
    if(throw_errors)
    {
      G_EMTHROW(ex);
    }else
    {
      get_portcaster()->notify_error(this,ex.get_cause());
    }
  }else
  {
    GURL url=get_url();
    GUTF8String url_str=url.get_string();
//    if (url.is_local_file_url())
//      url_str=url.filename();
    
    GUTF8String msg = GUTF8String( ERR_MSG("DjVuFile.EOF") "\t") + url;
    if(throw_errors)
    {
      G_EMTHROW(GException(msg, ex.get_file(), ex.get_line(), 
                           ex.get_function() ));
    }else
    {
      get_portcaster()->notify_error(this,msg);
    }
  }
}

void
DjVuFile::process_incl_chunks(void)
// This function may block for data
// NOTE: It may be called again when INCL_FILES_CREATED is set.
// It happens in insert_file() when it has modified the data
// and wants to create the actual file
{
  DEBUG_MSG("DjVuFile::process_incl_chunks(void)\n");
  DEBUG_MAKE_INDENT(3);
  check();
  
  int incl_cnt=0;
  
  const GP<ByteStream> str(data_pool->get_stream());
  GUTF8String chkid;
  const GP<IFFByteStream> giff(IFFByteStream::create(str));
  IFFByteStream &iff=*giff;
  if (iff.get_chunk(chkid))
  {
    int chunks=0;
    int last_chunk=0;
    G_TRY
    {
      int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
      int chksize;
      for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
      {
        chunks++;
        if (chkid=="INCL")
        {
          G_TRY
          {
            process_incl_chunk(*iff.get_bytestream(), incl_cnt++);
          }
          G_CATCH(ex);
          {
            report_error(ex,(recover_errors <= SKIP_PAGES));
          }
          G_ENDCATCH;
        }else if(chkid=="FAKE")
        {
          set_needs_compression(true);
          set_can_compress(true);
        }else if(chkid=="BGjp")
        {
          set_can_compress(true);
        }else if(chkid=="Smmr")
        {
          set_can_compress(true);
        }
        iff.seek_close_chunk();
      }
      if (chunks_number < 0) chunks_number=last_chunk;
    }
    G_CATCH(ex)
    {	
      if (chunks_number < 0)
        chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
      report_error(ex,(recover_errors <= SKIP_PAGES));
    }
    G_ENDCATCH;
  }
  flags|=INCL_FILES_CREATED;
  data_pool->clear_stream();
}

GP<JB2Dict>
DjVuFile::static_get_fgjd(void *arg)
{
  DjVuFile *file = (DjVuFile*)arg;
  return file->get_fgjd(1);
}

GP<JB2Dict>
DjVuFile::get_fgjd(int block)
{
  check();
  
  // Simplest case
  if (fgjd)
    return fgjd;
  // Check wether included files
  chunk_mon.enter();
  G_TRY {
    for(;;)
    {
      int active = 0;
      GPList<DjVuFile> incs = get_included_files();
      for (GPosition pos=incs.firstpos(); pos; ++pos)
      {
        GP<DjVuFile> file = incs[pos];
        if (file->is_decoding())
          active = 1;
        GP<JB2Dict> fgjd = file->get_fgjd();
        if (fgjd)
        {
          chunk_mon.leave();
          return fgjd;
        }
      }
      // Exit if non-blocking mode
      if (! block)
        break;
      // Exit if there is no decoding activity
      if (! active)
        break;
      // Wait until a new chunk gets decoded
      wait_for_chunk();
    }
  } G_CATCH_ALL {
    chunk_mon.leave();
    G_RETHROW;
  } G_ENDCATCH;
  chunk_mon.leave();
  if (is_decode_stopped()) G_THROW( DataPool::Stop );
  return 0;
}

int
DjVuFile::get_dpi(int w, int h)
{
  int dpi=0, red=1;
  if (info)
  {
    for(red=1; red<=12; red++)
      if ((info->width+red-1)/red==w)
        if ((info->height+red-1)/red==h)
          break;
    if (red>12)
      G_THROW( ERR_MSG("DjVuFile.corrupt_BG44") );
    dpi=info->dpi;
  }
  return (dpi ? dpi : 300)/red;
}

static inline bool
is_info(const GUTF8String &chkid)
{
  return (chkid=="INFO");
}

static inline bool
is_annotation(const GUTF8String &chkid)
{
  return (chkid=="ANTa" ||
    chkid=="ANTz" ||
    chkid=="FORM:ANNO" ); 
}

static inline bool
is_text(const GUTF8String &chkid)
{
  return (chkid=="TXTa" || chkid=="TXTz");
}

static inline bool
is_meta(const GUTF8String &chkid)
{
  return (chkid=="METa" || chkid=="METz");
}


GUTF8String
DjVuFile::decode_chunk( const GUTF8String &id, const GP<ByteStream> &gbs,
  bool djvi, bool djvu, bool iw44)
{
  DEBUG_MSG("DjVuFile::decode_chunk()\n");
  ByteStream &bs=*gbs;
  check();
  
  // If this object is referenced by only one GP<> pointer, this
  // pointer should be the "life_saver" created by the decoding thread.
  // If it is the only GP<> pointer, then nobody is interested in the
  // results of the decoding and we can abort now with #DataPool::Stop#
  if (get_count()==1)
    G_THROW( DataPool::Stop );
  
  GUTF8String desc = ERR_MSG("DjVuFile.unrecog_chunk");
  GUTF8String chkid = id;
  DEBUG_MSG("DjVuFile::decode_chunk() : decoding " << id << "\n");
  
  // INFO  (information chunk for djvu page)
  if (is_info(chkid) && (djvu || djvi))
  {
    if (info)
      G_THROW( ERR_MSG("DjVuFile.corrupt_dupl") );
    if (djvi)
      G_THROW( ERR_MSG("DjVuFile.corrupt_INFO") );
    // DjVuInfo::decode no longer throws version exceptions
    GP<DjVuInfo> xinfo=DjVuInfo::create();
    xinfo->decode(bs);
    info = xinfo;
    desc.format( ERR_MSG("DjVuFile.page_info") );
    // Consistency checks (previously in DjVuInfo::decode)
    if (info->width<0 || info->height<0)
      G_THROW( ERR_MSG("DjVuFile.corrupt_zero") );
    if (info->version >= DJVUVERSION_TOO_NEW)
      G_THROW( ERR_MSG("DjVuFile.new_version") "\t" 
               STRINGIFY(DJVUVERSION_TOO_NEW) );
  }
  
  // INCL (inclusion chunk)
  else if (chkid == "INCL" && (djvi || djvu || iw44))
  {
    GP<DjVuFile> file=process_incl_chunk(bs);
    if (file)
    {
      int decode_was_already_started = 1;
      {
        GMonitorLock lock(&file->flags);
          // Start decoding
        if(file->resume_decode())
        {
          decode_was_already_started = 0;
        }
      }
      // Send file notifications if previously started
      if (decode_was_already_started)
      {
        // May send duplicate notifications...
        if (file->is_decode_ok())
          get_portcaster()->notify_file_flags_changed(file, DECODE_OK, 0);
        else if (file->is_decode_failed())
          get_portcaster()->notify_file_flags_changed(file, DECODE_FAILED, 0);
      }
      desc.format( ERR_MSG("DjVuFile.indir_chunk1") "\t" + file->get_url().fname() );
    } else
      desc.format( ERR_MSG("DjVuFile.indir_chunk2") );
  }
  
  // Djbz (JB2 Dictionary)
  else if (chkid == "Djbz" && (djvu || djvi))
  {
    if (this->fgjd)
      G_THROW( ERR_MSG("DjVuFile.dupl_Dxxx") );
    if (this->fgjd)
      G_THROW( ERR_MSG("DjVuFile.Dxxx_after_Sxxx") );
    GP<JB2Dict> fgjd = JB2Dict::create();
    fgjd->decode(gbs);
    this->fgjd = fgjd;
    desc.format( ERR_MSG("DjVuFile.shape_dict") "\t%d", fgjd->get_shape_count() );
  } 
  
  // Sjbz (JB2 encoded mask)
  else if (chkid=="Sjbz" && (djvu || djvi))
  {
    if (this->fgjb)
      G_THROW( ERR_MSG("DjVuFile.dupl_Sxxx") );
    GP<JB2Image> fgjb=JB2Image::create();
    // ---- begin hack
    if (info && info->version <=18)
      fgjb->reproduce_old_bug = true;
    // ---- end hack
    fgjb->decode(gbs, static_get_fgjd, (void*)this);
    this->fgjb = fgjb;
    desc.format( ERR_MSG("DjVuFile.fg_mask") "\t%d\t%d\t%d",
      fgjb->get_width(), fgjb->get_height(),
      get_dpi(fgjb->get_width(), fgjb->get_height()));
  }
  
  // Smmr (MMR-G4 encoded mask)
  else if (chkid=="Smmr" && (djvu || djvi))
  {
    if (this->fgjb)
      G_THROW( ERR_MSG("DjVuFile.dupl_Sxxx") );
    set_can_compress(true);
    this->fgjb = MMRDecoder::decode(gbs);
    desc.format( ERR_MSG("DjVuFile.G4_mask") "\t%d\t%d\t%d",
      fgjb->get_width(), fgjb->get_height(),
      get_dpi(fgjb->get_width(), fgjb->get_height()));
  }
  
  // BG44 (background wavelets)
  else if (chkid == "BG44" && (djvu || djvi))
  {
    if (!bg44)
    {
      if (bgpm)
        G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
      // First chunk
      GP<IW44Image> bg44=IW44Image::create_decode(IW44Image::COLOR);
      bg44->decode_chunk(gbs);
      this->bg44 = bg44;
      desc.format( ERR_MSG("DjVuFile.IW44_bg1") "\t%d\t%d\t%d",
		      bg44->get_width(), bg44->get_height(),
          get_dpi(bg44->get_width(), bg44->get_height()));
    } 
    else
    {
      // Refinement chunks
      GP<IW44Image> bg44 = this->bg44;
      bg44->decode_chunk(gbs);
      desc.format( ERR_MSG("DjVuFile.IW44_bg2") "\t%d\t%d",
		      bg44->get_serial(), get_dpi(bg44->get_width(), bg44->get_height()));
    }
  }
  
  // FG44 (foreground wavelets)
  else if (chkid == "FG44" && (djvu || djvu))
  {
    if (fgpm || fgbc)
      G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
    GP<IW44Image> gfg44=IW44Image::create_decode(IW44Image::COLOR);
    IW44Image &fg44=*gfg44;
    fg44.decode_chunk(gbs);
    fgpm=fg44.get_pixmap();
    desc.format( ERR_MSG("DjVuFile.IW44_fg") "\t%d\t%d\t%d",
      fg44.get_width(), fg44.get_height(),
      get_dpi(fg44.get_width(), fg44.get_height()));
  } 
  
  // LINK (background LINK)
  else if (chkid == "LINK" && (djvu || djvi))
  {
    if (bg44 || bgpm)
      G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
    if(djvu_decode_codec)
    {
      set_modified(true);
      set_can_compress(true);
      set_needs_compression(true);
      this->bgpm = djvu_decode_codec(bs);
      desc.format( ERR_MSG("DjVuFile.color_import1") "\t%d\t%d\t%d",
        bgpm->columns(), bgpm->rows(),
        get_dpi(bgpm->columns(), bgpm->rows()));
    }else
    {
      desc.format( ERR_MSG("DjVuFile.color_import2") );
    }
  } 
  
  // BGjp (background JPEG)
  else if (chkid == "BGjp" && (djvu || djvi))
  {
    if (bg44 || bgpm)
      G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
    set_can_compress(true);
#ifdef NEED_JPEG_DECODER
    this->bgpm = JPEGDecoder::decode(bs);
    desc.format( ERR_MSG("DjVuFile.JPEG_bg1") "\t%d\t%d\t%d",
      bgpm->columns(), bgpm->rows(),
      get_dpi(bgpm->columns(), bgpm->rows()));
#else
    desc.format( ERR_MSG("DjVuFile.JPEG_bg2") );
#endif
  } 
  
  // FGjp (foreground JPEG)
  else if (chkid == "FGjp" && (djvu || djvi))
  {
    if (fgpm || fgbc)
      G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
#ifdef NEED_JPEG_DECODER
    this->fgpm = JPEGDecoder::decode(bs);
    desc.format( ERR_MSG("DjVuFile.JPEG_fg1") "\t%d\t%d\t%d",
      fgpm->columns(), fgpm->rows(),
      get_dpi(fgpm->columns(), fgpm->rows()));
#else
    desc.format( ERR_MSG("DjVuFile.JPEG_fg2") );
#endif
  } 
  
  // BG2k (background JPEG-2000) Note: JPEG2K bitstream not finalized.
  else if (chkid == "BG2k" && (djvu || djvi))
  {
    if (bg44)
      G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
    desc.format( ERR_MSG("DjVuFile.JPEG2K_bg") );
  } 
  
  // FG2k (foreground JPEG-2000) Note: JPEG2K bitstream not finalized.
  else if (chkid == "FG2k" && (djvu || djvi))
  {
    if (fgpm || fgbc)
      G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
    desc.format( ERR_MSG("DjVuFile.JPEG2K_fg") );
  } 
  
  // FGbz (foreground color vector)
  else if (chkid == "FGbz" && (djvu || djvi))
  {
    if (fgpm || fgbc)
      G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
    GP<DjVuPalette> fgbc = DjVuPalette::create();
    fgbc->decode(gbs);
    this->fgbc = fgbc;
    desc.format( ERR_MSG("DjVuFile.JB2_fg") "\t%d\t%d",
      fgbc->size(), fgbc->colordata.size());
  }
  
  // BM44/PM44 (IW44 data)
  else if ((chkid == "PM44" || chkid=="BM44") && iw44)
  {
    if (!bg44)
    {
      // First chunk
      GP<IW44Image> bg44 = IW44Image::create_decode(IW44Image::COLOR);
      bg44->decode_chunk(gbs);
      GP<DjVuInfo> info = DjVuInfo::create();
      info->width = bg44->get_width();
      info->height = bg44->get_height();
      info->dpi = 100;
      this->bg44 = bg44;
      this->info = info;
      desc.format( ERR_MSG("DjVuFile.IW44_data1") "\t%d\t%d\t%d",
                   bg44->get_width(), bg44->get_height(),
                   get_dpi(bg44->get_width(), bg44->get_height()));
    } 
    else
    {
      // Refinement chunks
      GP<IW44Image> bg44 = this->bg44;
      bg44->decode_chunk(gbs);
      desc.format( ERR_MSG("DjVuFile.IW44_data2") "\t%d\t%d",
                   bg44->get_serial(),
                   get_dpi(bg44->get_width(), bg44->get_height()));
    }
  }
  
  // NDIR (obsolete navigation chunk)
  else if (chkid == "NDIR")
  {
    GP<DjVuNavDir> dir=DjVuNavDir::create(url);
    dir->decode(bs);
    this->dir=dir;
    desc.format( ERR_MSG("DjVuFile.nav_dir") );
  }
  
  // FORM:ANNO (obsolete) (must be before other annotations)
  else if (chkid == "FORM:ANNO") 
    {
      const GP<ByteStream> gachunk(ByteStream::create());
      ByteStream &achunk=*gachunk;
      achunk.copy(bs);
      achunk.seek(0);
      GCriticalSectionLock lock(&anno_lock);
      if (! anno)
      {
        anno=ByteStream::create();
      }
      anno->seek(0,SEEK_END);
      if (anno->tell())
      {
        anno->write((void*)"", 1);
      }
      // Copy data
      anno->copy(achunk);
      desc.format( ERR_MSG("DjVuFile.anno1") );
    }
  
  // ANTa/ANTx/TXTa/TXTz annotations
  else if (is_annotation(chkid))  // but not FORM:ANNO
    {
      const GP<ByteStream> gachunk(ByteStream::create());
      ByteStream &achunk=*gachunk;
      achunk.copy(bs);
      achunk.seek(0);
      GCriticalSectionLock lock(&anno_lock);
      if (! anno)
      {
        anno = ByteStream::create();
      }
      anno->seek(0,SEEK_END);
      if (anno->tell() & 1)
      {
        anno->write((const void*)"", 1);
      }
      // Recreate chunk header
      const GP<IFFByteStream> giffout(IFFByteStream::create(anno));
      IFFByteStream &iffout=*giffout;
      iffout.put_chunk(id);
      iffout.copy(achunk);
      iffout.close_chunk();
      desc.format( ERR_MSG("DjVuFile.anno2") );
    }
  else if (is_text(chkid))
    {
      const GP<ByteStream> gachunk(ByteStream::create());
      ByteStream &achunk=*gachunk;
      achunk.copy(bs);
      achunk.seek(0);
      GCriticalSectionLock lock(&text_lock);
      if (! text)
      {
        text = ByteStream::create();
      }
      text->seek(0,SEEK_END);
      if (text->tell())
      {
        text->write((const void*)"", 1);
      }
      // Recreate chunk header
      const GP<IFFByteStream> giffout(IFFByteStream::create(text));
      IFFByteStream &iffout=*giffout;
      iffout.put_chunk(id);
      iffout.copy(achunk);
      iffout.close_chunk();
      desc.format( ERR_MSG("DjVuFile.text") );
    }
  else if (is_meta(chkid))
    {
      const GP<ByteStream> gachunk(ByteStream::create());
      ByteStream &achunk=*gachunk;
      achunk.copy(bs);
      achunk.seek(0);
      GCriticalSectionLock lock(&meta_lock);
      if (! meta)
      {
        meta = ByteStream::create();
      }
      meta->seek(0,SEEK_END);
      if (meta->tell())
      {
        meta->write((const void*)"", 1);
      }
      // Recreate chunk header
      const GP<IFFByteStream> giffout(IFFByteStream::create(meta));
      IFFByteStream &iffout=*giffout;
      iffout.put_chunk(id);
      iffout.copy(achunk);
      iffout.close_chunk();
    }
  else if (chkid == "CELX")
    {
      G_THROW( ERR_MSG("DjVuFile.securedjvu") );
    }
  
  // Return description
  return desc;
}

void
DjVuFile::set_decode_codec(GP<GPixmap> (*codec)(ByteStream &bs))
{
  djvu_decode_codec=codec;
}

void
DjVuFile::decode(const GP<ByteStream> &gbs)
{
  check();
  DEBUG_MSG("DjVuFile::decode(), url='" << url << "'\n");
  DEBUG_MAKE_INDENT(3);
  DjVuPortcaster * pcaster=get_portcaster();
  
  // Get form chunk
  GUTF8String chkid;
  const GP<IFFByteStream> giff(IFFByteStream::create(gbs));
  IFFByteStream &iff=*giff;
  if (!iff.get_chunk(chkid)) 
    REPORT_EOF(true)
    
    // Check file format
  bool djvi = (chkid=="FORM:DJVI")?true:false;
  bool djvu = (chkid=="FORM:DJVU")?true:false;
  bool iw44 = ((chkid=="FORM:PM44") || (chkid=="FORM:BM44"));
  if (djvi || djvu)
    mimetype = "image/x.djvu";
  else if (iw44)
    mimetype = "image/x-iw44";
  else
    G_THROW( ERR_MSG("DjVuFile.unexp_image") );
  
  // Process chunks
  int size_so_far=iff.tell();
  int chunks=0;
  int last_chunk=0;
  G_TRY
  {
    int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
    int chksize;
    for(;(chunks_left--)&&(chksize = iff.get_chunk(chkid));last_chunk=chunks)
    {
      chunks++;

      // Decode and get chunk description
      GUTF8String str = decode_chunk(chkid, iff.get_bytestream(), djvi, djvu, iw44);
      // Add parameters to the chunk description to give the size and chunk id
      GUTF8String desc;
      desc.format("\t%5.1f\t%s", chksize/1024.0, (const char*)chkid);
      // Append the whole thing to the growing file description
      description = description + str + desc + "\n";

      pcaster->notify_chunk_done(this, chkid);
      // Close chunk
      iff.seek_close_chunk();
      // Record file size
      size_so_far=iff.tell();
    }
    if (chunks_number < 0) chunks_number=last_chunk;
  }
  G_CATCH(ex)
  {
    if(!ex.cmp_cause(ByteStream::EndOfFile))
    {
      if (chunks_number < 0)
        chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
      report_error(ex,(recover_errors <= SKIP_PAGES));
    }else
    {
      report_error(ex,true);
    }
  }
  G_ENDCATCH;
  
  // Record file size
  file_size=size_so_far;
  // Close form chunk
  iff.close_chunk();
  // Close BG44 codec
  if (bg44) 
    bg44->close_codec();
  
  // Complete description
  if (djvu && !info)
    G_THROW( ERR_MSG("DjVuFile.corrupt_missing_info") );
  if (iw44 && !info)
    G_THROW( ERR_MSG("DjVuFile.corrupt_missing_IW44") );
  if (info)
  {
    GUTF8String desc;
    if (djvu || djvi)
      desc.format( ERR_MSG("DjVuFile.djvu_header") "\t%d\t%d\t%d\t%d", 
        info->width, info->height,
        info->dpi, info->version);
    else if (iw44)
      desc.format( ERR_MSG("DjVuFile.IW44_header") "\t%d\t%d\t%d", 
        info->width, info->height, info->dpi);
    description=desc + "\n" + description;
    int rawsize=info->width*info->height*3;
    desc.format( ERR_MSG("DjVuFile.ratio") "\t%0.1f\t%0.1f",
      (double)rawsize/file_size, file_size/1024.0 );
    description=description+desc;
  }
}

void
DjVuFile::start_decode(void)
{
  check();
  DEBUG_MSG("DjVuFile::start_decode(), url='" << url << "'\n");
  DEBUG_MAKE_INDENT(3);
  
  GThread * thread_to_delete=0;
  flags.enter();
  G_TRY {
    if (!(flags & DONT_START_DECODE) && !is_decoding())
    {
      if (flags & DECODE_STOPPED) reset();
      flags&=~(DECODE_OK | DECODE_STOPPED | DECODE_FAILED);
      flags|=DECODING;
      
      // Don't delete the thread while you're owning the flags lock
      // Beware of deadlock!
      thread_to_delete=decode_thread; decode_thread=0;
      
      // We want to create it right here to be able to stop the
      // decoding thread even before its function is called (it starts)
      decode_data_pool=DataPool::create(data_pool);
      decode_life_saver=this;
      
      decode_thread=new GThread();
      decode_thread->create(static_decode_func, this);
    }
  }
  G_CATCH_ALL
  {
    flags&=~DECODING;
    flags|=DECODE_FAILED;
    flags.leave();
    get_portcaster()->notify_file_flags_changed(this, DECODE_FAILED, DECODING);
    delete thread_to_delete;
    G_RETHROW;
  }
  G_ENDCATCH;
  flags.leave();
  delete thread_to_delete;
}

bool
DjVuFile::resume_decode(const bool sync)
{
  bool retval=false;
  {
    GMonitorLock lock(&flags);
    if( !is_decoding() && !is_decode_ok() && !is_decode_failed() )
    {
      start_decode();
      retval=true;
    }
  }
  if(sync)
  {
    wait_for_finish();
  }
  return retval;
}

void
DjVuFile::stop_decode(bool sync)
{
  check();
  
  DEBUG_MSG("DjVuFile::stop_decode(), url='" << url <<
    "', sync=" << (int) sync << "\n");
  DEBUG_MAKE_INDENT(3);
  
  G_TRY
  {
    flags|=DONT_START_DECODE;
    
    // Don't stop SYNCHRONOUSLY from the thread where the decoding is going!!!
    {
      // First - ask every included child to stop in async mode
      GCriticalSectionLock lock(&inc_files_lock);
      for(GPosition pos=inc_files_list;pos;++pos)
        inc_files_list[pos]->stop_decode(0);
      
//      if (decode_data_pool) decode_data_pool->stop();
    }
    
    if (sync)
    {
      while(1)
      {
        GP<DjVuFile> file;
        {
          GCriticalSectionLock lock(&inc_files_lock);
          for(GPosition pos=inc_files_list;pos;++pos)
          {
            GP<DjVuFile> & f=inc_files_list[pos];
            if (f->is_decoding())
            {
              file=f; break;
            }
          }
        }
        if (!file) break;
        
        file->stop_decode(1);
      }
      
      wait_for_finish(1);	// Wait for self termination
      
      // Don't delete the thread here. Until GPBase::preserve() is
      // reimplemented somehow at the GThread level.
      // delete decode_thread; decode_thread=0;
    }
    flags&=~(DONT_START_DECODE);
  } G_CATCH_ALL {
    flags&=~(DONT_START_DECODE);
    G_RETHROW;
  } G_ENDCATCH;
}

void
DjVuFile::stop(bool only_blocked)
// This is a one-way function. There is no way to undo the stop()
// command.
{
  DEBUG_MSG("DjVuFile::stop(): Stopping everything\n");
  DEBUG_MAKE_INDENT(3);
  
  flags|=only_blocked ? BLOCKED_STOPPED : STOPPED;
  if (data_pool) data_pool->stop(only_blocked);
  GCriticalSectionLock lock(&inc_files_lock);
  for(GPosition pos=inc_files_list;pos;++pos)
    inc_files_list[pos]->stop(only_blocked);
}

GP<DjVuNavDir>
DjVuFile::find_ndir(GMap<GURL, void *> & map)
{
  check();
  
  DEBUG_MSG("DjVuFile::find_ndir(): looking for NDIR in '" << url << "'\n");
  DEBUG_MAKE_INDENT(3);
  
  if (dir) return dir;
  
  if (!map.contains(url))
  {
    map[url]=0;
    
    GPList<DjVuFile> list=get_included_files(false);
    for(GPosition pos=list;pos;++pos)
    {
      GP<DjVuNavDir> d=list[pos]->find_ndir(map);
      if (d) return d;
    }
  }
  return 0;
}

GP<DjVuNavDir>
DjVuFile::find_ndir(void)
{
  GMap<GURL, void *> map;
  return find_ndir(map);
}

GP<DjVuNavDir>
DjVuFile::decode_ndir(GMap<GURL, void *> & map)
{
  check();
  
  DEBUG_MSG("DjVuFile::decode_ndir(): decoding for NDIR in '" << url << "'\n");
  DEBUG_MAKE_INDENT(3);
  
  if (dir) return dir;
  
  if (!map.contains(url))
  {
    map[url]=0;
    
    const GP<ByteStream> str(data_pool->get_stream());
    
    GUTF8String chkid;
    const GP<IFFByteStream> giff(IFFByteStream::create(str));
    IFFByteStream &iff=*giff;
    if (!iff.get_chunk(chkid)) 
      REPORT_EOF(true)

    int chunks=0;
    int last_chunk=0;
#ifndef SLOW_BUT_EXACT_DETECTION_OF_NDIR
    int found_incl=0;
#endif
    G_TRY
    {
      int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
      int chksize;
      for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
      {
        chunks++;
        if (chkid=="NDIR")
        {
          GP<DjVuNavDir> d=DjVuNavDir::create(url);
          d->decode(*iff.get_bytestream());
          dir=d;
          break;
        }
#ifndef SLOW_BUT_EXACT_DETECTION_OF_NDIR
        if (chkid=="INCL")
          found_incl = 1;
        if (chunks>2 && !found_incl && !data_pool->is_eof())
          return 0;
#endif
        iff.seek_close_chunk();
      }
      if ((!dir)&&(chunks_number < 0)) chunks_number=last_chunk;
    }
    G_CATCH(ex)
    {
       if(!ex.cmp_cause(ByteStream::EndOfFile))
       {
          if (chunks_number < 0)
             chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
          report_error(ex,(recover_errors<=SKIP_PAGES));
       }else
       {
          report_error(ex,true);
       }
    }
    G_ENDCATCH;
    
    data_pool->clear_stream();
    if (dir) return dir;
    
    GPList<DjVuFile> list=get_included_files(false);
    for(GPosition pos=list;pos;++pos)
    {
      GP<DjVuNavDir> d=list[pos]->decode_ndir(map);
      if (d) return d;
    }
    data_pool->clear_stream();
  }
  return 0;
}

GP<DjVuNavDir>
DjVuFile::decode_ndir(void)
{
  GMap<GURL, void *> map;
  return decode_ndir(map);
}

void
DjVuFile::get_merged_anno(const GP<DjVuFile> & file,
  const GP<ByteStream> &gstr_out, const GList<GURL> & ignore_list,
  int level, int & max_level, GMap<GURL, void *> & map)
{
  DEBUG_MSG("DjVuFile::get_merged_anno()\n");
  GURL url=file->get_url();
  if (!map.contains(url))
  {
    ByteStream &str_out=*gstr_out;
    map[url]=0;

    // Do the included files first (To make sure that they have
    // less precedence)
    // Depending on if we have all data present, we will
    // either create all included files or will use only
    // those that have already been created
    GPList<DjVuFile> list=file->get_included_files(!file->is_data_present());
    for(GPosition pos=list;pos;++pos)
      get_merged_anno(list[pos], gstr_out, ignore_list, level+1, max_level, map);

    // Now process the DjVuFile's own annotations
    if (!ignore_list.contains(file->get_url()))
      {
	if (!file->is_data_present() ||
	    (file->is_modified() && file->anno))
	  {
	    // Process the decoded (?) anno
	    GCriticalSectionLock lock(&file->anno_lock);
	    if (file->anno && file->anno->size())
	      {
		if (str_out.tell())
		  {
		    str_out.write((void *) "", 1);
		  }
		file->anno->seek(0);
		str_out.copy(*file->anno);
	      }
	  } else if (file->is_data_present())
	  {
	    // Copy all annotations chunks, but do NOT modify
	    // this->anno (to avoid correlation with DjVuFile::decode())
	    const GP<ByteStream> str(file->data_pool->get_stream());
	    const GP<IFFByteStream> giff(IFFByteStream::create(str));
	    IFFByteStream &iff=*giff;
	    GUTF8String chkid;
	    if (iff.get_chunk(chkid))
	      while(iff.get_chunk(chkid))
		{
		  if (chkid=="FORM:ANNO")
		    {
		      if (max_level<level)
			max_level=level;
		      if (str_out.tell())
			{
			  str_out.write((void *) "", 1);
			}
		      str_out.copy(*iff.get_bytestream());
		    }
		  else if (is_annotation(chkid)) // but not FORM:ANNO
		    {
		      if (max_level<level)
			max_level=level;
		      if (str_out.tell()&&chkid != "ANTz")
			{
			  str_out.write((void *) "", 1);
			}
		      const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
		      IFFByteStream &iff_out=*giff_out;
		      iff_out.put_chunk(chkid);
		      iff_out.copy(*iff.get_bytestream());
		      iff_out.close_chunk();
		    }
		  iff.close_chunk();
		}
	    file->data_pool->clear_stream();
	  }
      }
  }
}

GP<ByteStream>
DjVuFile::get_merged_anno(const GList<GURL> & ignore_list,
                          int * max_level_ptr)
                          // Will do the same thing as get_merged_anno(int *), but will
                          // ignore DjVuFiles with URLs from the ignore_list
{
  DEBUG_MSG("DjVuFile::get_merged_anno()\n");
  GP<ByteStream> gstr(ByteStream::create());
  GMap<GURL, void *> map;
  int max_level=0;
  get_merged_anno(this, gstr, ignore_list, 0, max_level, map);
  if (max_level_ptr)
    *max_level_ptr=max_level;
  ByteStream &str=*gstr;
  if (!str.tell()) 
  {
    gstr=0;
  }else
  {
    str.seek(0);
  }
  return gstr;
}

GP<ByteStream>
DjVuFile::get_merged_anno(int * max_level_ptr)
// Will go down the DjVuFile's hierarchy and decode all DjVuAnno even
// when the DjVuFile is not fully decoded yet. To avoid correlations
// with DjVuFile::decode(), we do not modify DjVuFile::anno data.
//
// Files deeper in the hierarchy have less influence on the
// results. It means, for example, that the if annotations are
// specified in the top level page file and in a shared file,
// the top level page file settings will take precedence.
//
// NOTE! This function guarantees correct results only if the
// DjVuFile has all data
{
  GList<GURL> ignore_list;
  return get_merged_anno(ignore_list, max_level_ptr);
}


// [LB->BCR] The following six functions get_anno, get_text, get_meta 
// contain the same code in triplicate!!!

void
DjVuFile::get_anno(
  const GP<DjVuFile> & file, const GP<ByteStream> &gstr_out)
{
  DEBUG_MSG("DjVuFile::get_anno()\n");
  ByteStream &str_out=*gstr_out;
  if (!file->is_data_present() ||
      (file->is_modified() && file->anno))
    {
      // Process the decoded (?) anno
      GCriticalSectionLock lock(&file->anno_lock);
      if (file->anno && file->anno->size())
	{
	  if (str_out.tell())
	    {
	      str_out.write((void *) "", 1);
	    }
	  file->anno->seek(0);
	  str_out.copy(*file->anno);
	}
    } else if (file->is_data_present())
    {
      // Copy all anno chunks, but do NOT modify
      // DjVuFile::anno (to avoid correlation with DjVuFile::decode())
      const GP<ByteStream> str=file->data_pool->get_stream();
      const GP<IFFByteStream> giff=IFFByteStream::create(str);
      IFFByteStream &iff=*giff;
      GUTF8String chkid;
      if (iff.get_chunk(chkid))
	{
	  while(iff.get_chunk(chkid))
	    {
	      if (is_annotation(chkid))
		{
		  if (str_out.tell())
		    {
		      str_out.write((void *) "", 1);
		    }
		  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
		  IFFByteStream &iff_out=*giff_out;
		  iff_out.put_chunk(chkid);
		  iff_out.copy(*iff.get_bytestream());
		  iff_out.close_chunk();
		}
	      iff.close_chunk();
	    }
	}
      file->data_pool->clear_stream();
    }
}

void
DjVuFile::get_text(
  const GP<DjVuFile> & file, const GP<ByteStream> &gstr_out)
{
  DEBUG_MSG("DjVuFile::get_text()\n");
  ByteStream &str_out=*gstr_out;
  if (!file->is_data_present() ||
      (file->is_modified() && file->text))
    {
      // Process the decoded (?) text
      GCriticalSectionLock lock(&file->text_lock);
      if (file->text && file->text->size())
	{
	  if (str_out.tell())
	    {
	      str_out.write((void *) "", 1);
	    }
	  file->text->seek(0);
	  str_out.copy(*file->text);
	}
    } else if (file->is_data_present())
    {
      // Copy all text chunks, but do NOT modify
      // DjVuFile::text (to avoid correlation with DjVuFile::decode())
      const GP<ByteStream> str=file->data_pool->get_stream();
      const GP<IFFByteStream> giff=IFFByteStream::create(str);
      IFFByteStream &iff=*giff;
      GUTF8String chkid;
      if (iff.get_chunk(chkid))
	{
	  while(iff.get_chunk(chkid))
	    {
	      if (is_text(chkid))
		{
		  if (str_out.tell())
		    {
		      str_out.write((void *) "", 1);
		    }
		  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
		  IFFByteStream &iff_out=*giff_out;
		  iff_out.put_chunk(chkid);
		  iff_out.copy(*iff.get_bytestream());
		  iff_out.close_chunk();
		}
	      iff.close_chunk();
	    }
	}
      file->data_pool->clear_stream();
    }
}

void
DjVuFile::get_meta(
  const GP<DjVuFile> & file, const GP<ByteStream> &gstr_out)
{
  DEBUG_MSG("DjVuFile::get_meta()\n");
  ByteStream &str_out=*gstr_out;
  if (!file->is_data_present() ||
      (file->is_modified() && file->meta))
    {
      // Process the decoded (?) meta
      GCriticalSectionLock lock(&file->meta_lock);
      if (file->meta && file->meta->size())
	{
	  if (str_out.tell())
	    {
	      str_out.write((void *) "", 1);
	    }
	  file->meta->seek(0);
	  str_out.copy(*file->meta);
	}
    } else if (file->is_data_present())
    {
      // Copy all meta chunks, but do NOT modify
      // DjVuFile::meta (to avoid correlation with DjVuFile::decode())
      const GP<ByteStream> str=file->data_pool->get_stream();
      const GP<IFFByteStream> giff=IFFByteStream::create(str);
      IFFByteStream &iff=*giff;
      GUTF8String chkid;
      if (iff.get_chunk(chkid))
	{
	  while(iff.get_chunk(chkid))
	    {
	      if (is_meta(chkid))
		{
		  if (str_out.tell())
		    {
		      str_out.write((void *) "", 1);
		    }
		  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
		  IFFByteStream &iff_out=*giff_out;
		  iff_out.put_chunk(chkid);
		  iff_out.copy(*iff.get_bytestream());
		  iff_out.close_chunk();
		}
	      iff.close_chunk();
	    }
	}
      file->data_pool->clear_stream();
    }
}

GP<ByteStream>
DjVuFile::get_anno(void)
{
  DEBUG_MSG("DjVuFile::get_text(void)\n");
  GP<ByteStream> gstr(ByteStream::create());
  get_anno(this, gstr);
  ByteStream &str=*gstr;
  if (!str.tell())
  { 
    gstr=0;
  }else
  {
    str.seek(0);
  }
  return gstr;
}

GP<ByteStream>
DjVuFile::get_text(void)
{
  DEBUG_MSG("DjVuFile::get_text(void)\n");
  GP<ByteStream> gstr(ByteStream::create());
  get_text(this, gstr);
  ByteStream &str=*gstr;
  if (!str.tell())
  { 
    gstr=0;
  }else
  {
    str.seek(0);
  }
  return gstr;
}

GP<ByteStream>
DjVuFile::get_meta(void)
{
  DEBUG_MSG("DjVuFile::get_meta(void)\n");
  GP<ByteStream> gstr(ByteStream::create());
  get_meta(this, gstr);
  ByteStream &str=*gstr;
  if (!str.tell())
  { 
    gstr=0;
  }else
  {
    str.seek(0);
  }
  return gstr;
}

void
DjVuFile::static_trigger_cb(void * cl_data)
{
  DjVuFile * th=(DjVuFile *) cl_data;
  G_TRY {
    GP<DjVuPort> port=DjVuPort::get_portcaster()->is_port_alive(th);
    if (port && port->inherits("DjVuFile"))
      ((DjVuFile *) (DjVuPort *) port)->trigger_cb();
  } G_CATCH(exc) {
    G_TRY {
      get_portcaster()->notify_error(th, exc.get_cause());
    } G_CATCH_ALL {} G_ENDCATCH;
  } G_ENDCATCH;
}

void
DjVuFile::trigger_cb(void)
{
  GP<DjVuFile> life_saver=this;
  
  DEBUG_MSG("DjVuFile::trigger_cb(): got data for '" << url << "'\n");
  DEBUG_MAKE_INDENT(3);
  
  file_size=data_pool->get_length();
  flags|=DATA_PRESENT;
  get_portcaster()->notify_file_flags_changed(this, DATA_PRESENT, 0);
  
  if (!are_incl_files_created())
    process_incl_chunks();
  
  bool all=true;
  inc_files_lock.lock();
  GPList<DjVuFile> files_list=inc_files_list;
  inc_files_lock.unlock();
  for(GPosition pos=files_list;pos&&(all=files_list[pos]->is_all_data_present());++pos)
    EMPTY_LOOP;
  if (all)
  {
    DEBUG_MSG("DjVuFile::trigger_cb(): We have ALL data for '" << url << "'\n");
    flags|=ALL_DATA_PRESENT;
    get_portcaster()->notify_file_flags_changed(this, ALL_DATA_PRESENT, 0);
  }
}

void
DjVuFile::progress_cb(int pos, void * cl_data)
{
  DEBUG_MSG("DjVuFile::progress_cb() called\n");
  DEBUG_MAKE_INDENT(3);
  
  DjVuFile * th=(DjVuFile *) cl_data;
  
  int length=th->decode_data_pool->get_length();
  if (length>0)
  {
    float progress=(float) pos/length;
    DEBUG_MSG("progress=" << progress << "\n");
    get_portcaster()->notify_decode_progress(th, progress);
  } else
  {
    DEBUG_MSG("DataPool size is still unknown => ignoring\n");
  }
}

//*****************************************************************************
//******************************** Utilities **********************************
//*****************************************************************************

void
DjVuFile::move(GMap<GURL, void *> & map, const GURL & dir_url)
// This function may block for data.
{
  if (!map.contains(url))
  {
    map[url]=0;
    
    url=GURL::UTF8(url.name(),dir_url);
    
    
    // Leave the lock here!
    GCriticalSectionLock lock(&inc_files_lock);
    for(GPosition pos=inc_files_list;pos;++pos)
      inc_files_list[pos]->move(map, dir_url);
  }
}

void
DjVuFile::move(const GURL & dir_url)
// This function may block for data.
{
  check();
  DEBUG_MSG("DjVuFile::move(): dir_url='" << dir_url << "'\n");
  DEBUG_MAKE_INDENT(3);
  
  GMap<GURL, void *> map;
  move(map, dir_url);
}

void
DjVuFile::set_name(const GUTF8String &name)
{
  DEBUG_MSG("DjVuFile::set_name(): name='" << name << "'\n");
  DEBUG_MAKE_INDENT(3);
  url=GURL::UTF8(name,url.base());
}

//*****************************************************************************
//****************************** Data routines ********************************
//*****************************************************************************

int
DjVuFile::get_chunks_number(void)
{
  if(chunks_number < 0)
  {
    const GP<ByteStream> str(data_pool->get_stream());
    GUTF8String chkid;
    const GP<IFFByteStream> giff(IFFByteStream::create(str));
    IFFByteStream &iff=*giff;
    if (!iff.get_chunk(chkid))
      REPORT_EOF(true)
      
      int chunks=0;
    int last_chunk=0;
    G_TRY
    {
      int chksize;
      for(;(chksize=iff.get_chunk(chkid));last_chunk=chunks)
      {
        chunks++;
        iff.seek_close_chunk();
      }
      chunks_number=last_chunk;
    }
    G_CATCH(ex)
    {
      chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
      report_error(ex,(recover_errors<=SKIP_PAGES));
    }
    G_ENDCATCH;
    data_pool->clear_stream();
  }
  return chunks_number;
}

GUTF8String
DjVuFile::get_chunk_name(int chunk_num)
{
  if(chunk_num < 0)
  {
    G_THROW( ERR_MSG("DjVuFile.illegal_chunk") );
  }
  if((chunks_number >= 0)&&(chunk_num > chunks_number))
  {
    G_THROW( ERR_MSG("DjVuFile.missing_chunk") );
  }
  check();
  
  GUTF8String name;
  const GP<ByteStream> str(data_pool->get_stream());
  GUTF8String chkid;
  const GP<IFFByteStream> giff(IFFByteStream::create(str));
  IFFByteStream &iff=*giff;
  if (!iff.get_chunk(chkid)) 
    REPORT_EOF(true)
    
    int chunks=0;
  int last_chunk=0;
  G_TRY
  {
    int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
    int chksize;
    for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
    {
      if (chunks++==chunk_num) { name=chkid; break; }
      iff.seek_close_chunk();
    }
  }
  G_CATCH(ex)
  {
    if (chunks_number < 0)
      chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
    report_error(ex,(recover_errors <= SKIP_PAGES));
  }
  G_ENDCATCH;
  if (!name.length())
  {
    if (chunks_number < 0) chunks_number=chunks;
    G_THROW( ERR_MSG("DjVuFile.missing_chunk") );
  }
  return name;
}

bool
DjVuFile::contains_chunk(const GUTF8String &chunk_name)
{
  check();
  DEBUG_MSG("DjVuFile::contains_chunk(): url='" << url << "', chunk_name='" <<
    chunk_name << "'\n");
  DEBUG_MAKE_INDENT(3);
  
  bool contains=0;
  const GP<ByteStream> str(data_pool->get_stream());
  GUTF8String chkid;
  const GP<IFFByteStream> giff(IFFByteStream::create(str));
  IFFByteStream &iff=*giff;
  if (!iff.get_chunk(chkid)) 
    REPORT_EOF((recover_errors<=SKIP_PAGES))
    
    int chunks=0;
  int last_chunk=0;
  G_TRY
  {
    int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
    int chksize;
    for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
    {
      chunks++;
      if (chkid==chunk_name) { contains=1; break; }
      iff.seek_close_chunk();
    }
    if (!contains &&(chunks_number < 0)) chunks_number=last_chunk;
  }
  G_CATCH(ex)
  {
    if (chunks_number < 0)
      chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
    report_error(ex,(recover_errors <= SKIP_PAGES));
  }
  G_ENDCATCH;
  data_pool->clear_stream();
  return contains;
}

bool
DjVuFile::contains_anno(void)
{
  const GP<ByteStream> str(data_pool->get_stream());
  
  GUTF8String chkid;
  const GP<IFFByteStream> giff(IFFByteStream::create(str));
  IFFByteStream &iff=*giff;
  if (!iff.get_chunk(chkid))
    G_THROW( ByteStream::EndOfFile );
  
  while(iff.get_chunk(chkid))
  {
    if (is_annotation(chkid))
      return true;
    iff.close_chunk();
  }
  
  data_pool->clear_stream();
  return false;
}

bool
DjVuFile::contains_text(void)
{
  const GP<ByteStream> str(data_pool->get_stream());
  
  GUTF8String chkid;
  const GP<IFFByteStream> giff(IFFByteStream::create(str));
  IFFByteStream &iff=*giff;
  if (!iff.get_chunk(chkid))
    G_THROW( ByteStream::EndOfFile );
  
  while(iff.get_chunk(chkid))
  {
    if (is_text(chkid))
      return true;
    iff.close_chunk();
  }
  
  data_pool->clear_stream();
  return false;
}

bool
DjVuFile::contains_meta(void)
{
  const GP<ByteStream> str(data_pool->get_stream());
  
  GUTF8String chkid;
  const GP<IFFByteStream> giff(IFFByteStream::create(str));
  IFFByteStream &iff=*giff;
  if (!iff.get_chunk(chkid))
    G_THROW( ByteStream::EndOfFile );
  
  while(iff.get_chunk(chkid))
  {
    if (is_meta(chkid))
      return true;
    iff.close_chunk();
  }
  
  data_pool->clear_stream();
  return false;
}

//*****************************************************************************
//****************************** Save routines ********************************
//*****************************************************************************

static void
copy_chunks(const GP<ByteStream> &from, IFFByteStream &ostr)
{
  from->seek(0);
  const GP<IFFByteStream> giff(IFFByteStream::create(from));
  IFFByteStream &iff=*giff;
  GUTF8String chkid;
  int chksize;
  while ((chksize=iff.get_chunk(chkid)))
  {
    ostr.put_chunk(chkid);
    int ochksize=ostr.copy(*iff.get_bytestream());
    ostr.close_chunk();
    iff.seek_close_chunk();
    if(ochksize != chksize)
    {
      G_THROW( ByteStream::EndOfFile );
    }
  }
}


void
DjVuFile::add_djvu_data(IFFByteStream & ostr, GMap<GURL, void *> & map,
                        const bool included_too, const bool no_ndir)
{
  check();
  if (map.contains(url)) return;
  bool top_level = !map.size();
  map[url]=0;
  bool processed_annotation = false;
  bool processed_text = false;
  bool processed_meta = false;
  
  const GP<ByteStream> str(data_pool->get_stream());
  GUTF8String chkid;
  const GP<IFFByteStream> giff(IFFByteStream::create(str));
  IFFByteStream &iff=*giff;
  if (!iff.get_chunk(chkid)) 
    REPORT_EOF(true)
    
    // Open toplevel form
    if (top_level) 
      ostr.put_chunk(chkid);
    // Process chunks
    int chunks=0;
    int last_chunk=0;
    G_TRY
    {
      int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
      int chksize;
      for(;(chunks_left--)&&(chksize = iff.get_chunk(chkid));last_chunk=chunks)
      {
        chunks++;
        if (is_info(chkid) && info)
        {
          ostr.put_chunk(chkid);
          info->encode(*ostr.get_bytestream());
          ostr.close_chunk();
        }
        else if (chkid=="INCL" && included_too)
        {
          GP<DjVuFile> file = process_incl_chunk(*iff.get_bytestream());
          if (file)
          {
            if(recover_errors!=ABORT)
              file->set_recover_errors(recover_errors);
            if(verbose_eof)
              file->set_verbose_eof(verbose_eof);
            file->add_djvu_data(ostr, map, included_too, no_ndir);
          }
        } 
        else if (is_annotation(chkid) && anno && anno->size())
        {
          if (!processed_annotation)
          {
            processed_annotation = true;
            GCriticalSectionLock lock(&anno_lock);
            copy_chunks(anno, ostr);
          }
        }
        else if (is_text(chkid) && text && text->size())
        {
          if (!processed_text)
          {
            processed_text = true;
            GCriticalSectionLock lock(&text_lock);
            copy_chunks(text, ostr);
          }
        }
        else if (is_meta(chkid) && meta && meta->size())
        {
          if (!processed_meta)
          {
            processed_meta = true;
            GCriticalSectionLock lock(&meta_lock);
            copy_chunks(meta, ostr);
          }
        }
        else if (chkid!="NDIR"||!(no_ndir || dir))
        {  // Copy NDIR chunks, but never generate new ones.
          ostr.put_chunk(chkid);
          ostr.copy(*iff.get_bytestream());
          ostr.close_chunk();
        }
        iff.seek_close_chunk();
      }
      if (chunks_number < 0) chunks_number=last_chunk;
    }
    G_CATCH(ex)
    {
      if(!ex.cmp_cause(ByteStream::EndOfFile))
      {
        if (chunks_number < 0)
          chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
        report_error(ex,(recover_errors<=SKIP_PAGES));
      }else
      {
        report_error(ex,true);
      }
    }
    G_ENDCATCH;
    
    // Otherwise, writes annotation at the end (annotations could be big)
    if (!processed_annotation && anno && anno->size())
    {
      processed_annotation = true;
      GCriticalSectionLock lock(&anno_lock);
      copy_chunks(anno, ostr);
    }
    if (!processed_text && text && text->size())
    {
      processed_text = true;
      GCriticalSectionLock lock(&text_lock);
      copy_chunks(text, ostr);
    }
    if (!processed_meta && meta && meta->size())
    {
      processed_meta = true;
      GCriticalSectionLock lock(&meta_lock);
      copy_chunks(meta, ostr);
    }
    // Close iff
    if (top_level) 
      ostr.close_chunk();

  data_pool->clear_stream();
}

GP<ByteStream>  
DjVuFile::get_djvu_bytestream(const bool included_too, const bool no_ndir)
{
   check();
   DEBUG_MSG("DjVuFile::get_djvu_bytestream(): creating DjVu raw file\n");
   DEBUG_MAKE_INDENT(3);
   const GP<ByteStream> pbs(ByteStream::create());
   const GP<IFFByteStream> giff=IFFByteStream::create(pbs);
   IFFByteStream &iff=*giff;
   GMap<GURL, void *> map;
   add_djvu_data(iff, map, included_too, no_ndir);
   iff.flush();
   pbs->seek(0, SEEK_SET);
   return pbs;
}

GP<DataPool>
DjVuFile::get_djvu_data(const bool included_too, const bool no_ndir)
{
  const GP<ByteStream> pbs = get_djvu_bytestream(included_too, no_ndir);
  return DataPool::create(pbs);
}

void
DjVuFile::merge_anno(ByteStream &out)
{
  // Reuse get_merged_anno(), which is better than the previous
  // implementation due to three things:
  //  1. It works even before the file is completely decoded
  //  2. It merges annotations taking into account where a child DjVuFile
  //     is included.
  //  3. It handles loops in DjVuFile's hierarchy
  
  const GP<ByteStream> str(get_merged_anno());
  if (str)
  {
    str->seek(0);
    if (out.tell())
    {
      out.write((void *) "", 1);
    }
    out.copy(*str);
  }
}

void
DjVuFile::get_text(ByteStream &out)
{
  const GP<ByteStream> str(get_text());
  if (str)
  {
    str->seek(0);
    if (out.tell())
    {
      out.write((void *) "", 1);
    }
    out.copy(*str);
  }
}

void
DjVuFile::get_meta(ByteStream &out)
{
  const GP<ByteStream> str(get_meta());
  if (str)
  {
    str->seek(0);
    if (out.tell())
    {
      out.write((void *) "", 1);
    }
    out.copy(*str);
  }
}



//****************************************************************************
//******************************* Modifying **********************************
//****************************************************************************

void
DjVuFile::remove_anno(void)
{
  DEBUG_MSG("DjVuFile::remove_anno()\n");
  const GP<ByteStream> str_in(data_pool->get_stream());
  const GP<ByteStream> gstr_out(ByteStream::create());
  
  GUTF8String chkid;
  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
  IFFByteStream &iff_in=*giff_in;
  if (!iff_in.get_chunk(chkid))
    G_THROW( ByteStream::EndOfFile );
  
  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
  IFFByteStream &iff_out=*giff_out;
  iff_out.put_chunk(chkid);
  
  while(iff_in.get_chunk(chkid))
  {
    if (!is_annotation(chkid))
    {
      iff_out.put_chunk(chkid);
      iff_out.copy(*iff_in.get_bytestream());
      iff_out.close_chunk();
    }
    iff_in.close_chunk();
  }
  
  iff_out.close_chunk();
  
  gstr_out->seek(0, SEEK_SET);
  data_pool=DataPool::create(gstr_out);
  chunks_number=-1;
  
  anno=0;
  
  flags|=MODIFIED;
  data_pool->clear_stream();
}

void
DjVuFile::remove_text(void)
{
  DEBUG_MSG("DjVuFile::remove_text()\n");
  const GP<ByteStream> str_in(data_pool->get_stream());
  const GP<ByteStream> gstr_out(ByteStream::create());
  
  GUTF8String chkid;
  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
  IFFByteStream &iff_in=*giff_in;
  if (!iff_in.get_chunk(chkid))
    G_THROW( ByteStream::EndOfFile );
  
  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
  IFFByteStream &iff_out=*giff_out;
  iff_out.put_chunk(chkid);
  
  while(iff_in.get_chunk(chkid))
  {
    if (!is_text(chkid))
    {
      iff_out.put_chunk(chkid);
      iff_out.copy(*iff_in.get_bytestream());
      iff_out.close_chunk();
    }
    iff_in.close_chunk();
  }
  
  iff_out.close_chunk();
  
  gstr_out->seek(0, SEEK_SET);
  data_pool=DataPool::create(gstr_out);
  chunks_number=-1;
  
  text=0;
  
  flags|=MODIFIED;
  data_pool->clear_stream();
}

void
DjVuFile::remove_meta(void)
{
  DEBUG_MSG("DjVuFile::remove_meta()\n");
  const GP<ByteStream> str_in(data_pool->get_stream());
  const GP<ByteStream> gstr_out(ByteStream::create());
  
  GUTF8String chkid;
  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
  IFFByteStream &iff_in=*giff_in;
  if (!iff_in.get_chunk(chkid))
    G_THROW( ByteStream::EndOfFile );
  
  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
  IFFByteStream &iff_out=*giff_out;
  iff_out.put_chunk(chkid);
  
  while(iff_in.get_chunk(chkid))
  {
    if (!is_meta(chkid))
    {
      iff_out.put_chunk(chkid);
      iff_out.copy(*iff_in.get_bytestream());
      iff_out.close_chunk();
    }
    iff_in.close_chunk();
  }
  
  iff_out.close_chunk();
  
  gstr_out->seek(0, SEEK_SET);
  data_pool=DataPool::create(gstr_out);
  chunks_number=-1;
  
  meta=0;
  
  flags|=MODIFIED;
  data_pool->clear_stream();
}

void
DjVuFile::rebuild_data_pool(void)
{
  data_pool=get_djvu_data(false,false);
  chunks_number=1;
  flags|=MODIFIED;
}

// Do NOT comment this function out. It's used by DjVuDocEditor to convert
// old-style DjVu documents to BUNDLED format.

GP<DataPool>
DjVuFile::unlink_file(const GP<DataPool> & data, const GUTF8String &name)
// Will process contents of data[] and remove any INCL chunk
// containing 'name'
{
  DEBUG_MSG("DjVuFile::unlink_file()\n");
  const GP<ByteStream> gstr_out(ByteStream::create());
  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
  IFFByteStream &iff_out=*giff_out;
  
  const GP<ByteStream> str_in(data->get_stream());
  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
  IFFByteStream &iff_in=*giff_in;
  
  int chksize;
  GUTF8String chkid;
  if (!iff_in.get_chunk(chkid)) return data;
  
  iff_out.put_chunk(chkid);
  
  while((chksize=iff_in.get_chunk(chkid)))
  {
    if (chkid=="INCL")
    {
      GUTF8String incl_str;
      char buffer[1024];
      int length;
      while((length=iff_in.read(buffer, 1024)))
        incl_str+=GUTF8String(buffer, length);
      
      // Eat '\n' in the beginning and at the end
      while(incl_str.length() && incl_str[0]=='\n')
      {
        incl_str=incl_str.substr(1,(unsigned int)(-1));
      }
      while(incl_str.length()>0 && incl_str[(int)incl_str.length()-1]=='\n')
      {
        incl_str.setat(incl_str.length()-1, 0);
      }
      if (incl_str!=name)
      {
        iff_out.put_chunk(chkid);
        iff_out.get_bytestream()->writestring(incl_str);
        iff_out.close_chunk();
      }
    } else
    {
      iff_out.put_chunk(chkid);
      char buffer[1024];
      int length;
      for(const GP<ByteStream> gbs(iff_out.get_bytestream());
        (length=iff_in.read(buffer, 1024));)
      {
        gbs->writall(buffer, length);
      }
      iff_out.close_chunk();
    }
    iff_in.close_chunk();
  }
  iff_out.close_chunk();
  iff_out.flush();
  gstr_out->seek(0, SEEK_SET);
  data->clear_stream();
  return DataPool::create(gstr_out);
}

#ifndef NEED_DECODER_ONLY
void
DjVuFile::insert_file(const GUTF8String &id, int chunk_num)
{
  DEBUG_MSG("DjVuFile::insert_file(): id='" << id << "', chunk_num="
    << chunk_num << "\n");
  DEBUG_MAKE_INDENT(3);
  
  // First: create new data
  const GP<ByteStream> str_in(data_pool->get_stream());
  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
  IFFByteStream &iff_in=*giff_in;
  
  const GP<ByteStream> gstr_out(ByteStream::create());
  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
  IFFByteStream &iff_out=*giff_out;
  
  int chunk_cnt=0;
  bool done=false;
  GUTF8String chkid;
  if (iff_in.get_chunk(chkid))
  {
    iff_out.put_chunk(chkid);
    int chksize;
    while((chksize=iff_in.get_chunk(chkid)))
    {
      if (chunk_cnt++==chunk_num)
      {
        iff_out.put_chunk("INCL");
        iff_out.get_bytestream()->writestring(id);
        iff_out.close_chunk();
        done=true;
      }
      iff_out.put_chunk(chkid);
      iff_out.copy(*iff_in.get_bytestream());
      iff_out.close_chunk();
      iff_in.close_chunk();
    }
    if (!done)
    {
      iff_out.put_chunk("INCL");
      iff_out.get_bytestream()->writestring(id);
      iff_out.close_chunk();
    }
    iff_out.close_chunk();
  }
  gstr_out->seek(0, SEEK_SET);
  data_pool=DataPool::create(gstr_out);
  chunks_number=-1;
  
  // Second: create missing DjVuFiles
  process_incl_chunks();
  
  flags|=MODIFIED;
  data_pool->clear_stream();
}
#endif

void
DjVuFile::unlink_file(const GUTF8String &id)
{
  DEBUG_MSG("DjVuFile::insert_file(): id='" << id << "'\n");
  DEBUG_MAKE_INDENT(3);
  
  // Remove the file from the list of included files
  {
    GURL url=DjVuPort::get_portcaster()->id_to_url(this, id);
    if (url.is_empty()) url=GURL::UTF8(id,this->url.base());
    GCriticalSectionLock lock(&inc_files_lock);
    for(GPosition pos=inc_files_list;pos;)
      if (inc_files_list[pos]->get_url()==url)
      {
        GPosition this_pos=pos;
        ++pos;
        inc_files_list.del(this_pos);
      } else ++pos;
  }
  
  // And update the data.
  const GP<ByteStream> str_in(data_pool->get_stream());
  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
  IFFByteStream &iff_in=*giff_in;
  
  const GP<ByteStream> gstr_out(ByteStream::create());
  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
  IFFByteStream &iff_out=*giff_out;
  
  GUTF8String chkid;
  if (iff_in.get_chunk(chkid))
  {
    iff_out.put_chunk(chkid);
    int chksize;
    while((chksize=iff_in.get_chunk(chkid)))
    {
      if (chkid!="INCL")
      {
        iff_out.put_chunk(chkid);
        iff_out.copy(*iff_in.get_bytestream());
        iff_out.close_chunk();
      } else
      {
        GUTF8String incl_str;
        char buffer[1024];
        int length;
        while((length=iff_in.read(buffer, 1024)))
          incl_str+=GUTF8String(buffer, length);
        
	       // Eat '\n' in the beginning and at the end
        while(incl_str.length() && incl_str[0]=='\n')
        {
          incl_str=incl_str.substr(1,(unsigned int)(-1));
        }
        while(incl_str.length()>0 && incl_str[(int)incl_str.length()-1]=='\n')
          incl_str.setat(incl_str.length()-1, 0);
        if (incl_str!=id)
        {
          iff_out.put_chunk("INCL");
          iff_out.get_bytestream()->writestring(incl_str);
          iff_out.close_chunk();
        }
      }
      iff_in.close_chunk();
    }
    iff_out.close_chunk();
  }
  
  gstr_out->seek(0, SEEK_SET);
  data_pool=DataPool::create(gstr_out);
  chunks_number=-1;
  
  flags|=MODIFIED;
}

void
DjVuFile::change_info(GP<DjVuInfo> xinfo,const bool do_reset)
{
  DEBUG_MSG("DjVuFile::change_text()\n");
  // Mark this as modified
  set_modified(true);
  if(do_reset)
    reset();
  info=xinfo;
}

#ifndef NEED_DECODER_ONLY
void
DjVuFile::change_text(GP<DjVuTXT> txt,const bool do_reset)
{
  DEBUG_MSG("DjVuFile::change_text()\n");
  GP<DjVuText> gtext_c=DjVuText::create();
  DjVuText &text_c=*gtext_c;
  if(contains_text())
  {
    const GP<ByteStream> file_text(get_text());
    if(file_text)
    {
      text_c.decode(file_text);
    }
  }
  GCriticalSectionLock lock(&text_lock);
  // Mark this as modified
  set_modified(true);
  if(do_reset)
    reset();
  text_c.txt = txt;
  text=ByteStream::create();
  text_c.encode(text);
}

void
DjVuFile::change_meta(const GUTF8String &xmeta,const bool do_reset)
{
  DEBUG_MSG("DjVuFile::change_meta()\n");
  // Mark this as modified
  set_modified(true);
  if(contains_meta())
  {
    (void)get_meta();
  }
  if(do_reset)
    reset();
  GCriticalSectionLock lock(&meta_lock);
  meta=ByteStream::create();
  if(xmeta.length())
  {
    const GP<IFFByteStream> giff=IFFByteStream::create(meta);
    IFFByteStream &iff=*giff;
    iff.put_chunk("METz");
    {
      GP<ByteStream> gbsiff=BSByteStream::create(iff.get_bytestream(),50);
      gbsiff->writestring(xmeta);
    }
    iff.close_chunk();
  }
}
#endif


#ifdef HAVE_NAMESPACES
}
# ifndef NOT_USING_DJVU_NAMESPACE
using namespace DJVU;
# endif
#endif
