/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <QDebug>
#include <QDir>
#include <QFile>
#include <QStringList>
#include <QTextStream>

#include "MvQOdbMetaData.h"

#ifdef METVIEW_ODB_NEW

//#include <odb_api/odblib/odb_api.h>
#ifdef ODB_ECKIT
    #include <odb_api/ColumnType.h>
    #include <odb_api/Reader.h>
#else
    #include <odb_api/odblib/ColumnType.h>
    #include <odb_api/odblib/Reader.h>
#endif

using namespace odb;

extern "C" {
#ifdef ODB_ECKIT
    #include <odb_api/odbcapi.h>
#else
    #include <odb_api/odblib/odbcapi.h>
#endif
}

static QMap<ColumnType,MvQOdbColumn::OdbColumnType> typeIdMap;
static QMap<ColumnType,QString> typeIdNameMap;

#endif

void MvQOdbTable::findLinks()
{
	linksTo_.clear();
	
	foreach(MvQOdbColumn *col,columns_)
	{
		if(col->type().contains("@LINK"))
		{
			QStringList lst=col->name().split("@");
			if(lst.count() > 1 && lst[0].isEmpty() == false)
			{
			  	linksTo_ << lst[0];
			}	
		}
	}			
}	


MvQOdbMetaData::MvQOdbMetaData(string path,string odbVersion)
{
	path_=QString::fromStdString(path);

	if(odbVersion == "ODB_NEW")
	{
#ifndef METVIEW_ODB_NEW
		odbVersion_=InvalidVersion;
		return;
#else	
		odbVersion_=Version2;
#endif
	}
	else if(odbVersion == "ODB_OLD")
	{
		odbVersion_=Version1;
	}
	else
	{
		odbVersion_=InvalidVersion;
	}

#ifdef METVIEW_ODB_NEW
	if(typeIdMap.isEmpty())
	{
		typeIdMap[odb::IGNORE]=MvQOdbColumn::None;
		typeIdMap[odb::INTEGER]=MvQOdbColumn::Int;
		typeIdMap[odb::REAL]=MvQOdbColumn::Float;
		typeIdMap[odb::STRING]=MvQOdbColumn::String;
		typeIdMap[odb::BITFIELD]=MvQOdbColumn::Bitfield;
		typeIdMap[odb::DOUBLE]=MvQOdbColumn::Double;

		typeIdNameMap[odb::IGNORE]="none";
		typeIdNameMap[odb::INTEGER]="int";
		typeIdNameMap[odb::REAL]="float";
		typeIdNameMap[odb::STRING]="string";
		typeIdNameMap[odb::BITFIELD]="bitfield";
		typeIdNameMap[odb::DOUBLE]="double";	
	}
#endif

	if(odbVersion_==Version1)
	{
		loadSchemaFile();
	}

	else if(odbVersion_==Version2)
	{
#ifdef METVIEW_ODB_NEW	
		loadOdbHeader();
#endif
	}
}

MvQOdbMetaData::~MvQOdbMetaData()
{
	foreach(MvQOdbTable* t,tables_.values())
	{	
		delete t;
	}
	
	foreach(MvQOdbColumn* c, columns_)
	{
		delete c;
	}

	foreach(MvQOdbVar* v, vars_)
	{
		delete v;
	}
}

QString MvQOdbMetaData::odbVersionString()
{
	if(odbVersion_ == Version1)
	{
		return "ODB 1";
	}
	else if(odbVersion_ == Version2)
	{
		return "ODB 2";
	}
	else
	{
		return "Invalid";	
	}

	return QString();
}

void MvQOdbMetaData::removeCommentFromLine(QString &line)
{
	int commentIndex=line.indexOf("//");
	if(commentIndex != -1)
	{
		line.remove(commentIndex,line.size()-commentIndex+1);
	}
}


