/*
* Copyright (c) 2009 Stanford University, School of Medicine All rights reserved. 
* 
* STANFORD ACADEMIC SOFTWARE LICENSE AGREEMENT 
* FOR AngioLocalize SOURCE CODE: 
* 
* 1.  This is a legal agreement between you, RECIPIENT, and STANFORD UNIVERSITY.  
*       By accepting, receiving, and using AngioLocalize.including any accompanying information, materials or 
*       manuals (Program), you are agreeing to be bound by the terms of this Agreement.  
*       If you do not agree to the terms of this Agreement, promptly return the Program to the STANFORD.

* 2.  STANFORD grants to RECIPIENT a royalty-free, nonexclusive, and nontransferable license to use the Program 
*       and accompanying source code furnished hereunder, upon the terms and conditions set out below. 
*       documentation and/or other materials provided with the distribution. 
* 3.  RECIPIENT acknowledges that the Program is a research tool still in the development stage and that 
*       it is being supplied as is, without any accompanying services, support or improvements from STANFORD.
* 4.  RECIPIENT agrees to use the Program solely for internal non-commercial purposes. 
* 5.  RECIPIENT may create derivatives and further transfer the Program under the following terms and conditions: 
*        A) Recipient agrees to not commercially exploit the Program and agrees all uses will be for non-commercial purposes; and,
*        B) Recipient may not mark any derivatives with the title of Program and may not to use iPad mark for any derivatives; and,
*        C) Recipient agrees to mark and acknowledge all derivatives as follows: "This is a derivative work based on AngioLocalize:
*           Frameless AVM localization with 2D Digital Subtraction Angiography (DSA) and 3D Rotational Angiography (DRA) Images developed
*           by Dimitre Hristov, Lina Liu.
*        D) Recipient agrees to make all improvements, enhancements and derivatives available under 
*           similar terms and conditions to the research community. 
* 6.   RECIPIENT warrants that RECIPIENT will not remove or export any part of the software or Program from the United 
*       States except in full compliance with all United States and other applicable laws and regulations.
* 7.  RECIPIENT will use the Program in compliance with all applicable laws, policies and regulations including, but not 
*       limited to, any approvals, informed consent and patient confidentiality principles.
* 8.   RECIPIENT will indemnify, hold harmless, and defend Stanford against any claim of any kind arising out of or
*       related to the exercise of any rights granted under this Agreement or the breach of this Agreement by RECIPIENT.
* 9.  Title and copyright to the Program and any associated documentation shall at all times remain with STANFORD, 
*       and RECIPIENT agrees to preserve same.
*/ 
//----------------------------------------------------------------------------------
//! The ML module class ImportRTStructure.
/*!
// \file    mlImportRTStructure.cpp
// \author  Lina Liu, Dimitre Hristov
// \date    2010-01-15
//
// Import RT structure from a DCM file and convert it into a CSO group
*/
//----------------------------------------------------------------------------------

// Local includes
#include "STUImportRTStructure.h"
#include <iostream>

ML_START_NAMESPACE


//! Implements code for the runtime type system of the ML
ML_BASEOP_CLASS_SOURCE(ImportRTStructure, BaseOp);

