/************************************************************
 *              SAXMzxmlHandler.cpp
 * Adapted from the SAXMzmlHandler.cpp fork at ISB
 * Originally adapted from SAXMzdataHandler.cpp
 * August 2008
 * Ronald Beavis
 *
 * December 2010 - Drastically modified and cannibalized to create
 * robust, yet generic, mzXML pareser
 * Mike Hoopmann, Institute for Systems Biology
 *
 * See http://sashimi.sourceforge.net/schema_revision/mzXML_3.2/ for
 * mzXML schema information.
 *
 * Inspired by DtaSAX2Handler.cpp
 * copyright            : (C) 2002 by Pedrioli Patrick, ISB, Proteomics
 * email                : ppatrick@systemsbiology.org
 * Artistic License granted 3/11/2005
 *******************************************************/

#include "mzParser.h"



mzpSAXMzxmlHandler::mzpSAXMzxmlHandler(BasicSpectrum* bs){
	m_bInMsInstrument=false;
	m_bInDataProcessing=false;
	m_bInScan=false;
	m_bInPrecursorMz=false;
	m_bInMsRun=false;
	m_bInIndex=false;
	m_bInPeaks=false;
	m_bCompressedData=false;
	m_bHeaderOnly=false;
	m_bLowPrecision=false;
	m_bNetworkData=true;
	m_bNoIndex=true;
	m_bScanIndex=false;
	m_bIndexSorted = true;
	spec=bs;
	indexOffset=-1;
}

mzpSAXMzxmlHandler::~mzpSAXMzxmlHandler(){
	spec=NULL;
}

void mzpSAXMzxmlHandler::startElement(const XML_Char *el, const XML_Char **attr){

	string s;

	if (isElement("dataProcessing",el)) {
		m_bInDataProcessing=true;

	} else if(isElement("index",el)){
		if(!strcmp(getAttrValue("name", attr),"scan")) m_bScanIndex=true;
		m_vIndex.clear();
		m_bInIndex=true;

	} else if(isElement("msDetector",el)){
		m_instrument.detector=getAttrValue("value", attr);

	} else if(isElement("msInstrument",el)){
		m_bInMsInstrument=true;
		m_instrument.clear();
		m_instrument.id=getAttrValue("id", attr);

	} else if(isElement("msIonisation",el)){
		m_instrument.ionization=getAttrValue("value", attr);

	} else if(isElement("msManufacturer",el)){
		m_instrument.manufacturer=getAttrValue("value", attr);

	} else if(isElement("msMassAnalyzer",el)){
		m_instrument.analyzer=getAttrValue("value", attr);

	} else if(isElement("msModel",el)){
		m_instrument.model=getAttrValue("value", attr);

	} else if(isElement("msRun",el)){
		m_bInMsRun=true;

	} else if(isElement("offset",el) && m_bScanIndex){
		m_strData.clear();
		curIndex.scanNum=atoi(getAttrValue("id", attr));
		curIndex.idRef="";

	} else if(isElement("peaks",el)){
		m_strData.clear();
		m_bInPeaks=true;

		if(!strcmp("network",getAttrValue("byteOrder", attr))) m_bNetworkData=true;
		else m_bNetworkData=true; //make it true anyway.
		if(!strcmp("64",getAttrValue("precision", attr))) m_bLowPrecision=false;
		else m_bLowPrecision=true;

		m_bCompressedData=false;
		s=getAttrValue("compressionType", attr);
		if(!strcmp("zlib",&s[0])) m_bCompressedData=true;
		else if(!strcmp("none",&s[0])) m_bCompressedData=false;
		else if(s.length()>0) {
			cout << "Halting! Unknown compression type: " <<  &s[0] << endl;
			exit(-5);
		}
		s=getAttrValue("compressedLen", attr);
		if(s.length()>0) m_compressLen = (uLong)atoi(&s[0]);
		else m_compressLen=0;

		if(m_bHeaderOnly) stopParser();

	}	
	else if (isElement("precursorMz", el)) {
		m_strData.clear();
		s=getAttrValue("precursorCharge", attr);
		if(s.length()>0) spec->setPrecursorCharge(atoi(&s[0]));
		else spec->setPrecursorCharge(0);
		s=getAttrValue("precursorIntensity", attr);
		if(s.length()>0) spec->setPrecursorIntensity(atof(&s[0]));
		else spec->setPrecursorIntensity(0.0);
		s=getAttrValue("precursorScanNum", attr);
		if(s.length()>0) spec->setPrecursorScanNum(atoi(&s[0]));
		else spec->setPrecursorScanNum(0);
		m_bInPrecursorMz = true;
		
		s=getAttrValue("activationMethod", attr);
		if(s.length()>0){
			if(!strcmp("CID",&s[0])) spec->setActivation(CID);
			else if(!strcmp("ETD",&s[0])) spec->setActivation(ETD);
			else if(!strcmp("HCD",&s[0])) spec->setActivation(HCD);
			else if(!strcmp("ECD",&s[0])) spec->setActivation(ECD);
			else if(!strcmp("ETD+SA",&s[0])) spec->setActivation(ETDSA);
		} else {
			spec->setActivation(none);
		}

		s=getAttrValue("activationMethod", attr);
		if(s.length()>0){
			if(!strcmp("CID",&s[0])) spec->setActivation(CID);
			else if(!strcmp("ETD",&s[0])) spec->setActivation(ETD);
			else if(!strcmp("HCD",&s[0])) spec->setActivation(HCD);
			else if(!strcmp("ECD",&s[0])) spec->setActivation(ECD);
			else if(!strcmp("ETD+SA",&s[0])) spec->setActivation(ETDSA);
		} 
	}	
	else if (isElement("scan", el)) {
		if(m_bInScan){
			pushSpectrum();
			stopParser();
		} else {
			m_bInScan=true;
			spec->setScanNum(atoi(getAttrValue("num", attr)));
			spec->setMSLevel(atoi(getAttrValue("msLevel", attr)));
			spec->setBasePeakIntensity(atof(getAttrValue("basePeakIntensity", attr)));
			spec->setBasePeakMZ(atof(getAttrValue("basePeakMz", attr)));
			spec->setCentroid((bool)getAttrValue("centroided",attr));
			spec->setCollisionEnergy(atof(getAttrValue("collisionEnergy", attr)));
			spec->setCompensationVoltage(atof(getAttrValue("CompensationVoltage", attr)));
			s=getAttrValue("filterLine", attr);
			spec->setFilterLine(&s[0]);
			spec->setHighMZ(atof(getAttrValue("highMz", attr)));
			spec->setLowMZ(atof(getAttrValue("lowMz", attr)));
			if(spec->getHighMZ()==0) spec->setHighMZ(atof(getAttrValue("endMz", attr)));
			if(spec->getLowMZ()==0) spec->setLowMZ(atof(getAttrValue("startMz", attr)));
			spec->setTotalIonCurrent(atof(getAttrValue("totIonCurrent",attr)));
			m_peaksCount = atoi(getAttrValue("peaksCount", attr));
			spec->setPeaksCount(m_peaksCount);
			
			s=getAttrValue("retentionTime", attr);
			if(s.length()>0){
				float f=(float)atof(&s[2]);
				if(s[s.length()-1]=='S') spec->setRTime(f/60.0f);
				else spec->setRTime(f);
			} else {
				spec->setRTime(0.0f);
			}
		}

	}
}


