/**********************************************************************
 *
 * Fdt.cpp
 * Copyright (C) 2003  UNESCO
 *
 * A component of the Greenstone digital library software
 * from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * This program is free software; you can 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 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *********************************************************************/

///////////////////////////////////////////////////////////////////////////
//  fdt.cpp

#include "stdafx.h"
#include "Fdt.h"
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <stdio.h>

//////////////////////////////
 
CFdt::CFdt()   
// Constructor
{
   InitData();
   InitFields();
}

//////////////////////////////

CFdt::~CFdt()
{
	for (int i = 0; i < m_nFieldCount; i++)
	{
#ifndef _MSC_VER
		CFieldDef* p = (CFieldDef*) m_fieldArray[i];
#else
		CFieldDef* p = (CFieldDef*) m_fieldArray.at(i);
#endif
    	delete p;
	}
	m_fieldArray.clear();

	Close();
}

////////////////////////////

bool CFdt::Open(const _TCHAR *szFileName, bool readOnly)
{
	if (!m_bIsEmpty)
		Close();
	m_sFileName = szFileName;
	if (!Load(szFileName))
	{
		Close();
		return false;
	}

	m_bReadOnly = readOnly;
	return Seek(0);   // 
}




//////////////////////////////

void CFdt::InitData()
{
   m_iCurrentRecord = -1;
   m_sFileName.erase();
   
   m_bReadOnly = false;
   m_bIsEmpty  = true;
   m_fdtEntry.Clear();
   m_bDirty    = false;
   m_aFdt.clear();
   m_asPrelude.clear();
   // Suppose the fdt is empty and we are 
   // going to append 
   m_bAppend = true;
}

//////////////////////////////

void CFdt::Close()
{
	if (!m_bIsEmpty)
		DeleteContents();

    InitData();        // Reset data members   
	m_bIsEmpty = true;
}

////////////////////////////////////
 
void CFdt::DeleteContents()
// Delete the fdt data structures
{
    m_aFdt.clear();
  
}



bool CFdt::Load(const _TCHAR *dbaseName)
{
	std::ifstream in((const char*)dbaseName);
	if (!in)
    {
//#ifdef _WIN32
//	   CString strMsg;
//		strMsg.Format(_T("Error opening %s for reading. "), dbaseName);
//		AfxMessageBox(strMsg, MB_OK | MB_ICONEXCLAMATION);
//#else
		std::cout << _T("Error opening") << dbaseName << _T("for reading") << std::endl;
//#endif
		return false;
    }


	std::string s;

    while (std::getline(in,s) && s.substr(0,3) != "***")
    {
		m_asPrelude.push_back(s);
    }

    while (std::getline(in,s))
    {
        CFdtEntry e;
        strncpy(e.name, s.c_str(), FDT_NAME_LENG);
	    e.name[FDT_NAME_LENG] = _T('\0');
	    for (int i=FDT_NAME_LENG-1; e.name[i]==_T(' '); e.name[i--]=_T('\0'))
		   ;
        if (s[FDT_NAME_LENG] == _T(' '))        //no subfield delimiters spec.
		 	e.subfields[0] = _T('\0');
	    else
		{
		 	strncpy(e.subfields, s.c_str()+FDT_NAME_LENG, FDT_SFLD_LENG);
			e.subfields[FDT_SFLD_LENG] = _T('\0');
			for (int i= FDT_SFLD_LENG-1; e.subfields[i]==_T(' '); e.subfields[i--]='\0')
				 ;
		}
	    sscanf(s.c_str() + FDT_NAME_LENG + FDT_SFLD_LENG, "%d %d %d %d",
										  &e.tag, &e.len, &e.type, &e.rep);
	    m_aFdt.push_back(e);
    }
    in.close();
    return true;
}

		
		
		


// Define the fields used for the dictionary

static CFieldDef fdtFields[7] =  { 
   {"Name",       'C', 30, 30, 0, 0, 30},
   {"Subfields",  'C', 20, 20, 0, 30, 20},
   {"Tag",        'N', 4, 4, 0,50, 4},
   {"Len",        'N', 4, 4, 0,54, 4},
   {"Type",       'N', 1, 1, 0,58, 4},
   {"Rep",        'C', 1, 1, 0,62, 4},
   {0,             0, 0, 0, 0, 0, 0}
}; 