//----------------------------------------------------------------------------------
//! Constructor
//----------------------------------------------------------------------------------
ImportRTStructure::ImportRTStructure ()
  : BaseOp(0, 0)
{
  ML_TRACE_IN("ImportRTStructure::ImportRTStructure ()")

  // Suppress calls of handleNotification on field changes to
  // avoid side effects during initialization phase.
  handleNotificationOff();

  //! Get reference to the container for parameters/fields.
  FieldContainer &fields = *getFieldContainer();
  
  ML_CHECK(&fields);

  //! initiate fields
  (_outListFld = fields.addBase("outCSOList"))->setBaseValue(NULL);
  _inDCMFileFld = fields.addString("inputDCMFile");
  _thresholdFld = fields.addInt("threshold");
  _dividendFld = fields.addInt("interpOpt1");
  _divisorFld = fields.addInt("interpOpt2");
  _allTagsFld = fields.addString("allTags");
  _applyFld = fields.addNotify("apply");
  _dcmObjFld = fields.addBase("outputBase");
  _dcmObjFld->setBaseValue(NULL);
  

  _dicmFileFld = fields.addString("dicomFile");
  std::string name("tagName");
  std::string value("tagValue");
  for (int i = 0; i < tagFieldNum; i++) {
	  std::stringstream strName;
	  strName << name;
	  strName << i;

	  std::stringstream strValue;
	  strValue << value;
	  strValue << i;
	  _tagNameFld[i] = fields.addString(strName.str().c_str());
      _tagValueFld[i] = fields.addString(strValue.str().c_str());
  }
  
  _getValuesFld = fields.addNotify("getValues");
  _trimSpacesFld = fields.addBool("trimSpaces");
  _printFld = fields.addNotify("print");

  //! initiate the memeber variables
  _outList = NULL;
  _objToTransfer = NULL;
  // Reactivate calls of handleNotification on field changes.
  handleNotificationOn();

  // Activate inplace data buffers for output outIndex and input inIndex.
  // setOutImgInplace(outIndex, inIndex);

  // Activate page data bypass from input inIdx to output outIdx.
  // Note that the module must still be able to calculate the output image.
  // setBypass(outIndex, inIndex);

  // Activate parallel execution of calcOutSubImage.
  // setThreadSupport(supportMode);
  // with supportMode =
  //   NO_THREAD_SUPPORT                 //! The module is not thread safe at all.
  //   CALC_OUTSUBIMAGE_ON_STD_TYPES     //! calcOutSubImage can be called in parallel for scalar voxel types.
  //   CALC_OUTSUBIMAGE_ON_CARRIER_TYPES //! calcOutSubImage can be called in parallel for carrier voxel types.
  //   CALC_OUTSUBIMAGE_ON_ALL_TYPES     //! calcOutSubImage can be called in parallel for all voxel types.
  // Warning: You should be familiar with multithreading before activating this feature.

  // Specify whether the module can only process standard scalar voxel types or
  // also registered voxel types (vec2, mat2, complexf, Vector, etc.)
  // setVoxelDataTypeSupport(permittedTypes);
  // with permittedTypes =
  //   ONLY_STANDARD_TYPES               //! Only standard scalar voxels are supported.
  //   FULLY_OPERATIONAL                 //! Scalar and registered voxels types are supported.
  //   MINIMUM_OPERATIONAL               //! Scalar and registered voxel types are supported.
  //                                     //! Voxel operators are not used by algorithm.
  //
  // See ML Programming Guide, "Configuring Image Processing Behaviour of the BaseOp"
  // for further details.
}

//----------------------------------------------------------------------------------
//! Handle field changes of the field field.
//----------------------------------------------------------------------------------
void ImportRTStructure::handleNotification (Field *field)
{
  ML_TRACE_IN("ImportRTStructure::handleNotification ()")
  
  // Handle changes of module parameters and connectors here.
  if (field == _applyFld) {
	  std::string inDCMFile = _inDCMFileFld->getStringValue();
	  if (inDCMFile.empty()) {
          //ML_PRINT_WARNING("ImportRTStructure::_HandleNotification",ML_BAD_PARAMETER, "Please load the dcm file first."); 
		  return;
	  }
	  if (RTStructure2CSO(inDCMFile) == false) 
		  return;
	  _outListFld->setBaseValue(_outList);
	  _outListFld->activateAttachments();
	  _allTagsFld->setStringValue(_allTagValues);
	  _dicmFileFld->setStringValue(inDCMFile);
	  removeContourData();
	  _objToTransfer = new RTStructure();
      std::ostringstream mystream;
	  _treePtr->toStream(_objToTransfer->stream, true);
      _dcmObjFld->setBaseValue(_objToTransfer);
	  mystream.clear();	  
  } else if (field == _getValuesFld) {
	  if (_treePtr == NULL) {
		//ML_PRINT_INFORMATION("ImportRTStructure::_HandleNotification",ML_BAD_PARAMETER, "Please load the dcm file first."); 
		return;
	  }
	  if(_treePtr->tagsSize() == 0)
		  return;

	  for (int i = 0; i < tagFieldNum; i ++) {
		  std::string tagName = _tagNameFld[i]->getStringValue();
		  if (tagName.empty()) {
			  continue;
		  }
		  DCMTree::Const_TagPtr tagPtr;
		  if (tagName.find(",") != std::string::npos) {
			  // tags are in (****,****) format
			  tagPtr = _treePtr->getTag(DCMTree::TagId(tagName));
			  _tagValueFld[i]->setStringValue(std::string(reinterpret_cast<const char*> (tagPtr->getValue().data())));
		  } else {
             //tags are in their name
			  tagPtr = _treePtr->getTag(tagName);
		  }
			 
		  std::string value = getTagValue(tagPtr);
		  if (_trimSpacesFld->getBoolValue() == true) {
				  std::string::iterator end_pos = std::remove(value.begin(), value.end(), ' ');
				  value.erase(end_pos, value.end());
			  }
			  _tagValueFld[i]->setStringValue(value);
		  } 
  } else if (field == _printFld) {
	  std::cout << _allTagValues << std::endl;
	  _allTagValues.clear();
	  
  } 
   
}