void mzpSAXMzxmlHandler::endElement(const XML_Char *el) {

	if(isElement("dataProcessing", el))	{
		m_bInDataProcessing=false;

	} else if(isElement("index",el)){
		m_bInIndex=false;
		posIndex=-1;
		stopParser();
		if (!m_bIndexSorted) {
      qsort(&m_vIndex[0],m_vIndex.size(),sizeof(cindex),cindex::compare);
      m_bIndexSorted=true;
		}

	} else if(isElement("msInstrument",el)){
		m_vInstrument.push_back(m_instrument);
		m_bInMsInstrument=false;

	} else if(isElement("msRun",el)){
		m_bInMsRun=false;

	} else if(isElement("offset",el) && m_bScanIndex){
		if(!m_bInIndex){ //bad index reference
			cout << "Index offset points to wrong location. Please rebuild index." << endl;
			m_bNoIndex=true;
			stopParser();
		}
		curIndex.offset=mzpatoi64(&m_strData[0]);
		m_vIndex.push_back(curIndex);
		if (m_bIndexSorted && m_vIndex.size() > 1) {
		  if (m_vIndex[m_vIndex.size()-1].scanNum<m_vIndex[m_vIndex.size()-2].scanNum) {
		    m_bIndexSorted = false;
		  }
		}

	} else if(isElement("peaks",el)){
		if(m_bLowPrecision && m_bCompressedData) decompress32();
		else if(m_bLowPrecision && !m_bCompressedData) decode32();
		else if(!m_bLowPrecision && m_bCompressedData) decompress64();
		else decode64();
		m_bInPeaks = false;

	}	else if(isElement("precursorMz", el)) {
		spec->setPrecursorMZ(atof(&m_strData[0]));
		m_bInPrecursorMz=false;
		
	} else if(isElement("scan",el)) {
		m_bInScan = false;
		pushSpectrum();
		stopParser();

	}
}