//+----------------------------------------------------------------
//
// Member:     Store(const char* fileName)
//
// Synopsis:   Store dictionary tables on a file
//
// Parameters: 
//
//     fileName -- Full path of dictionary file
// 
//-----------------------------------------------------------------   

void FormatFdtEntry(CFdtEntry &e, ustring &s)
{
#ifdef _WIN32
  // Not sure why we could have code here that only runs if on Windows
	ustringstream ost;

	ost.setf(std::ios_base::left, std::ios_base::adjustfield);
	ost << std::setw(30) << e.name << std::setw(20) << e.subfields;
	ost.setf(std::ios_base::internal,std::ios_base::adjustfield);
	ost << " " << e.tag << " " << e.len << " " << e.type << " " << e.rep;
	s = ost.str();
#endif
}

void CFdt::Store(const char* fileName)
{
	if (m_bDirty)
	{
		Flush();
		m_bDirty = false;
	}

	std::ofstream out((const char *) fileName);
	TRACE(_T("\n<CFdt::Store> fileName=%s"),fileName);
	for (int i=0; i<m_asPrelude.size(); i++)
		out << m_asPrelude[i] << std::endl;
	
	out << "***" << std::endl;

	ustring s;
#ifdef _MSC_VER
	for (i=0; i< m_aFdt.size(); i++)
#else
	for (int i=0; i< m_aFdt.size(); i++)
#endif
	{
		FormatFdtEntry(m_aFdt[i], s);
		out << s << std::endl;
	}
    out.close();

}

//////////////////////////////

int CFdt::InitFields ()
{
	m_nFieldCount = 0;

	for (int i = 0; fdtFields[i].width != 0; i++)
	{
		CFieldDef* fld = (CFieldDef*) new CFieldDef();
		//TRACE("\n<CFdt::InitFields>i=%d after new",i);
		//TRACE("<CFdt::InitFields>After SetAtGrow");
		strcpy(fld->name, fdtFields[i].name);
		fld->type          = fdtFields[i].type;
		fld->len           = fdtFields[i].len;
		fld->width         = fdtFields[i].width;
		fld->decimals      = fdtFields[i].decimals;
		fld->offset        = fdtFields[i].offset;
		fld->display_width = fdtFields[i].display_width;

		int l = strlen(fld->name);
		if (fld->width < l)
			fld->width = static_cast<short>(l);

		m_fieldArray.push_back(fld);
		m_nFieldCount++;
	}
	//TRACE("\n<CFdt::InitFields>InitField After loop");
	return 1;
}

////////////////////////////////////////////////////////////////////

CFieldDef* CFdt::GetField(int n) const
// Returns field info
{
	ASSERT(n >= 0 && n <= m_nFieldCount);
#ifndef _MSC_VER
	return (CFieldDef*) m_fieldArray[n];
#else
	return (CFieldDef*) m_fieldArray.at(n);
#endif
}

/////////////////////////////////////////////////////////////////////
// Methods for accessing and maintaining the list of CDictTRecords 
// pointers

////////////////////////////
// First the List Accessors
////////////////////////////

CFdtEntry CFdt::GetAt(int i)
{
#ifndef _MSC_VER
	return m_aFdt[i];
#else
	return m_aFdt.at(i);
#endif

}


//////////////////////////

const CFdtEntry CFdt::GetAt(int i) const
{
#ifndef _MSC_VER
	return m_aFdt[i];
#else
	return m_aFdt.at(i);
#endif
}

//////////////////////////
// And now the modifiers
//////////////////////////

int CFdt::Append(CFdtEntry& e)
{
	int n = m_aFdt.size();
	m_aFdt.push_back(e);
    m_iCurrentRecord = -1;
    m_bIsEmpty = false;

    return m_aFdt.size();
}


/**-------------------------------------------------------------------
 * RemoveAt(int i)
 *
 *--------------------------------------------------------------------
 */
void CFdt::RemoveAt(int i)
{

	m_aFdt.erase(m_aFdt.begin()+i);
    m_iCurrentRecord = -1;
}

/**-------------------------------------------------------------------
 * SetAt(int i, CFdtEntry& e)
 *
 *--------------------------------------------------------------------
 */
void CFdt::SetAt(int i, CFdtEntry& e)
{

	m_aFdt[i] = e;
	// Force reloading
    m_iCurrentRecord = -1;
}

/////////////////////////////////////////////////////////////////
// 
// We use a buffer "m_fdtEntry" to hold the content of the current
// fdt entry. This buffer is filled by the "Seek" member function
// as follow:
//