//-----------------------------------------------------------------------------------------
//! Update internal module state after load or clone and enable notification handling again.
//-----------------------------------------------------------------------------------------
std::string ImportRTStructure::getTagValue(DCMTree::Const_TagPtr constTagPtr)
{
	std::string value;
	DCMTree::TagInfo tagInfo = constTagPtr->info();
	if (constTagPtr!= NULL && !constTagPtr->isEmpty()) {
				DCMTree::Vr vr = tagInfo.vr();
				
				switch(vr)
				{
					case(DCMTree::UL) : 
					case(DCMTree::FL):
					case(DCMTree::FD):
					case(DCMTree::IS):
					case(DCMTree::LO): 
						value = constTagPtr->toString(); 
						break;
					default:
						value = reinterpret_cast<const char*> (constTagPtr->getValue().data());
				}
				
	} 
	return value;
}

//-----------------------------------------------------------------------------------------
//! Update internal module state after load or clone and enable notification handling again.
//-----------------------------------------------------------------------------------------
void ImportRTStructure::activateAttachments () 
{
  ML_TRACE_IN("ImportRTStructure::activateAttachments ()")
  
  // Update members to new field state here.
  // Call super class functionality to enable notification handling again.
  BaseOp::activateAttachments ();
}

//----------------------------------------------------------------------------------
//! This function converts RTStrcture in the input DCM file into a CSOGroup 
//----------------------------------------------------------------------------------
void  ImportRTStructure::getDCMTreeFromFile(std::string inDCMFile) 
{
	DCMTree::TreePtr treePtr;
	try {
		DCMTree::IOParameterPtr ioParamPtr = DCMTree::IOParameter::create(std::string(inDCMFile), false);
        DCMTree::ReaderPtr      reader     = DCMTree::Reader::create(DCMTree::Dict::singleton());
        DCMTree::MessagePtr     messagePtr = reader->read(ioParamPtr);
        _treePtr    = messagePtr->tags();
    }catch(DCMTree::Exception& ex){    
		 ML_PRINT_ERROR("ImportRTStructure::_HandleNotification",ML_BAD_PARAMETER, ex.toString().c_str()); 
    }
}

//----------------------------------------------------------------------------------
//! This function converts RTStrcture in the input DCM file into a CSOGroup 
//----------------------------------------------------------------------------------
bool  ImportRTStructure::RTStructure2CSO(std::string inDCMFile) 
{	
	getDCMTreeFromFile(inDCMFile);
	if (_treePtr == NULL) 
		return false;
	ML_DELETE(_outList);
	_outList = new CSOList();
	parseCSOGroup();
	parseCSO();
	fillUpDCMTagViewer();
	return true;
}

//------------------------------------------------------------------------------------------------------------------
//! This function fill up the DCMTag Viewer with patient information, acquisition information and Image information
//------------------------------------------------------------------------------------------------------------------
void ImportRTStructure::fillUpDCMTagViewer() 
{
	 _allTagValues.clear();
	 const DCMTree::TagPtrMap map= _treePtr->tags();
	 std::map<DCMTree::TagId,DCMTree::TagPtr>::const_iterator itr; 
	 for (itr = map.begin(); itr != map.end(); ++itr) {
		DCMTree::TagId id = itr->first;
		DCMTree::TagPtr ptr = itr->second;
		DCMTree::Const_TagPtr constTagPtr = _treePtr->getTag(id);
		if (!constTagPtr->isSequence()) {
			std::string idChar = id.toString();
			DCMTree::TagInfo tagInfo = constTagPtr->info();
			std::string name = _treePtr->getDict()->name(tagInfo.id());
			std::string value;
			value = getTagValue(constTagPtr);
			_allTagValues.append(idChar);
			_allTagValues.append(" ");
			_allTagValues.append(name); 
			_allTagValues.append(": ");
			_allTagValues.append(value);
			_allTagValues.append("\n");
			
		}
	 }
}