void mzpSAXMzxmlHandler::characters(const XML_Char *s, int len) {
	m_strData.append(s, len);
}

bool mzpSAXMzxmlHandler::readHeader(int num){
	spec->clear();

	if(m_bNoIndex){
		cout << "Currently only supporting indexed mzXML" << endl;
		return false;
	}

	//if no scan was requested, grab the next one
	if(num<0){
		posIndex++;
		if(posIndex>=(int)m_vIndex.size()) return false;
		m_bHeaderOnly=true;
		parseOffset(m_vIndex[posIndex].offset);
		m_bHeaderOnly=false;
		return true;
	}

	//Assumes scan numbers are in order
	int mid=m_vIndex.size()/2;
	int upper=m_vIndex.size();
	int lower=0;
	while(m_vIndex[mid].scanNum!=num){
		if(lower==upper) break;
		if(m_vIndex[mid].scanNum>num){
			upper=mid-1;
			mid=(lower+upper)/2;
		} else {
			lower=mid+1;
			mid=(lower+upper)/2;
		}
	}

	//need something faster than this, perhaps binary search
	if(m_vIndex[mid].scanNum==num) {
		m_bHeaderOnly=true;
		parseOffset(m_vIndex[mid].offset);
		//force scan number; this was done for files where scan events are not numbered
		if(spec->getScanNum()!=m_vIndex[mid].scanNum) spec->setScanNum(m_vIndex[mid].scanNum);
		spec->setScanIndex(mid+1); //set the index, which starts from 1, so offset by 1
		m_bHeaderOnly=false;
		posIndex=mid;
		return true;
	}
	return false;
}

bool mzpSAXMzxmlHandler::readSpectrum(int num){
	spec->clear();

	if(m_bNoIndex){
		cout << "Currently only supporting indexed mzXML" << endl;
		return false;
	}

	//if no scan was requested, grab the next one
	if(num<0){
		posIndex++;
		if(posIndex>=(int)m_vIndex.size()) return false;
		parseOffset(m_vIndex[posIndex].offset);
		return true;
	}

	//Assumes scan numbers are in order
	int mid=m_vIndex.size()/2;
	int upper=m_vIndex.size();
	int lower=0;
	while(m_vIndex[mid].scanNum!=num){
		if(lower==upper) break;
		if(m_vIndex[mid].scanNum>num){
			upper=mid-1;
			mid=(lower+upper)/2;
		} else {
			lower=mid+1;
			mid=(lower+upper)/2;
		}
	}

	//need something faster than this perhaps
	if(m_vIndex[mid].scanNum==num) {
		parseOffset(m_vIndex[mid].offset);
		//force scan number; this was done for files where scan events are not numbered
		if(spec->getScanNum()!=m_vIndex[mid].scanNum) spec->setScanNum(m_vIndex[mid].scanNum);
		spec->setScanIndex(mid+1); //set the index, which starts from 1, so offset by 1
		posIndex=mid;
		return true;
	}
	return false;
}

void mzpSAXMzxmlHandler::pushSpectrum(){

	specDP dp;
	for(unsigned int i=0;i<vdM.size();i++)	{
		dp.mz = vdM[i];
		dp.intensity = vdI[i];
		spec->addDP(dp);
	}
	
}

void mzpSAXMzxmlHandler::decompress32(){

	vdM.clear();
	vdI.clear();
	if(m_peaksCount < 1) return;
	
	union udata {
		float f;
		uint32_t i;
	} uData;

	uLong uncomprLen;
	uint32_t* data;
	int length;
	const char* pData = m_strData.data();
	size_t stringSize = m_strData.size();
	
	//Decode base64
	char* pDecoded = (char*) new char[m_compressLen];
	memset(pDecoded, 0, m_compressLen);
	length = b64_decode_mio( (char*) pDecoded , (char*) pData, stringSize );
	pData=NULL;

	//zLib decompression
	data = new uint32_t[m_peaksCount*2];
	uncomprLen = m_peaksCount * 2 * sizeof(uint32_t);
	uncompress((Bytef*)data, &uncomprLen, (const Bytef*)pDecoded, length);
	delete [] pDecoded;

	//write data to arrays
	int n = 0;
	for(int i=0;i<m_peaksCount;i++){
		uData.i = dtohl(data[n++], m_bNetworkData);
		vdM.push_back((double)uData.f);
		uData.i = dtohl(data[n++], m_bNetworkData);
		vdI.push_back((double)uData.f);
	}
	delete [] data;
}

