/*
* 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 CSODCMHdrToRTStructure.
/*!
// \file    mlCSODCMHdrToRTStructure.cpp
// \author  linal
// \date    2010-08-18
//
// CSO and DCM header convert to a RT Structure dcm file
*/
//----------------------------------------------------------------------------------

// Local includes
#include "mlCSODCMHdrToRTStructure.h"

ML_START_NAMESPACE


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

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

	// 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();

  _inputCSOs = NULL;
  
  ML_CHECK(&fields);

  (_inListFld = fields.addBase("inCSOList"))->setBaseValue(NULL);
  _dcmObjFld = fields.addBase("inputBase");
  _exportFld = fields.addNotify("export");
  _exportDestinationFld = fields.addString("exportDestination");
  _inputList = NULL;

  // Connect input fields with output field to update connected
  // modules if input images change.
  for (int inIdx=0; inIdx < 0; ++inIdx){
    for (int outIdx=0; outIdx < 0; ++outIdx){
      getInField(inIdx)->attachField(getOutField(outIdx));
    }
  }
  // Reactivate calls of handleNotification on field changes.
  handleNotificationOn();
}

//----------------------------------------------------------------------------------
//! Handle field changes of the field field.
//----------------------------------------------------------------------------------
void CSODCMHdrToRTStructure::handleNotification (Field *field)
{
  ML_TRACE_IN("CSODCMHdrToRTStructure::handleNotification ()");

  // Handle changes of module parameters and connectors here.
	// Handle changes of module parameters and connectors here.
  // check input values
  if (field == _inListFld) {
	   Base *csoListB = _inListFld->getBaseValue();
       _inputCSOs = mlbase_cast<CSOList*>(csoListB);
	   if (_inputCSOs == NULL) return;
  } else if (field == _dcmObjFld) {
	    Base *dcmTreeObj = _dcmObjFld->getBaseValue();
        RTStructure *_RTStructure = mlbase_cast<RTStructure*>(dcmTreeObj);
		if(_RTStructure == NULL) 
			return;
		DCMTree::TreePtr _tree(new DCMTree::Tree());
        _tree->fromStream(_RTStructure->stream, true);
		if (_tree->tagsSize() == 0) 
			return;
		else
			_inputTree = _tree;
		combineCSOs();
  } else if (field == _exportFld) {
	  if(_inputCSOs == NULL || _inputTree->tagsSize() == 0 || _exportDestinationFld->getStringValue().empty())
		  return;
	  if (!checkCSOList())
		   return;
	  combineCSOs();

	  DCMTree::TagPtr SOPInstUIDTag = _inputTree->getTag_rw(DCMTree::TagId(0x0002,0x0003));
		if (!DCMTree_Utils::UIDGenerator::isInitialized()) {
			DCMTree_Utils::UIDGenerator::initialize("1.2.276.0.28","3","42");
		}
	  std::string generatedUID = DCMTree_Utils::UIDGenerator::uid();
	  SOPInstUIDTag->setValue(generatedUID);
	  DCMTree::TagPtr SOPInstUIDTag2 = _inputTree->getTag_rw(DCMTree::TagId(0x0008,0x0018));
	  SOPInstUIDTag2->setValue(generatedUID);
	  std::ifstream ifile(_exportDestinationFld->getStringValue().c_str());
	  if (ifile) {
		  ifile.close();
		  remove(_exportDestinationFld->getStringValue().c_str());
	  }
      writeToDCMFile(_inputTree, _exportDestinationFld->getStringValue());
  }
}

//----------------------------------------------------------------------------------
//! Configures (in)validation handling of inputs which are not connected or up to date.
//----------------------------------------------------------------------------------
bool CSODCMHdrToRTStructure::writeToDCMFile(DCMTree::TreePtr tree, std::string dest) {

	try {
	 
	DCMTree::MessagePtr messagePtr = DCMTree::MessagePtr( 
      new DCMTree::Message(DCMTree_Unicode::UTF8CharacterSet(), DCMTree::IMPLICIT_LITTLE_ENDIAN)); 
    messagePtr->setTags(tree); 

    // Create writer object 
	DCMTree::WriterPtr writer = DCMTree::Writer::create(DCMTree::Dict::singleton()); 
 
    // Write message 
	DCMTree::IOParameterPtr param(DCMTree::IOParameter::create(dest)); 
   writer->write(param, messagePtr); 
	} catch(DCMTree::Exception& ex){
		ML_PRINT_ERROR("CSODCMHdrToRTStructure::_HandleNotification",ML_BAD_PARAMETER, ex.toString().c_str()); 
		return false;
	}
	return true;
}