//----------------------------------------------------------------------------------
//! This function parse the DCM tree and get CSOGroup Information
//----------------------------------------------------------------------------------
void ImportRTStructure::parseCSOGroup() 
{
	// find the tagPtr for "StructureSetROISequence"-- create CSOGroup
	DCMTree::Const_TagPtr groupTagPtr = _treePtr->getTag(DCMTree::TagId(0x3006,0x0020));
	if (groupTagPtr != NULL && groupTagPtr->isSequence()) {
		for (unsigned int groupIndex = 0; groupIndex < groupTagPtr->numberOfSequenceItems(); groupIndex ++) {
			DCMTree::TreePtr embeddedTreePtr = groupTagPtr->sequenceItems()[groupIndex];
			CSOGroup  *group = group = _outList->addGroup(); 
			
			// find ROINumber -- group ID
            DCMTree::Const_TagPtr ROINumTagPtr = embeddedTreePtr->getTag(DCMTree::TagId(0x3006,0x0022));
			group->setId(ROINumTagPtr->toInt());

			// find ROINname -- group Label
			DCMTree::Const_TagPtr ROILabelTagPtr = embeddedTreePtr->getTag(DCMTree::TagId(0x3006,0x0026));
			group->setLabel(ROILabelTagPtr->toString());

			// find ROIGenerationAlgorithm
            DCMTree::Const_TagPtr ROIUIDTagPtr = embeddedTreePtr->getTag(DCMTree::TagId(0x3006,0x0024));
            DCMTree::Const_TagPtr ROIAlgTagPtr = embeddedTreePtr->getTag(DCMTree::TagId(0x3006,0x0036));
            
			group->setDescription(ROIUIDTagPtr->toString() + ":"+ ROIAlgTagPtr->toString());
		}
	}
}

//----------------------------------------------------------------------------------
//! This function removes the conntourData information from the copied Dicom tree 
//----------------------------------------------------------------------------------
void ImportRTStructure::removeContourData() 
{
	DCMTree::Const_TagPtr tagPtr = _treePtr->getTag(DCMTree::TagId(0x3006,0x0039));
	if (tagPtr != NULL && tagPtr->isSequence()) {
       for (unsigned int i = 0; i < tagPtr->numberOfSequenceItems(); i++) {
		   DCMTree::TreePtr embeddedTreePtr = tagPtr->sequenceItems()[i];
		   DCMTree::Const_TagPtr contourSeqTagPtr = embeddedTreePtr->getTag(DCMTree::TagId(0x3006,0x0040));

			if (contourSeqTagPtr != NULL && !contourSeqTagPtr->isEmpty() && contourSeqTagPtr->isSequence()) {
				for (unsigned int embeddedIndex = 0; embeddedIndex < contourSeqTagPtr->numberOfSequenceItems(); embeddedIndex ++) {
					DCMTree::TreePtr contourSeqTreePtr = contourSeqTagPtr->sequenceItems()[embeddedIndex];
					// find the tagPtr for "contourData"
					DCMTree::TagPtr contourDataTagPtr = contourSeqTreePtr->getTag_rw(DCMTree::TagId(0x3006,0x0050));
					contourDataTagPtr->dropValues();
				}
			}
		}
	}

}