void mzpSAXMzxmlHandler::decompress64(){

	vdM.clear();
	vdI.clear();
	if(m_peaksCount < 1) return;
	
	union udata {
		double d;
		uint64_t i;
	} uData;

	uLong uncomprLen;
	uint64_t* data;
	int length;
	const char* pData = m_strData.data();
	size_t stringSize = m_strData.size();
	
	//Decode base64
	char* pDecoded = (char*) new char[m_compressLen];
	memset(pDecoded, 0, m_compressLen);
	length = b64_decode_mio( (char*) pDecoded , (char*) pData, stringSize );
	pData=NULL;

	//zLib decompression
	data = new uint64_t[m_peaksCount*2];
	uncomprLen = m_peaksCount * 2 * sizeof(uint64_t);
	uncompress((Bytef*)data, &uncomprLen, (const Bytef*)pDecoded, length);
	delete [] pDecoded;

	//write data to arrays
	int n = 0;
	for(int i=0;i<m_peaksCount;i++){
		uData.i = dtohl(data[n++], m_bNetworkData);
		vdM.push_back(uData.d);
		uData.i = dtohl(data[n++], m_bNetworkData);
		vdI.push_back(uData.d);
	}
	delete [] data;

}


void mzpSAXMzxmlHandler::decode32(){
// This code block was revised so that it packs floats correctly
// on both 64 and 32 bit machines, by making use of the uint32_t
// data type. -S. Wiley
	const char* pData = m_strData.data();
	size_t stringSize = m_strData.size();
	
	size_t size = m_peaksCount * 2 * sizeof(uint32_t);
	char* pDecoded = (char *) new char[size];
	memset(pDecoded, 0, size);

	if(m_peaksCount > 0) {
		// Base64 decoding
		// By comparing the size of the unpacked data and the expected size
		// an additional check of the data file integrity can be performed
		int length = b64_decode_mio( (char*) pDecoded , (char*) pData, stringSize );
		if(length != size) {
			cout << " decoded size " << length << " and required size " << (unsigned long)size << " dont match:\n";
			cout << " Cause: possible corrupted file.\n";
			exit(EXIT_FAILURE);
		}
	}

	// And byte order correction
	union udata {
		float fData;
		uint32_t iData;  
	} uData; 

	vdM.clear();
	vdI.clear();
	int n = 0;
	uint32_t* pDecodedInts = (uint32_t*)pDecoded; // cast to uint_32 for reading int sized chunks
	for(int i = 0; i < m_peaksCount; i++) {
		uData.iData = dtohl(pDecodedInts[n++], m_bNetworkData);
		vdM.push_back((double)uData.fData);
		uData.iData = dtohl(pDecodedInts[n++], m_bNetworkData);
		vdI.push_back((double)uData.fData);
	}

	// Free allocated memory
	delete[] pDecoded;
}

void mzpSAXMzxmlHandler::decode64(){

// This code block was revised so that it packs floats correctly
// on both 64 and 32 bit machines, by making use of the uint32_t
// data type. -S. Wiley
	const char* pData = m_strData.data();
	size_t stringSize = m_strData.size();

	size_t size = m_peaksCount * 2 * sizeof(uint64_t);
	char* pDecoded = (char *) new char[size];
	memset(pDecoded, 0, size);

	if(m_peaksCount > 0) {
		// Base64 decoding
		// By comparing the size of the unpacked data and the expected size
		// an additional check of the data file integrity can be performed
		int length = b64_decode_mio( (char*) pDecoded , (char*) pData, stringSize );
		if(length != size) {
			cout << " decoded size " << length << " and required size " << (unsigned long)size << " dont match:\n";
			cout << " Cause: possible corrupted file.\n";
			exit(EXIT_FAILURE);
		}
	}

	// And byte order correction
	union udata {
		double fData;
		uint64_t iData;  
	} uData; 

	vdM.clear();
	vdI.clear();
	int n = 0;
	uint64_t* pDecodedInts = (uint64_t*)pDecoded; // cast to uint_64 for reading int sized chunks
	for(int i = 0; i < m_peaksCount; i++) {
		uData.iData = dtohl(pDecodedInts[n++], m_bNetworkData);
		vdM.push_back(uData.fData);
		uData.iData = dtohl(pDecodedInts[n++], m_bNetworkData);
		vdI.push_back(uData.fData);
	}

	// Free allocated memory
	delete[] pDecoded;
}