//----------------------------------------------------------------------------------
//! Configures (in)validation handling of inputs which are not connected or up to date.
//----------------------------------------------------------------------------------
void CSODCMHdrToRTStructure::combineCSOs() {
	insertCSOGroup();
	insertCSOList();
	insertObservation();
	
}
//----------------------------------------------------------------------------------
//! Configures (in)validation handling of inputs which are not connected or up to date.
//----------------------------------------------------------------------------------
bool CSODCMHdrToRTStructure::checkCSOList(){
	unsigned int totalCSOs = 0;
	
	for(unsigned int i = 0; i < _inputCSOs->numGroups(); i++){
	   CSOGroup * group = _inputCSOs->getGroupAt(i);
	   if (group->getLabel().length() == 0) {
		   //warning
		   ML_PRINT_WARNING("mlCSOToRTStructure::_checkCSOList",ML_BAD_PARAMETER, "group label is missing");
		   return false;
	   }
	   totalCSOs += group->numCSO();
	}
	if (totalCSOs != _inputCSOs->numCSO()) {
		//give warning!
		ML_PRINT_ERROR("mlCSOToRTStructure::_checkCSOList",ML_BAD_PARAMETER, "CSO must belong to a group.");
		return false;
	}
	// check if each CSO has a ReferenceID
	for(unsigned int i = 0; i < _inputCSOs->numGroups(); i++){
	   CSOGroup * group = _inputCSOs->getGroupAt(i);
	   for (unsigned int csoInd = 0; csoInd < group->numCSO(); csoInd ++) {
		   std::string desc = group->getCSOAt(csoInd)->getDescription();
		   if (desc.empty() || desc.find_first_of(":") ==  desc.length()-1){
				ML_PRINT_ERROR("mlCSOToRTStructure::_checkCSOList",ML_BAD_PARAMETER, "CSO must have a desc including ReferencedSOPClassUID and ReferencedSOPInstanceUID, otherwise the program cannot proceed.");
		   }
	   }
	}
   return true;
}
//----------------------------------------------------------------------------------
//! Configures (in)validation handling of inputs which are not connected or up to date.
//----------------------------------------------------------------------------------
void CSODCMHdrToRTStructure::insertCSOGroup() {
   if(_inputCSOs == NULL) 
	   return;
    DCMTree::Const_TagPtr sopTagPtr = _inputTree->getTag(DCMTree::TagId(0x0008,0x0018));
   //get a handle of groupID in DCM file
   DCMTree::TagPtr groupTagPtr = _inputTree->getTag_rw(DCMTree::TagId(0x3006,0x0020));
   if (!groupTagPtr->isSequence()) 
	   return;
   unsigned int seqNum = groupTagPtr->numberOfSequenceItems();
   unsigned int groupNum = _inputCSOs->numGroups();
   if (seqNum == groupNum) {
   // get the group from CSO list
	   copyCSOGroups(groupTagPtr, groupNum);
   }else if(seqNum > groupNum) {
      copyCSOGroups(groupTagPtr, groupNum);
	  groupTagPtr->sequenceItems().erase(groupTagPtr->sequenceItems().begin() + groupNum,
		  groupTagPtr->sequenceItems().begin() + seqNum);
   } else {
	  DCMTree::TreePtr embeddedTreePtr = groupTagPtr->sequenceItems()[seqNum -1];
	  for (unsigned int rest = seqNum; rest < groupNum; rest++) {
			groupTagPtr->addSequenceItem(embeddedTreePtr->copy());
	  }
      copyCSOGroups(groupTagPtr, groupNum);	
   }
}