void MvQOdbMetaData::loadSchemaFile()
{
	QDir dir(path_);  
	QStringList filters;
     	filters << "*.sch";  
	QStringList schLst=dir.entryList(filters,QDir::Files | QDir::NoDotAndDotDot);

	QMap<QString,QList<MvQOdbBitfieldMember*> > bitfields;

	if(schLst.count() < 1)
	{
		return;
	}

	QFile file(path_ + "/" + schLst[0]);

	if(file.open(QFile::ReadOnly | QFile::Text) == false)
	{
		return;
	}

	QTextStream in(&file);

	while (!in.atEnd()) 
	{
        	QString line = in.readLine();

		//Vars
		if(line.startsWith("SET"))
		{
			QStringList lst=line.split(" ",QString::SkipEmptyParts);
			if(lst.count() == 4 && lst[2] == "=")
			{
				QString name, val;
				if(lst[1].startsWith("$"))
				{
					name=lst[1].remove(0,1);
				}
				if(lst[3].endsWith(";"))
				{
					val=lst[3].remove(lst[3].size()-1,1);
				}
				
				if(name.isEmpty() == false && 
				   val.isEmpty() == false)
				{
					vars_.push_back(new MvQOdbVar(name,val));
				}
			}	
		}

		//Bitfield
		if(line.startsWith("CREATE TYPE"))
		{
			QString name, bitLen, bitPos;
			int bitPosInt=0, bitLenInt=0;	
			
			removeCommentFromLine(line);
			QString str=line;
			while(!in.atEnd() && line.contains(");") == false)
			{				
				line=in.readLine();
				removeCommentFromLine(line);
				str += " " + line;
				
			}

			QStringList lst=str.split(" ",QString::SkipEmptyParts);

			if(lst.size() < 3)
			{
				break;
			}
	
			QString bfName=lst[2];	
			QList<MvQOdbBitfieldMember*> bf;

			for(int i=5; i < lst.size(); i+=2)
			{
				name=lst[i];
				if(i+1 < lst.size() && lst[i+1].endsWith(","))
				{
					bitLen=lst[i+1].remove(lst[i+1].size()-1,1);	
					bitLen.remove("bit");
					bitLenInt=bitLen.toInt();
					bitPos=QString::number(bitPosInt);		
					bf.push_back(new MvQOdbBitfieldMember(name,bitPosInt,bitLenInt));	
					bitPosInt+=bitLenInt;					
				}				
			}
			bitfields[bfName]=bf;

		}

		//Table
		if(line.startsWith("CREATE TABLE"))
		{
			removeCommentFromLine(line);

			QString name,type;
			QString str=line;
			while(!in.atEnd() && line.contains(");") == false)
			{
				line=in.readLine();
				removeCommentFromLine(line);
				str += " " + line;
			}

			QStringList lst=str.split(" ",QString::SkipEmptyParts);

			if(lst.size() < 3)
			{
				break;
			}

			//qDebug() <<  lst;

			QString tableName=lst[2];
			MvQOdbTable *table = new MvQOdbTable(tableName);
			for(int i=5; i < lst.size(); i+=2)
			{
				name=lst[i] + "@" +tableName;
				//qDebug() << "table: " << name;
				if(i+1 < lst.size() && lst[i+1].endsWith(","))
				{
					type=lst[i+1].remove(lst[i+1].size()-1,1);	
					MvQOdbColumn *col=new MvQOdbColumn(name,type,tableName);
					columns_.push_back(col);
					table->addColumn(col);	
				}				
			}
			
			table->findLinks();

			tables_[tableName]=table;

			//qDebug() << "table:" << tableName << table->linksTo() ;
		}
	}

	file.close();

	foreach(MvQOdbColumn *col,columns_)
	{
		if(bitfields.contains(col->type()))
		{
			foreach(MvQOdbBitfieldMember *bf,bitfields[col->type()])
			{
				col->addBitfieldMember(new MvQOdbBitfieldMember(*bf));
			}	
			//qDebug() << "num:" << bitfields[col->type()].size() << col->bitfieldNum();
		}	
		//qDebug() << col->name();
	}

	//Delete tmp bitfields
	QMapIterator<QString,QList<MvQOdbBitfieldMember*> > it(bitfields);
 	while (it.hasNext()) 
	{
     		it.next();
		foreach(MvQOdbBitfieldMember* b,it.value())
			delete b;
	}

	getTableTree();
}