//----------------------------------------------------------------------------------
//! This function parse the DCM tree and construct the contour data into  CSOGroups 
//----------------------------------------------------------------------------------
void ImportRTStructure::parseCSO() 
{
	// find the tagPtr for "ROIContourSequence"
	DCMTree::Const_TagPtr tagPtr = _treePtr->getTag(DCMTree::TagId(0x3006,0x0039));
	if (tagPtr != NULL && tagPtr->isSequence()) {
		bool ignore = false;
		int threshold = atoi(_thresholdFld->getStringValue().c_str());
        int dividend = atoi(_dividendFld->getStringValue().c_str());
		int divisor = atoi(_divisorFld->getStringValue().c_str());

		if (threshold == 0 || divisor == 0 || dividend == 0)
			ignore = true;
		for (unsigned int i = 0; i < tagPtr->numberOfSequenceItems(); i++) {
			DCMTree::TreePtr embeddedTreePtr = tagPtr->sequenceItems()[i];

            // find display color
            DCMTree::Const_TagPtr colorTagPtr = embeddedTreePtr->getTag(DCMTree::TagId(0x3006,0x002a));
			vec3 color(colorTagPtr->toFloat(0)/255.0, colorTagPtr->toFloat(1)/255.0, colorTagPtr->toFloat(2)/255.0);
            // find the tagPtr for "ReferencedROINumber"
			DCMTree::Const_TagPtr refROITagPtr = embeddedTreePtr->getTag(DCMTree::TagId(0x3006,0x0084));

			std::string refImgFrame;
            
			// find the tagPtr for "ContourSequence"
			DCMTree::Const_TagPtr contourSeqTagPtr = embeddedTreePtr->getTag(DCMTree::TagId(0x3006,0x0040));

			if (contourSeqTagPtr != NULL && !contourSeqTagPtr->isEmpty() && contourSeqTagPtr->isSequence()) {
				for (unsigned int embeddedIndex = 0; embeddedIndex < contourSeqTagPtr->numberOfSequenceItems(); embeddedIndex ++) {
					DCMTree::TreePtr contourSeqTreePtr = contourSeqTagPtr->sequenceItems()[embeddedIndex];

					// find the tag " contour Image Sequence--find the reference image frame
					DCMTree::Const_TagPtr contourImgSeqTagPtr = contourSeqTreePtr->getTag(DCMTree::TagId(0x3006,0x0016));
					if (contourImgSeqTagPtr != NULL && contourImgSeqTagPtr->isSequence()) {
                        for (unsigned int imgSeqIndex = 0; imgSeqIndex < contourImgSeqTagPtr->numberOfSequenceItems(); imgSeqIndex ++) {
							DCMTree::TreePtr contourImgSeqTreePtr = contourImgSeqTagPtr->sequenceItems()[imgSeqIndex];
							DCMTree::Const_TagPtr refImgTagPtr = contourImgSeqTreePtr->getTag(DCMTree::TagId(0x0008, 0x1150));
							DCMTree::Const_TagPtr refImgFrameTagPtr = contourImgSeqTreePtr->getTag(DCMTree::TagId(0x0008, 0x1155));
							refImgFrame = refImgTagPtr->toString() + ":" + refImgFrameTagPtr->toString();
						}
					}
                    // find the tag " NumberOfContourPoints--number of seed points in the contour
					DCMTree::Const_TagPtr numOfSeedPtsTagPtr = contourSeqTreePtr->getTag(DCMTree::TagId(0x3006,0x0046));
					int numOfSeedPts = numOfSeedPtsTagPtr->toInt();

					// find the tagPtr for "contourData"
					DCMTree::Const_TagPtr contourDataTagPtr = contourSeqTreePtr->getTag(DCMTree::TagId(0x3006,0x0050));

					// find the tagPtr for "ContourGeometricType"
					DCMTree::Const_TagPtr geoTagPtr = contourSeqTreePtr->getTag(DCMTree::TagId(0x3006,0x0042));
		            bool csoShouldbeClosed = false;

					if (std::string("CLOSED_PLANAR") == geoTagPtr->toString()) {
						csoShouldbeClosed = true;
					}
                   
					// create new CSO and fill it in with coordinates
					int actualSeedPts = numOfSeedPts;
					if (ignore == false && threshold < numOfSeedPts) {
					  //int reminder = _cpp_min((numOfSeedPts % divisor), dividend); 
						int reminder = numOfSeedPts % divisor > dividend ?  dividend : numOfSeedPts % divisor;
						actualSeedPts = numOfSeedPts - numOfSeedPts/divisor * dividend - reminder;
					}
					CSO* cso = _outList->addCSO(actualSeedPts, csoShouldbeClosed, true);
					cso->setColor(color);
					cso->setDescription(refImgFrame);
					cso->setMarkerSize(0.1);
					cso->setMarkerColor(color);
					cso->setLineStyle(LINE_STYLE_SOLID);
					cso->setCreatorId(1);
                    cso->setType("CSOFreehandProcessor");
					cso->setSubType("SUB_TYPE_CLOSED_SPLINE");

					int contourIndex = 0;
					int index = 0;
					double p1, p2, p3;
					for (unsigned int valIndex = 0; valIndex < contourDataTagPtr->numberOfValues() && contourIndex < actualSeedPts; valIndex++) {
						if (ignore == false && threshold < numOfSeedPts && index % divisor < dividend ) {
							index ++;
							valIndex +=2;
							continue;
						}
						// create positions in CSO
						p1 = contourDataTagPtr->toDouble(valIndex);
						p2 = contourDataTagPtr->toDouble(++ valIndex);
						p3 = contourDataTagPtr->toDouble(++ valIndex);
						cso->getSeedPointAt(contourIndex ++)->worldPosition.assign(p1, p2, p3);  
						index ++;
					}
					CSOGeneratePathPoints::fillAllPathPointsSplineInterpolation(cso, 1); 
					CSOGroup  *group = _outList->getGroupById(refROITagPtr->toInt());
					group->setColor(color);
					_outList->combineCSOandGroup(cso, group);
				}
			}
		}
	}
	
}

//----------------------------------------------------------------------------------
//! This function parse the DCM tree and construct the contour data into  CSOGroups 
//----------------------------------------------------------------------------------
ImportRTStructure::~ImportRTStructure() 
{
    ML_DELETE(_outList);
	_outList = NULL;
	ML_DELETE(_objToTransfer);
	_objToTransfer = NULL;
}

ML_END_NAMESPACE

