//
// LDASTools frameCPP - A library implementing the LIGO/Virgo frame
// specification
//
// Copyright (C) 2018 California Institute of Technology
//
// LDASTools frameCPP is free software; you may redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 (GPLv2) of the
// License or at your discretion, any later version.
//
// LDASTools frameCPP is distributed in the hope that it will be useful, but
// without any warranty or even the implied warranty of merchantability
// or fitness for a particular purpose. See the GNU General Public
// License (GPLv2) for more details.
//
// Neither the names of the California Institute of Technology (Caltech),
// The Massachusetts Institute of Technology (M.I.T), The Laser
// Interferometer Gravitational-Wave Observatory (LIGO), nor the names
// of its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// You should have received a copy of the licensing terms for this
// software included in the file LICENSE located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE
//

#include <framecpp_config.h>

#include <fstream>

#include "framecpp/Common/BufferSpecialization.hh"
#include "framecpp/Common/FrameBuffer.hh"

//=======================================================================
// Specialization of the IOStream class
//=======================================================================
namespace FrameCPP
{
    namespace Common
    {
        using std::filebuf;

        template <>
        bool
        FrameBuffer< filebuf >::FilterInternally( ) const
        {
            return false;
        }

#if 0
    template< >
    filebuf::int_type FrameBuffer< filebuf >::
    overflow( int_type C )
    {
      int_type retval = C;

      if ( C == EOF )
      {
	//---------------------------------------------------------------
	// Handle filtering of what has been cached
	//---------------------------------------------------------------
	INT_8U size = this->pptr( ) - this->pbase( ) + eof_offset( );

#if LM_DEBUG_OUTPUT
	std::cerr << "overflow: Handling EOF: prefilter: "
		  << " start: " << (void*)( this->pbase( ) )
		  << " size: " << size
		  << " m_bytes_filtered: " << m_bytes_filtered
		  << " pbase: " << (void*)( this->pbase( ) )
		  << " pptr: " << (void*)( this->pptr( ) )
		  << " eof_offset: " << eof_offset( )
		  << std::endl;
	std::ofstream f;
	f.open( "z-pre", std::ios::out | std::ios::binary );
	f.write( this->pbase( ), size );
	f.close( );
#endif /* LM_DEBUG_OUTPUT */

	retval = filebuf::overflow( C );

#if LM_DEBUG_OUTPUT
	MD5Sum m;
	m.Update( (const MD5Sum::data_type)this->pbase( ),
		  MD5Sum::size_type( size ) );
	m.Finalize( );

	filter_on = true;
#endif /* LM_DEBUG_OUTPUT */

	filter( this->pbase( ), size );

#if LM_DEBUG_OUTPUT
	filter_on = false;

	f.open( "z-post", std::ios::out | std::ios::binary );
	f.write( this->pbase( ), size );
	f.close( );

	std::cerr << "overflow: Handling EOF: postfilter: "
		  << " last buffer: " << size
		  << " m_bytes_filtered: " << m_bytes_filtered
		  << " pbase: " << (void*)( this->pbase( ) )
		  << " pptr: " << (void*)( this->pptr( ) )
		  << " eof_offset: " << eof_offset( )
		  << " md5sum: " << m
		  << std::endl;
#endif /* LM_DEBUG_OUTPUT */
      }
      else
      {
#if LM_DEBUG_OUTPUT
	std::cerr << "overflow: Handling non-EOF:"
		  << " pptr: " << (void*)pptr( )
		  << " pback: " << (void*)pbase( )
		  << " length: " << ( pptr( ) - pbase( ) )
		  << std::endl;
#endif /* LM_DEBUG_OUTPUT */
	retval = filebuf::overflow( C );
      }

      return retval;
    }