unsigned long mzpSAXMzxmlHandler::dtohl(uint32_t l, bool bNet) {

#ifdef OSX
	if (!bNet)
	{
		l = (l << 24) | ((l << 8) & 0xFF0000) |
			(l >> 24) | ((l >> 8) & 0x00FF00);
	}
#else
	if (bNet)
	{
		l = (l << 24) | ((l << 8) & 0xFF0000) |
			(l >> 24) | ((l >> 8) & 0x00FF00);
	}
#endif
	return l;
}

uint64_t mzpSAXMzxmlHandler::dtohl(uint64_t l, bool bNet) {

#ifdef OSX
	if (!bNet)
	{
		l = (l << 56) | ((l << 40) & 0xFF000000000000LL) | ((l << 24) & 0x0000FF0000000000LL) | ((l << 8) & 0x000000FF00000000LL) |
			(l >> 56) | ((l >> 40) & 0x0000000000FF00LL) | ((l >> 24) & 0x0000000000FF0000LL) | ((l >> 8) & 0x00000000FF000000LL) ;
	}
#else
	if (bNet)
	{
		l = (l << 56) | ((l << 40) & 0x00FF000000000000LL) | ((l << 24) & 0x0000FF0000000000LL) | ((l << 8) & 0x000000FF00000000LL) |
			(l >> 56) | ((l >> 40) & 0x000000000000FF00LL) | ((l >> 24) & 0x0000000000FF0000LL) | ((l >> 8) & 0x00000000FF000000LL) ;
	}
#endif
	return l;
}

//Finding the index list offset is done without the xml parser
//to speed things along. This can be problematic if the <indexListOffset>
//tag is placed anywhere other than the end of the mzML file.
f_off mzpSAXMzxmlHandler::readIndexOffset() {

	char buffer[200];
	char chunk[CHUNK];
	char* start;
	char* stop;
	int readBytes;
	size_t sz;

	if(!m_bGZCompression){
		FILE* f=fopen(&m_strFileName[0],"r");
		mzpfseek(f,-200,SEEK_END);
		sz = fread(buffer,1,200,f);
		fclose(f);
		start=strstr(buffer,"<indexOffset>");
		stop=strstr(buffer,"</indexOffset>");
	} else {
		readBytes = gzObj.extract(fptr, gzObj.getfilesize()-200, (unsigned char*)chunk, CHUNK);
		start=strstr(chunk,"<indexOffset>");
		stop=strstr(chunk,"</indexOffset>");
	}

	if(start==NULL || stop==NULL) {
		cerr << "No index list offset found. File will not be read." << endl;
		return 0;
	}

	char offset[64];
	int len=(int)(stop-start-13);
	strncpy(offset,start+13,len);
	offset[len]='\0';
	return mzpatoi64(offset);

}

bool mzpSAXMzxmlHandler::load(const char* fileName){
	if(!open(fileName)) return false;
	indexOffset = readIndexOffset();
	if(indexOffset==0){
		m_bNoIndex=true;
		return false;
	} else {
		m_bNoIndex=false;
		if(!parseOffset(indexOffset)){
			cerr << "Cannot parse index. Make sure index offset is correct or rebuild index." << endl;
			return false;
		}
		posIndex=-1;
	}
	m_vInstrument.clear();
	parseOffset(0);
	return true;
}


void mzpSAXMzxmlHandler::stopParser(){
	m_bStopParse=true;
	XML_StopParser(m_parser,false);

	//reset mzML flags
	m_bInMsInstrument=false;
	m_bInDataProcessing=false;
	m_bInScan=false;
	m_bInPrecursorMz=false;
	m_bInMsRun=false;
	m_bInIndex=false;
	m_bInPeaks=false;
	m_bLowPrecision=false;

	//reset other flags
	m_bScanIndex=false;
}

int mzpSAXMzxmlHandler::highScan() {
	if(m_vIndex.size()==0) return 0;
	return m_vIndex[m_vIndex.size()-1].scanNum;
}

int mzpSAXMzxmlHandler::lowScan() {
	if(m_vIndex.size()==0) return 0;
	return m_vIndex[0].scanNum;
}

vector<cindex>* mzpSAXMzxmlHandler::getIndex(){
	return &m_vIndex;
}

f_off mzpSAXMzxmlHandler::getIndexOffset(){
	return indexOffset;
}

instrumentInfo mzpSAXMzxmlHandler::getInstrument(){
	return m_instrument;
}

int mzpSAXMzxmlHandler::getPeaksCount(){
	return m_peaksCount;
}