//----------------------------------------------------------------------------------
//! Configures (in)validation handling of inputs which are not connected or up to date.
//----------------------------------------------------------------------------------
void  CSODCMHdrToRTStructure::copyCSOGroups(DCMTree::TagPtr groupTagPtr, unsigned int num) {
        if(_inputCSOs == NULL) 
			return;
        for (unsigned int i = 0; i < num; i ++ ){   
		   CSOGroup * aGroup = _inputCSOs->getGroupAt(i);
		   DCMTree::TreePtr embeddedTreePtr = groupTagPtr->sequenceItems()[i];
		   int groupId = (int) aGroup->getId();
		   std::string ROIName = aGroup->getLabel();
		   std::string desc = aGroup->getDescription();
		   std::string uid = desc.substr(0, desc.find_first_of(":"));
           std::string alg("");  
		   if (desc.find_first_of(":") < desc.length()-1)
			  alg = desc.substr(desc.find_first_of(":") + 1, desc.length()-1);
		   DCMTree::TagPtr ROINumTagPtr = embeddedTreePtr->getTag_rw(DCMTree::TagId(0x3006,0x0022));
		   ROINumTagPtr->dropValues();
		   ROINumTagPtr->addValue(groupId);
		   DCMTree::TagPtr ROILabelTagPtr = embeddedTreePtr->getTag_rw(DCMTree::TagId(0x3006,0x0026));
		   ROILabelTagPtr->setValue(ROIName);
		   DCMTree::TagPtr ROIUIDTagPtr  = embeddedTreePtr->getTag_rw(DCMTree::TagId(0x3006,0x0024));
			 ROIUIDTagPtr->setValue(uid);
		   DCMTree::TagPtr ROIAlgTagPtr = embeddedTreePtr->getTag_rw(DCMTree::TagId(0x3006,0x0036));
		   ROIAlgTagPtr->dropValues();
		   if (alg.length() != 0)
			 ROIAlgTagPtr->addValue(alg);
		}
}

//----------------------------------------------------------------------------------
//! Configures (in)validation handling of inputs which are not connected or up to date.
//----------------------------------------------------------------------------------
void  CSODCMHdrToRTStructure::insertCSOList() {
	if(_inputCSOs == NULL) 
			return;
    tagPtr = _inputTree->getTag_rw(DCMTree::TagId(0x3006,0x0039));
    if (tagPtr == NULL || !tagPtr->isSequence()) 
		return;
    unsigned int seqNum = tagPtr->numberOfSequenceItems();
	
    unsigned int groupNum = _inputCSOs->numGroups();
	if (seqNum > groupNum) {
		tagPtr->sequenceItems().erase(tagPtr->sequenceItems().begin()+groupNum,
           tagPtr->sequenceItems().begin()+seqNum);
	}else if (seqNum < groupNum) {
		    DCMTree::TreePtr seq(tagPtr->sequenceItems()[0]);
			tagPtr->addSequenceItem(seq);
	}
	for (unsigned int i = 0; i < groupNum; i++) {
		DCMTree::TreePtr embeddedTreePtr = tagPtr->sequenceItems()[i];
		CSOGroup* group = _inputCSOs->getGroupAt(i);
		ml::vec3 color = group->getColor();

    DCMTree::TagPtr colorTagPtr = embeddedTreePtr->getTag_rw(DCMTree::TagId(0x3006,0x002a));
		colorTagPtr->dropValues();
		colorTagPtr->addValue(color[0]*255.0);
	  colorTagPtr->addValue(color[1]*255.0);
	  colorTagPtr->addValue(color[2]*255.0);
        
		// find the tagPtr for "ReferencedROINumber" -- groupNum
	  DCMTree::TagPtr refROITagPtr = embeddedTreePtr->getTag_rw(DCMTree::TagId(0x3006,0x0084));
		refROITagPtr->dropValues();
		refROITagPtr->addValue((int) group->getId());

    // find the tagPtr for "ContourSequence"
	  DCMTree::TagPtr contourSeqTagPtr = embeddedTreePtr->getTag_rw(DCMTree::TagId(0x3006,0x0040));
    if (contourSeqTagPtr == NULL || contourSeqTagPtr->isEmpty() || !contourSeqTagPtr->isSequence())
		   return;
	  int numContours = (int) contourSeqTagPtr->numberOfSequenceItems();
    int numCSOs = group->numCSO();
	   //add or delete numContours so that it == numCSOs
	  if (numContours < numCSOs) {
			for (int rest = numContours; rest < numCSOs; rest ++){
			   DCMTree::TreePtr seq(contourSeqTagPtr->sequenceItems()[0]);
			   contourSeqTagPtr->addSequenceItem(seq);
		   }
	  } else if (numContours > numCSOs) {
           //for (int rest = numCSOs; rest < numContours; rest ++)
		   contourSeqTagPtr->sequenceItems().erase(contourSeqTagPtr->sequenceItems().begin()+numCSOs,
       contourSeqTagPtr->sequenceItems().begin()+numContours);
		   //unsigned int num = contourSeqTagPtr->numberOfSequenceItems();
		   //unsigned int numSeq = num;
	   }
     for (unsigned int imgSeqIndex = 0; imgSeqIndex < group->numCSO(); imgSeqIndex ++) {
		   DCMTree::TreePtr contourSeqTreePtr = contourSeqTagPtr->sequenceItems()[imgSeqIndex];
       CSO* aCSO = group->getCSOAt(imgSeqIndex);
       DCMTree::TagPtr geoTagPtr = contourSeqTreePtr->getTag_rw(DCMTree::TagId(0x3006,0x0042));
		   int numPts = (int) aCSO->numSeedPoints();
		   if (numPts == 1){
				 geoTagPtr->addValue("POINT");
		   } else {
			   bool closed = aCSO->isClosed();
			   bool inPlane = false;
			   if (closed == false)
				   inPlane = aCSO->isInPlane();
			   geoTagPtr->dropValues();
			   if (closed){ 
				   geoTagPtr->addValue("CLOSED_PLANAR");
			   }else if(inPlane) {
				   geoTagPtr->addValue("OPEN_PLANAR");
			   }else {
				   geoTagPtr->addValue("OPEN_NONPLANAR");
			   }
		   }

            DCMTree::TagPtr numTagPtr = contourSeqTreePtr->getTag_rw(DCMTree::TagId(0x3006,0x0046));
			//unsigned int numContours = numTagPtr->toInt();
			numTagPtr->dropValues();
			numTagPtr->addValue(numPts);

			 DCMTree::TagPtr conTagPtr = contourSeqTreePtr->getTag_rw(DCMTree::TagId(0x3006,0x0050));
			 conTagPtr->dropValues();
			 for (unsigned int sedIndex = 0; sedIndex < aCSO->numSeedPoints(); sedIndex ++) {
						ml::CSOSeedPoint* pt = aCSO->getSeedPointAt(sedIndex);
						vec3 pos = pt->worldPosition;
						for (int posInd = 0; posInd < 3; posInd++) {
							conTagPtr->addValue(pos[posInd]);
						}
			 }
			 DCMTree::TagPtr contourImgSeqTagPtr = contourSeqTreePtr->getTag_rw(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::TagPtr refImgTagPtr = contourImgSeqTreePtr->getTag_rw(DCMTree::TagId(0x0008, 0x1150));
						DCMTree::TagPtr refImgFrameTagPtr = contourImgSeqTreePtr->getTag_rw(DCMTree::TagId(0x0008, 0x1155));
						std::string desc(aCSO->getDescription());
						if (!desc.empty()) {
							std::string image = desc.substr(0, desc.find_first_of(":"));
							std::string frame = desc.substr(desc.find_first_of(":")+1, desc.length() -1);
							refImgTagPtr->dropValues();
							refImgTagPtr->addValue(image);
							refImgFrameTagPtr->dropValues();
							refImgFrameTagPtr->addValue(frame);
						}
				}
			 }
	}
	}
}