    template< >
    filebuf::int_type FrameBuffer< filebuf >::
    underflow( )
    {
      std::streamsize buf_size = this->egptr( ) - this->eback( );
      //  std::streamsize buf_used = this->gptr( ) - this->eback( );
      // std::streamsize buf_avail = this->egptr( ) - this->gptr( );
      const char_type* end_pos = this->gptr( );
#if LM_DEBUG_INPUT
      std::cerr << "underflow:"
		<< " gptr: " << (void*)gptr( )
		<< " eback: " << (void*)eback( )
		<< " length: " << ( gptr( ) - eback( ) )
		<< std::endl;
#endif /* LM_DEBUG_INPUT */
      if ( m_user_buf_size < buf_size )
      {
#if LM_DEBUG_INPUT
	MD5Sum m;
	m.Update( (const MD5Sum::data_type)this->eback( ),
		  MD5Sum::size_type( gptr( ) - eback( ) ) );
	m.Finalize( );

	std::cerr << "underflow: buffer md5sum: " << m
		  << std::endl;
#endif /* LM_DEBUG_INPUT */

	filter( eback( ), gptr( ) );
      }

#if LM_DEBUG_INPUT
      std::cerr << "About to call filebuf::underflow"
		<< std::endl;
#endif /* LM_DEBUG_INPUT */
      filebuf::int_type retval = filebuf::underflow( );

      if ( ( retval == EOF )
	   && ( m_user_buf_size >= buf_size ) )
      {
#if LM_DEBUG_INPUT
	std::cerr << "Handling EOF underflow"
		  << std::endl;
#endif /* LM_DEBUG_INPUT */
	filter( eback( ), end_pos );
      }
#if LM_DEBUG_INPUT
      std::cerr << "underflow: after calling underflow:"
		<< " gptr: " << (void*)gptr( )
		<< " eback: " << (void*)eback( )
		<< " length: " << ( gptr( ) - eback( ) )
		<< " retval: " << retval
		<< std::endl;
#endif /* LM_DEBUG_INPUT */
      mark( );

      return retval;
    }

    template< >
    std::streamsize FrameBuffer< filebuf >::
    xsgetn( char_type* S, std::streamsize N )
    {
      set_user_buf( S, N );

      std::streamsize buf_size = this->egptr( ) - this->eback( );

#if LM_DEBUG_INPUT
      std::streamsize buf_used = this->gptr( ) - this->eback( );
      std::streamsize buf_avail = this->egptr( ) - this->gptr( );

      std::cerr << "xsgetn:"
		<< " S: " << (void*)S
		<< " N: " << N
		<< " buf_size: " << buf_size
		<< " buf_avail: " << buf_avail
		<< " buf_used: " << buf_used
		<< std::endl;
#endif /* LM_DEBUG_INPUT */
      std::streamsize retval =  filebuf::xsgetn( S, N );

      if ( buf_size == 0 )
      {
	buf_size = egptr( ) - eback( );
      }

      if ( ( retval < N ) && ( buf_size ) )
      {
	//---------------------------------------------------------------
	// The usual cause for this is because the end of the file has
	//   been reached. Filter any data in the cache.
	//---------------------------------------------------------------
	if ( filter_last_input_buffer( ) )
	{
	  if ( eback( ) == gptr( ) )
	  {
#if LM_DEBUG_INPUT
	    MD5Sum m;
	    m.Update( (const MD5Sum::data_type)S,
		      MD5Sum::size_type( retval ) );
	    m.Finalize( );
	    
	    std::cerr << "xsgetn: eof: buffer md5sum: " << m
		      << " size: " << ( retval )
		      << std::endl;
#endif /* LM_DEBUG_INPUT */
	  }
	  else
	  {
	    filter( eback( ), gptr( ) );
#if LM_DEBUG_INPUT
	    MD5Sum m;
	    m.Update( (const MD5Sum::data_type)this->eback( ),
		      MD5Sum::size_type( gptr( ) - eback( ) ) );
	    m.Finalize( );
	    
	    std::cerr << "xsgetn: eof: buffer md5sum: " << m
		      << " size: " << ( gptr( ) - eback( ) )
		      << std::endl;
#endif /* LM_DEBUG_INPUT */
	  }
	}
#if LM_DEBUG_INPUT
	else
	{
	  std::cerr << "DEBUG: no filtering of last buffer"
		    << std::endl;
	}
#endif /* LM_DEBUG_INPUT */
      }
      else if ( ( retval >= buf_size )
		|| ( ( gptr( ) - eback( ) ) == 0 ) )
      {

#if LM_DEBUG_INPUT
	std::cerr << "xsgetn: filtering:"
		  << " (retval > buf_size): " << ( retval > buf_size )
		  << " ( ( gptr( ) - eback( ) ) == 0 ): "
		  << ( ( gptr( ) - eback( ) ) == 0 )
		  << std::endl;
#endif /* LM_DEBUG_INPUT */
	filter( S, retval );
	mark( );
      }

#if LM_DEBUG_INPUT
      std::cerr << "xsgetn:"
		<< " m_bytes_filtered: " << m_bytes_filtered
		<< " retval: " << retval
		<< std::endl;
#endif /* LM_DEBUG_INPUT */

      return retval;
    }

