/*
 * Copyright 2011-2019, European Organisation for the Exploitation of Meteorological Satellites (EUMETSAT)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define NOMINMAX

#include <iostream>

#include "CDataField.h"
#include "ErrorHandling.h"
#include "CompressT4.h"
#include "RMAErrorHandling.h"
#include "T4Codes.h"
#include "T4Decoder.h"

namespace COMP
{


// Initialize the decoder structures. If the image size given does not
// make sense, auto determination of the size if tried.
CT4Decoder::CT4Decoder( const Util::CDataFieldCompressedImage & i_datafield):
	m_ibuf( i_datafield ),
	m_QualityInfo( i_datafield.GetNL() )
{
	COMP_TRYTHIS
	Assert( i_datafield.GetNB()==1 , Util::CParamException());
	m_height = i_datafield.GetNL();
	m_width  = i_datafield.GetNC();
	if ( (m_height <= 0 ) || ( m_width <= 0 ))
	{
		DecodeBuffer();                   // Autodetermine the size of the input image
		m_QualityInfo.resize( m_height );
	}
	m_opbm = std::auto_ptr< CBitBuffer > ( new CBitBuffer( (unsigned long) m_height * m_width ) );
	Assert( m_opbm.get(), Util::CCLibException());
	for(unsigned int i=0; i < m_QualityInfo.size(); i++)
		m_QualityInfo[i] = 0;
	COMP_CATCHTHIS
}


// Reads the input buffer till an EOL symbol is encountered.
void CT4Decoder::SkipToEOL()
{
	COMP_TRYTHIS
	short Z = 0;
	bool  b = false;
	do 
	{
		Z = m_ibuf.GetNbZeroes();
		b = m_ibuf.ReadNextBitZ();
	} 
	while ( !( (Z > 10) && b ) );
	COMP_CATCHTHIS
}


// Decode the buffer (if the m_height & m_width have sensible values)
// otherwize, runs without filling the buffer and try to determine
// the image size from the data in the input buffer.
void CT4Decoder::DecodeBuffer()
{
	COMP_TRYTHIS
	// size & position managment
	short height    = 0;
	short width     = 0;
	short i         = 0;
	short j         = 0;
	// EOF / EOL managmend
	bool  EndOfFile = false;
	short NbEOL     = 0;
	short Z         = 0;    // nb of consecutive zeroes found
	// misc.
	bool  isWhite   = true; // lines always begin with a white code
	bool  b         = false;
	// T4 codes managment
	short code      = 0;
	short length    = 0;
	short count     = 0;

	// do we do the thing for real?
	bool real = (( m_height > 0 ) && ( m_width > 0 ));
	try
	{
		SkipToEOL();  // skip the first EOL
		while (!EndOfFile)
		{
			Z = m_ibuf.GetNbZeroes();
			b = m_ibuf.ReadNextBitZ();
			if (Z > 10)             // eat eventual fill bits
			{
				if (b)              // we found an EOL marker
				{
					if ( j == 0 )   // are we at the begining of a line ?
					{
						NbEOL ++;
						if (NbEOL > 5) 
							EndOfFile = true;
					}
					else
					{
						NbEOL = 1;
						if (    (i!=0)                       // we are not at the begining of the file
							 && (    ( real &&(j!=m_width))  // we are not at the end of a line
								  || (!real && (j < width))  // we found a line shorter than expected
								)
						   )
						{  // then, we shouldn't find an EOL.
#ifdef _DEBUG
							std::cout  << "Unexpected EOL at line " << i << std::endl;
#endif
							if(real)
							{
								m_opbm->ResetNextNBit( m_width - j);
								SetLineValidity(i, -j);
							}
						}
						else // expected EOL
						{
							SetLineValidity(i, j); // here, j==m_width
						}

						i++;
						if ( real && ( i > m_height ))
						{
#ifdef _DEBUG
							std::cout << "File too long...\n";
#endif
							EndOfFile=true;
						}
					}
					// if we are dummy-running, capture the maximum width
					// this does not allow for detecting bit errors in the file...
					if(!real && (j > width))
						width = j;
					j = 0;
					isWhite = true;
					code = 0;
					length = 0;
				}
			}
			else // we are shure it is not an EOL
			{
				code <<= 1;
				if (b)
				{
					code |= 1;
				}
				length ++;
				if (length > 13) // it is certainly a non valid code
				{
#ifdef _DEBUG
					std::cout << "Error decoding line "<< i <<" at col "<< j << std::endl;
#endif
					if(real)
					{
						m_opbm->ResetNextNBit( m_width - j);
						SetLineValidity(i, -j);
					}
					j=m_width; // to guard us agains a second COutOfBuffer if SkipToEOL fails...
					SkipToEOL();
					NbEOL++;
					code =0;
					length = 0;
					j=0;
					isWhite = true;
					i++;
					if ( real && ( i > m_height ))
					{
#ifdef _DEBUG
						std::cout << "File too long...\n";
#endif
						EndOfFile=true;
					}
				}
				else if ( (isWhite && (length >= 4) ) || (!isWhite && (length >= 2) ) ) 
				{
					count = m_AllDecodes.GetCount( code, length, isWhite);
					if (count >= 0) 
					{
						code = 0;
						length = 0;
						if (real)
						{
							if (j + count > m_width)
							{
#ifdef _DEBUG
								std::cout << "Line "<< i << " too long.\n";
#endif
								m_opbm->ResetNextNBit( m_width - j );
								SetLineValidity(i, -j);
								j=m_width; // to guard us agains a second COutOfBuffer if SkipToEOL fails...
								SkipToEOL();
								NbEOL++;
								count = 1000; // to avoid triggering the color reversal if count < 64
								j=-count;  // to cover the j+=count later.
								isWhite = true;
								i++;
								if ( i >= m_height )
								{
#ifdef _DEBUG
									std::cout << "File too long...\n";
#endif
									EndOfFile=true;
								}
							}
							else
							{
								if (isWhite)
								{
									m_opbm->ResetNextNBit( count );
								}
								else
								{
									m_opbm->SetNextNBit( count );
								}
							}
						}
						j+=count;
						if (count < 64) 
							isWhite = !isWhite;
					}
					count = 0;
				}
			}
		}
	}
	catch ( COutOfBufferException )
	{
		// If we get here, it means (at least) we lost the 6 EOL markers
		// So fill the rest of the image with white

//		Util::LogReason("Expected",Util::CNamedException("Out of buffer exception successfully handled"));
#ifdef _DEBUG
		std::cout << "Exception at ("<< i <<","<< j << "). Lost eof (at least)\n";
#endif 
	}
	if(real)
	{
		// this is only needed in case of OutOfBuffer exception or of prematurate
		// EOF (due to a missing EOL or a bad code in the file.
		if ( i < m_height)
		{
#ifdef _DEBUG
			std::cout << "There was a problem while decoding the file\n";
#endif
			//these loops should NEVER generate an OutOfBuffer Exceptions
			m_opbm->ResetNextNBit( m_width-j );
			SetLineValidity(i, -j);
			i++;
			for(;i < m_height;i++)
			{
				m_opbm->ResetNextNBit( m_width );
				SetLineValidity(i, 0);
			}
		}
	}

	if (!real)
	{
		m_height = i;
		m_width  = width;
	}
	m_ibuf.ResetBitIndex();
	COMP_CATCHTHIS
}


// create the CDataFieldUncompressedImage from the CBitBuffer and returns it
Util::CDataFieldUncompressedImage CT4Decoder::GetDecompressedImage()
{
	COMP_TRYTHIS
	Util::CDataFieldUncompressedImage dfui( (Util::CDataField)(*m_opbm), 1,  m_width, m_height, 1);
	return dfui;
	COMP_CATCHTHIS
}


} // end namespace