void MvQOdbMetaData::getTableTree()
{
        QMap<QString,MvQOdbTable*> tblMap;
 	
	QMapIterator<QString, MvQOdbTable*> it(tables_);
 	while (it.hasNext()) 
	{
     		it.next();
		foreach(QString tblName,it.value()->linksTo())
		{
			if(tables_.contains(tblName))
			{
				tables_[tblName]->addToLinksFrom(tblName);
			}
		}	
 	}

	//Find the root in the ODB table tree
	//It has only "to" links!!	

	int currentYPosMax=0;

	it.toFront();
	while (it.hasNext()) 
	{
     		it.next();
		if(it.value()->linksFrom().isEmpty())
		{
			MvQOdbTable *rt=it.value();

			rt->setTreePosX(0);
			rt->setTreePosY(currentYPosMax);
			//qDebug() << "root" << rt->name();
			
			int posy=currentYPosMax;
			computeTreePos(rt,posy);

			QMapIterator<QString, MvQOdbTable*> ityp(tables_);
			while (ityp.hasNext())
			{
				ityp.next();
				if(ityp.value()->treePosY() > currentYPosMax)
				{
					currentYPosMax=ityp.value()->treePosY(); 
				}
			}
			currentYPosMax++;
		}
	}

	/*int posy=0;
	if(rootTables_.count() > 0)
		computeTreePos(rootTables_[0],posy);*/

	/*QMapIterator<QString, MvQOdbTable*> it(tables_);
 	while (it.hasNext()) 
	{
     		it.next();
		if(it.value()->treePosX() > xr)
		{
			xr=it.value()->treePosX();
		}
		
		if(it.value()->treePosY() > yr)
		{
			yr=it.value()->treePosY();
		}
		
 	}*/

 	it.toFront();
	while (it.hasNext()) 
	{
     		it.next();
		//qDebug() << it.value()->name() << it.value()->treePosX() << it.value()->treePosY(); 
 	}

	/*
	xp=parent->treePosX()+1;
	yp=parent->treePosY();
	int i=0;
	foreach(MvQOdbTable* t,parent->linksFrom())
	{
		t->setTreePosX(xp);
		t->setTreePosY(yp);
		yp++;
	}



	//For each table find the position (x,y) in the tree!	
	int posX=1;
	vector<int> posY(100,0);
	QStack<QList<OdbTable*>::iterator> itActStack, itEndStack;		
	list<OdbTable*>::iterator itAct=root_->linksTo().begin();
	list<OdbTable*>::iterator itEnd=root_->linksTo().end();
	
	while(itAct != itEnd)
	{						
		(*itAct)->treePosX(posX);
		(*itAct)->treePosY(posY[posX]); 		
		
		//cout << "table: " << (*itAct)->name_ <<   " x: " << (*itAct)->treePosX_  << " y: "  << (*itAct)->treePosY_ << endl;
		
		posY[posX]=posY[posX]++;
		
		if(!(*itAct)->linksTo().empty())
		{
			itActStack.push(itAct);
			itEndStack.push(itEnd);
			
			//cout << " to stack: " << (*itAct)->name_ << endl;
			
			list<OdbTable*>::iterator itTmp=itAct;			
			itAct=(*itTmp)->linksTo().begin();
			itEnd=(*itTmp)->linksTo().end();			
			posX++;
			
			posY[posX]=posY[posX-1]-1;			
			
			continue;
		}
				
		itAct++;	
		
		while (itAct == itEnd && itActStack.empty() == false)
		{
			itAct=itActStack.top();						
			itEnd=itEndStack.top();
			
			itActStack.pop();
			itEndStack.pop();
			
			//cout << " rom stack: " << (*itAct)->name_ << endl;
			
			itAct++;
			posX--;
			
			posY[posX]=posY[posX+1];
																
		}			
	}	*/	

}	


void MvQOdbMetaData::computeTreePos(MvQOdbTable* parent,int &yp)
{
	//qDebug() << parent->name() << parent->treePosX() <<  parent->treePosY();

	int xp=parent->treePosX()+1;
	//int yp=parent->treePosY();
        //qDebug() << "    " << xp << yp;
	//qDebug() << "    " << parent->linksTo();

	foreach(QString tblName,parent->linksTo())
	{
		MvQOdbTable* t=tables_[tblName];
		t->setTreePosX(xp);
		t->setTreePosY(yp);
		computeTreePos(t,yp);
		yp++;
	}
}	
	
void MvQOdbMetaData::getTreePosRange(int &xr, int &yr)
{
	xr=0;
	yr=0;

	QMapIterator<QString, MvQOdbTable*> it(tables_);
 	while (it.hasNext()) 
	{
     		it.next();
		if(it.value()->treePosX() > xr)
		{
			xr=it.value()->treePosX();
		}
		
		if(it.value()->treePosY() > yr)
		{
			yr=it.value()->treePosY();
		}
		
 	}
}

#ifdef METVIEW_ODB_NEW

void MvQOdbMetaData::loadOdbHeader()
{	
	odb_start();
	odb::Reader oda(path_.toStdString());

        int row=0;

	odb::Reader::iterator it = oda.begin();

	for(MetaData::const_iterator itc=it->columns().begin(); itc != it->columns().end(); ++itc)
	{	
		MvQOdbColumn* acol = new MvQOdbColumn;

		string s=(*itc)->name();

		QString colName(s.c_str());
		QStringList lst=colName.split("@");
	
		if(lst.count() > 1)
		{
			acol->setTable(lst.back());	
		}

		acol->setName(QString(s.c_str()));
		acol->setConstant((*itc)->isConstant());
		acol->setMin((*itc)->min());
		acol->setMax((*itc)->max());
		
		if((*itc)->hasMissing())
		{  
			acol->setHasMissingValue((*itc)->hasMissing());
			acol->setMissingValue((*itc)->missingValue());
		}
		
		if(typeIdMap.contains((*itc)->type()))
		{
			acol->setTypeId(typeIdMap[(*itc)->type()]);
			acol->setType(typeIdNameMap[(*itc)->type()]);
		}
		
		if(acol->typeId() == MvQOdbColumn::Bitfield) 
		{
			int pos=0;
			for(unsigned int i=0; i < (*itc)->bitfieldDef().first.size() ; i++)
			{				
				string name=(*itc)->bitfieldDef().first.at(i);
				int size=(*itc)->bitfieldDef().second.at(i);
				acol->addBitfieldMember(new MvQOdbBitfieldMember(QString(name.c_str()),pos,size));
				pos+=size;
			}
		}								

		columns_.push_back(acol);
	}

	//qDebug() << "odb columns"  << columns_.size() << columns_.size() << columnNum();

}

#endif