//////////////////////////////

bool CFdt::Seek(mg_s_long iRecord)
{

	// move to new record and flush changes of previous record
	if (iRecord < 0 || iRecord > GetRecordCount())
		return false;
	else if (iRecord == m_iCurrentRecord)
		return true;

	if (m_iCurrentRecord != -1 &&  m_bDirty)
		Flush();
	if (m_bDirty)
	   /*AfxMessageBox(_T("Seek Alert Modification not saved"))*/;
	m_iCurrentRecord = iRecord;
	
	if (iRecord == GetRecordCount())
	{
		m_fdtEntry.Clear();
        m_bAppend = true;
	}
	else
	{
		TRACE(_T("\n<CFdt::Seek> Load iRecord=%d GetRecordCount()=%d"), iRecord, GetRecordCount());
		m_fdtEntry = GetAt(iRecord);
		m_bAppend = false;
	}
    m_bDirty = false;
	return true;
}



//+------------------------------------------------------------------
//
// Member:   Flush()
//
// Synopsis: Moves the changes made to the current record from
//           the buffer to the dictionary data structure so that
//           they are effective.
//-------------------------------------------------------------------    

void CFdt::Flush()
{
	if (m_iCurrentRecord == GetRecordCount())
	{
		Append(m_fdtEntry); 
	}
	else
	{
		SetAt(m_iCurrentRecord, m_fdtEntry);
	}
	m_bDirty = false;
}

//////////////////////////////////////////////////////////////
// Get the value of field n for the current record
//

bool CFdt::GetValue(int n, std::string& result) 
// Read value from buffer
{
	ASSERT(n >= 0 && n <= m_nFieldCount);
    ASSERT(m_iCurrentRecord >= 0 && m_iCurrentRecord <= GetRecordCount());
	
	if (!(n >= 0 && n <= m_nFieldCount))
		return false;

	result.erase();

	std::ostringstream os;
    switch (n)
	{
	    case 0:       // Field Name
			os << m_fdtEntry.name;
			result = os.str();
		    break;
	    case 1:       // Subfields
			os << m_fdtEntry.subfields;
			result = os.str();
		    break;
	    case 2:       // Tag
		  	if (m_fdtEntry.tag == 0)
				result = "";
			else
			{
			    os << m_fdtEntry.tag;
			    result = os.str();
			}
		    break;
	    case 3:       // length
		  	if (m_fdtEntry.len == 0)
				result = "";
			else
			{
			    os << m_fdtEntry.len;
			    result = os.str();
			}
		    break;
	    case 4:       // type
		  	if (m_fdtEntry.type == 0)
				result = "";
			else
			{
			    os << m_fdtEntry.type;
			    result = os.str();
			}
		    break;
	    case 5:       // Rep
		  	if (m_fdtEntry.rep == 0)
				result = "";
			else
			{
			    os << m_fdtEntry.rep;
			    result = os.str();
			}
		    break;
	}
	return true;
}


//////////////////////////////

bool CFdt::SetValue(int n, const char* pszValue)
// Write value to buffer
{
	ASSERT(n >= 0 && n <= m_nFieldCount);
    ASSERT(m_iCurrentRecord >= 0 && m_iCurrentRecord <= GetRecordCount());

	if (m_bReadOnly)
		return false;

	if (!(n >= 0 && n <= m_nFieldCount))
		return false;

    bool bEmpty = true;


	switch (n) 
	{
	case 0:       // Name
		strcpy(m_fdtEntry.name,(const char*)pszValue);
	//	TRACE("\nvalue=%s num=%d",pszValue,m_tRecord.num);
		break;
	case 1:       // Subfields
		strcpy(m_fdtEntry.subfields,(const char*)pszValue);
	//	TRACE("\nvalue=%s name=%d",pszValue,m_tRecord.name);
		break;
	case 2:       // Tag
		m_fdtEntry.tag = _ttoi(pszValue);
	//	TRACE("\nvalue=%s loc=%d",pszValue,m_tRecord.loc);
		break;
	case 3:       // Len
		m_fdtEntry.len = _ttoi(pszValue);
	//	TRACE("\nvalue=%s wid=%d",pszValue,m_tRecord.wid);
		break;
	case 4:       // Type
		m_fdtEntry.type = _ttoi(pszValue);
		break;
	case 5:       // Rep
		m_fdtEntry.type = _ttoi(pszValue);
		break;
	}
	m_bDirty = true;
	
	return true;
}