    template< >
    std::streamsize FrameBuffer< filebuf >::
    xsputn( const char_type* S, std::streamsize N )
    {
#if LM_DEBUG_OUTPUT
      filter_on = true;
#endif /*LM_DEBUG_OUTPUT */

      std::streamsize buf_size = this->epptr( ) - this->pbase( );
      std::streamsize buf_used = this->pptr( ) - this->pbase( );
      std::streamsize buf_avail = this->epptr( ) - this->pptr( );
      bool overflowed = false;

#if LM_DEBUG_OUTPUT

      MD5Sum m;
      m.Update( (void*)S, INT_4U( N ) );
      m.Finalize( );
      std::cerr << "DEBUG:"
		<< " S: " << (void*)S
		<< " N: " << N
		<< " md5sum: m: " << m
		<< std::endl;
#endif /*LM_DEBUG_OUTPUT */

      if ( buf_size && ( buf_avail != 0 ) )
      {
	//---------------------------------------------------------------
	// There is a buffer
	//---------------------------------------------------------------

	if ( N > buf_avail )
	{
#if LM_DEBUG_OUTPUT
      std::cerr << "xsputn: will overflow:"
		<< " pbase: " << (void*)this->pbase( )
		<< " buf_used: " << buf_used
		<< std::endl;
#endif /* LM_DEBUG_OUTPUT */
	  //-------------------------------------------------------------
	  // This buffer will overflow, so process the bytes that are
	  //   currently in the buffer before they go away.
	  //-------------------------------------------------------------
	  filter( this->pbase( ), buf_used );
	  overflowed = true;
	}
      }
#if LM_DEBUG_OUTPUT
      std::cerr << "xsputn: before:"
		<< " buf_avail: " << buf_avail
		<< " buf_size: " << buf_size
		<< " m_bytes_filtered: " << m_bytes_filtered
		<< " N: " << N
		<< std::endl;
#endif /* LM_DEBUG_OUTPUT */

      std::streamsize retval =  filebuf::xsputn( S, N );

      buf_size = this->epptr( ) - this->pbase( );
      if ( ! overflowed )
      {
	if ( retval > buf_size )
	{
	  overflowed = true;
	}
      }

#if LM_DEBUG_OUTPUT
	std::cerr << "xsputn: after:"
		  << " retval: " << retval
		  << " pbase( ): " << (void*)this->pbase( )
		  << " pptr( ): " << (void*)this->pptr( )
		  << " epptr( ): " << (void*)this->epptr( )
		  << std::endl;
#endif /* LM_DEBUG_OUTPUT */
      if ( !overflowed
	   && ( this->pptr( ) == this->epptr( ) ) )
      {
	filter( this->pbase( ), this->epptr( ) - this->pbase( ) + 1 );

#if LM_DEBUG_OUTPUT
	std::cerr << "xsputn: after(1):"
		  << " m_bytes_filtered: " << m_bytes_filtered
		  << " N: " << N
		  << " retval: " << retval
		  << std::endl;
#endif /* LM_DEBUG_OUTPUT */
      }
      else if ( overflowed			   // Overflowed
		|| ( retval > buf_size )	   // Buffer > cache
		|| ( ( pptr( ) - pbase( ) ) == 0 ) // Nothing written to cache
		)
      {
	filter( S, N );
#if LM_DEBUG_OUTPUT
	std::cerr << "xsputn: after(2):"
		  << " S: " << (void*)S
		  << " N: " << N
		  << " buf_used: " << ( this ->pptr( ) - this->pbase( ) )
		  << " m_bytes_filtered: " << m_bytes_filtered
		  << " retval: " << retval
		  << std::endl;
#endif /* LM_DEBUG_OUTPUT */
      }

#if LM_DEBUG_OUTPUT
      filter_on = false;
#endif /* LM_DEBUG_OUTPUT */
      return retval;
    }
#endif /* 0 */

        //===================================================================
        // Instantiate the classes for the specified buffer type
        //===================================================================
        template class FrameBuffer< filebuf >;

    } // namespace Common
} // namespace FrameCPP