//----------------------------------------------------------------------------------
//! Configures (in)validation handling of inputs which are not connected or up to date.
//----------------------------------------------------------------------------------
void  CSODCMHdrToRTStructure::insertObservation() {
	if(_inputCSOs == NULL)
			return;
	DCMTree::TagPtr tagPtr = _inputTree->getTag_rw(DCMTree::TagId(0x3006,0x0080));
    if (tagPtr == NULL || !tagPtr->isSequence())
		return;
    unsigned int seqNum = tagPtr->numberOfSequenceItems();
	unsigned int grpNum = _inputCSOs->numGroups();
	std::vector<int> CSOGrpIds;
	for (unsigned int i = 0; i < grpNum; i ++) {
		CSOGrpIds.push_back((int)_inputCSOs->getGroupAt(i)->getId());
	}
	if (seqNum > grpNum) {
		DCMTree::TreePtrVector::iterator seqIter = tagPtr->sequenceItems().begin();
		while(seqIter != tagPtr->sequenceItems().end()) {
			DCMTree::TreePtr treePtr = *seqIter;
			DCMTree::TagPtr tagPtr1 = treePtr->getTag_rw(DCMTree::TagId("0x3006,0x0084"));
			std::vector <int>::iterator it = (find(CSOGrpIds.begin(), CSOGrpIds.end(), tagPtr1->toInt()));
			if (it == CSOGrpIds.end())
				seqIter = tagPtr->sequenceItems().erase(seqIter);
			else
				++seqIter;
		}
	}
}

//----------------------------------------------------------------------------------
//! Configures (in)validation handling of inputs which are not connected or up to date.
//----------------------------------------------------------------------------------
BaseOp::INPUT_HANDLE CSODCMHdrToRTStructure::handleInput(int /*inIndex*/, INPUT_STATE /*state*/) const
{
  ML_TRACE_IN("test::handleInput ()");
  
  return INVALIDATE;
}

ML_END_NAMESPACE

