static const char *CopyrightIdentifier(void) { return "@(#)dccidump.cc Copyright (c) 1993-2022, David A. Clunie DBA PixelMed Publishing. All rights reserved."; }
#include "attrmxls.h"
#include "mesgtext.h"
#include "dcopt.h"
#include "elmconst.h"
#include "attrval.h"

static inline void
indent(TextOutputStream &log,unsigned depth,bool showtabs,bool showgt) {
	unsigned d=depth;
	if (showtabs) while (d--) log << "\t";
	d=depth;
	if (showgt) while (d--) log << ">";
}

static void
writeTagNameToLog(Tag tag,ElementDictionary *dict,TextOutputStream &log) {
	const char *desc = NULL;
	if (dict) {
		desc = dict->getDescription(tag);
	}
	if (desc && strlen(desc) > 0 && strcmp(desc,"?") != 0) {
		log << desc;
	}
}

static void
writeTagNameToLog(Attribute *a,ElementDictionary *dict,TextOutputStream &log) {
	writeTagNameToLog(a->getTag(),dict,log);
}

static void
logStringValueOfAttribute(Attribute *a,TextOutputStream &log) {
	if (a) {
		char *v = AttributeValue(a);
		if (v) {
			log << v;
			delete[] v;
		}
	}
}

static void
processCodeSequenceItem(AttributeList *list,TextOutputStream &log) {
	log << "(";
	logStringValueOfAttribute((*list)[TagFromName(CodeValue)],log);
	log << ",";
	logStringValueOfAttribute((*list)[TagFromName(CodingSchemeDesignator)],log);
	log << "," << "\"" ;
	logStringValueOfAttribute((*list)[TagFromName(CodeMeaning)],log);
	log << "\")";
}

static void
processCodeSequence(Attribute *aCodeSequence,const char *label,TextOutputStream &log) {
	log << label;

	if (aCodeSequence) {
		int nItems = 0;
		AttributeList ** vItems = 0;
		nItems=aCodeSequence->getLists(&vItems);
		Assert(nItems==0 || vItems);

		int i;
		AttributeList **iptr;

		for (i=0,iptr=vItems; i<nItems; ++i,++iptr) {
			AttributeList *ptr = *iptr;
			Assert(ptr);
			if (i > 0) log << ", ";
			processCodeSequenceItem(ptr,log);
		}
	}
}

static void
processMeasuredValueSequence(Attribute *aMeasuredValueSequence,const char *label,TextOutputStream &log) {
	log << label;

	if (aMeasuredValueSequence) {
		int nItems = 0;
		AttributeList ** vItems = 0;
		nItems=aMeasuredValueSequence->getLists(&vItems);
		Assert(nItems==0 || vItems);

		int i;
		AttributeList **iptr;

		for (i=0,iptr=vItems; i<nItems; ++i,++iptr) {
			AttributeList *ptr = *iptr;
			Assert(ptr);
			if (i > 0) log << ", ";

			logStringValueOfAttribute((*ptr)[TagFromName(NumericValue)],log);
			
			Attribute *aFloatingPointValue = (*ptr)[TagFromName(FloatingPointValue)];
			if (aFloatingPointValue) {
				log << " {";
				double v;
				if (aFloatingPointValue->getValue(0,v)) log << setprecision(16) << v;	// this is setprecision(std::numeric_limits<double>::digits10 + 1)
				log << "}";
			}

			Attribute *aRationalNumeratorValue = (*ptr)[TagFromName(RationalNumeratorValue)];
			Attribute *aRationalDenominatorValue = (*ptr)[TagFromName(RationalDenominatorValue)];
			if (aRationalNumeratorValue || aRationalDenominatorValue) {
				log << " {";
				double v;
				if (aRationalNumeratorValue && aRationalNumeratorValue->getValue(0,v)) log << v;
				log << "/";
				if (aRationalDenominatorValue && aRationalDenominatorValue->getValue(0,v)) log << v;
				log << "}";
			}

			Attribute *aMeasurementUnitsCodeSequence=(*ptr)[TagFromName(MeasurementUnitsCodeSequence)];
			if (aMeasurementUnitsCodeSequence) {
				log << " ";
				processCodeSequence(aMeasurementUnitsCodeSequence,"",log);
			}
		}
	}
}

static void
processReferencedSOPSequence(Attribute *aReferencedSOPSequence,const char *label,TextOutputStream &log) {
	log << label;

	if (aReferencedSOPSequence) {
		int nItems = 0;
		AttributeList ** vItems = 0;
		nItems=aReferencedSOPSequence->getLists(&vItems);
		Assert(nItems==0 || vItems);

		int i;
		AttributeList **iptr;

		for (i=0,iptr=vItems; i<nItems; ++i,++iptr) {
			AttributeList *ptr = *iptr;
			Assert(ptr);
			if (i > 0) log << ", ";

			log << "(";
			logStringValueOfAttribute((*ptr)[TagFromName(ReferencedSOPClassUID)],log);
			log << ",";
			logStringValueOfAttribute((*ptr)[TagFromName(ReferencedSOPInstanceUID)],log);
			log << ")";
			
			// the following items are specific to the usage in IMAGE content items (and hence should do nothing in others) ...
			
			Attribute *aReferencedFrameNumber=(*ptr)[TagFromName(ReferencedFrameNumber)];
			if (aReferencedFrameNumber) {
				log << " [Frame ";
				logStringValueOfAttribute(aReferencedFrameNumber,log);
				log << "]";
			}
			
			Attribute *aReferencedSegmentNumber=(*ptr)[TagFromName(ReferencedSegmentNumber)];
			if (aReferencedSegmentNumber) {
				log << " [Segment ";
				//logStringValueOfAttribute(aReferencedSegmentNumber,log);
				Uint16 value;
				if (aReferencedSegmentNumber->getValue((Uint16)0,value)) {
					log << value;
				}
				log << "]";
			}

			Attribute *aPresentationStateReferencedSOPSequence=(*ptr)[TagFromName(ReferencedSOPSequence)];
			if (aPresentationStateReferencedSOPSequence) {
				int nItems = 0;
				AttributeList ** vItems = 0;
				nItems=aPresentationStateReferencedSOPSequence->getLists(&vItems);
				Assert(nItems==0 || vItems);
				Assert(nItems==0 || vItems);

				int i;
				AttributeList **iptr;

				for (i=0,iptr=vItems; i<nItems; ++i,++iptr) {
					AttributeList *ptr = *iptr;
					Assert(ptr);
					if (i > 0) log << ",";

					log << " (PS ";
					logStringValueOfAttribute((*ptr)[TagFromName(ReferencedSOPClassUID)],log);
					log << ",";
					logStringValueOfAttribute((*ptr)[TagFromName(ReferencedSOPInstanceUID)],log);
					log << ")";
				}
			}

			Attribute *aReferencedRealWorldValueMappingInstanceSequence=(*ptr)[TagFromName(ReferencedRealWorldValueMappingInstanceSequence)];
			if (aReferencedRealWorldValueMappingInstanceSequence) {
				int nItems = 0;
				AttributeList ** vItems = 0;
				nItems=aReferencedRealWorldValueMappingInstanceSequence->getLists(&vItems);
				Assert(nItems==0 || vItems);
				Assert(nItems==0 || vItems);

				int i;
				AttributeList **iptr;

				for (i=0,iptr=vItems; i<nItems; ++i,++iptr) {
					AttributeList *ptr = *iptr;
					Assert(ptr);
					if (i > 0) log << ",";

					log << " (RWV ";
					logStringValueOfAttribute((*ptr)[TagFromName(ReferencedSOPClassUID)],log);
					log << ",";
					logStringValueOfAttribute((*ptr)[TagFromName(ReferencedSOPInstanceUID)],log);
					log << ")";
				}
			}
			
		}
	}
}

static void
processScoordContentItem(AttributeList *list,TextOutputStream &log) {
	logStringValueOfAttribute((*list)[TagFromName(GraphicType)],log);
	log << " {";
	//log.unsetf(ios_base::fixed,ios_base::scientific);	// :) should really explicitly establish the default floating point notation, in which the precision field specifies the maximum number of meaningful digits to display in total counting both those before and those after the decimal point
	log << setprecision (15);							// this choice of precision is based on the fact that a DS VR is 16 characters, including the decimal point; this is excessive in that the FL VR (32 bit IEEE float) significand has a precision of 24 binary bits (about 7 decimal digits)
	Attribute *aGraphicData=(*list)[TagFromName(GraphicData)];
	int nGraphicData = aGraphicData ? aGraphicData->getVM() : 0;
	int i;
	for (i=0; i<nGraphicData; ++ i) {
		if (i > 0) log << ",";
		float v;
		if (aGraphicData->getValue(i,v)) log << v;
	}
	log << "}";
}

static void
processScoord3DContentItem(AttributeList *list,TextOutputStream &log) {
	logStringValueOfAttribute((*list)[TagFromName(GraphicType)],log);
	log << " {";
	//log.unsetf(ios_base::fixed,ios_base::scientific);	// :) should really explicitly establish the default floating point notation, in which the precision field specifies the maximum number of meaningful digits to display in total counting both those before and those after the decimal point
	log << setprecision (15);							// this choice of precision is based on the fact that a DS VR is 16 characters, including the decimal point; this is excessive in that the FL VR (32 bit IEEE float) significand has a precision of 24 binary bits (about 7 decimal digits)
	Attribute *aGraphicData=(*list)[TagFromName(GraphicData)];
	int nGraphicData = aGraphicData ? aGraphicData->getVM() : 0;
	int i;
	for (i=0; i<nGraphicData; ++ i) {
		if (i > 0) log << ",";
		float v;
		if (aGraphicData->getValue(i,v)) log << v;
	}
	log << "}";
	log << " (FoR ";
	logStringValueOfAttribute((*list)[TagFromName(ReferencedFrameOfReferenceUID)],log);
	log << ")";
}

static void
processValueOfContentItem(AttributeList *list,TextOutputStream &log) {
	Attribute *aValueType=(*list)[TagFromName(ValueType)];
	char *vValueType=AttributeValue(aValueType);
	if (vValueType) {
		if (strcmp(vValueType,"NUM") == 0 || strcmp(vValueType,"NUMERIC") == 0) {	// note inconsitency between SR and Content Item Macro
			log << " = ";
			processMeasuredValueSequence((*list)[TagFromName(MeasuredValueSequence)],"",log);
			Attribute *aNumericValueQualifierCodeSequence=(*list)[TagFromName(NumericValueQualifierCodeSequence)];
			if (aNumericValueQualifierCodeSequence) {
				log << " ";
				processCodeSequence(aNumericValueQualifierCodeSequence,"",log);
			}
		}
		else if (strcmp(vValueType,"TEXT") == 0) {
			log << " = \"";
			logStringValueOfAttribute((*list)[TagFromName(TextValue)],log);
			log << "\"";
		}
		else if (strcmp(vValueType,"CODE") == 0) {
			log << " = ";
			processCodeSequence((*list)[TagFromName(ConceptCodeSequence)],"",log);
		}
		else if (strcmp(vValueType,"DATE") == 0) {
			log << " = \"";
			logStringValueOfAttribute((*list)[TagFromName(Date)],log);
			log << "\"";
		}
		else if (strcmp(vValueType,"TIME") == 0) {
			log << " = \"";
			logStringValueOfAttribute((*list)[TagFromName(Time)],log);
			log << "\"";
		}
		else if (strcmp(vValueType,"DATETIME") == 0) {
			log << " = \"";
			logStringValueOfAttribute((*list)[TagFromName(DateTime)],log);
			log << "\"";
		}
		else if (strcmp(vValueType,"PNAME") == 0) {
			log << " = \"";
			logStringValueOfAttribute((*list)[TagFromName(PersonName)],log);
			log << "\"";
		}
		else if (strcmp(vValueType,"UIDREF") == 0) {
			log << " = \"";
			logStringValueOfAttribute((*list)[TagFromName(UID)],log);
			log << "\"";
		}
		else if (strcmp(vValueType,"IMAGE") == 0) {
			log << " = ";
			processReferencedSOPSequence((*list)[TagFromName(ReferencedSOPSequence)],"",log);
		}
		else if (strcmp(vValueType,"WAVEFORM") == 0) {
			processReferencedSOPSequence((*list)[TagFromName(ReferencedSOPSequence)],"",log);
		}
		else if (strcmp(vValueType,"SCOORD") == 0) {
			log << " = ";
			processScoordContentItem(list,log);
		}
		else if (strcmp(vValueType,"SCOORD3D") == 0) {
			log << " = ";
			processScoord3DContentItem(list,log);
		}
		else if (strcmp(vValueType,"TCOORD") == 0) {
		}
		else if (strcmp(vValueType,"COMPOSITE") == 0) {
			processReferencedSOPSequence((*list)[TagFromName(ReferencedSOPSequence)],"",log);
		}
		else if (strcmp(vValueType,"CONTAINER") == 0) {
			log << " [";
			logStringValueOfAttribute((*list)[TagFromName(ContinuityOfContent)],log);
			log << "]";
			Attribute *aContentTemplateSequence = (*list)[TagFromName(ContentTemplateSequence)];
			if (aContentTemplateSequence) {
				AttributeList ** vItems = 0;
				int nItems=aContentTemplateSequence->getLists(&vItems);
				if (nItems > 0 && vItems) {
					AttributeList *itemList = vItems[0];
					Assert(itemList);
					log << " (";
					logStringValueOfAttribute((*itemList)[TagFromName(MappingResource)],log);
					log << ",";
					logStringValueOfAttribute((*itemList)[TagFromName(TemplateIdentifier)],log);
					log << ")";
				}
			}
		}
		else {
			log << " **** Unrecognized Value Type ****";
		}

		delete[] vValueType;
	}
}

static void
logReferencedContentItemIdentifier(Attribute *a,TextOutputStream &log) {
	if (a) {
		Uint16 vm = a->getVM();
		if (vm > 0) {
			const char *prefix = NULL;
			for (Uint16 i=0; i<vm; ++i) {
				if (prefix) {
					log << prefix;
				}
				char *v;
				a->getValue(i,v);
				log << v;
				delete[] v;
				prefix=".";
			}
		}
	}
}

static bool
processContentItem(AttributeList *list,TextOutputStream &log,bool verbose,bool veryverbose,unsigned depth,const char *identifierstring)   {
	bool success=true;
	Attribute *                       aValueType=(*list)[TagFromName(ValueType)];
	if (aValueType) {
		Attribute *             aObservationDateTime=(*list)[TagFromName(ObservationDateTime)];
		Attribute *         aConceptNameCodeSequence=(*list)[TagFromName(ConceptNameCodeSequence)];
		
		indent(log,depth,true,false/*showgt*/);
		if (identifierstring) {
			log << identifierstring << ": ";
		}
		
		logStringValueOfAttribute(aValueType,log);
		log << ": ";
		
		if (aConceptNameCodeSequence) {
			processCodeSequence(aConceptNameCodeSequence,"",log);
			log << " ";
		}
		
		processValueOfContentItem(list,log);
		
		if (aObservationDateTime) {
			log << " (";
			if (aObservationDateTime) {
				logStringValueOfAttribute(aObservationDateTime,log);
			}
			log << ")";
		}
		
		log << endl;
	}
	
	int attributeCount = 0;
	AttributeListIterator listi(*list);
	while (!listi) {
		Attribute *a=listi();
		Assert(a);
		char *attributeidentifierstring = NULL;
		if (identifierstring) {
			ostrstream attributeidentifierstream;
			attributeidentifierstream << identifierstring << "." << (attributeCount+1) << ends;
			attributeidentifierstring = attributeidentifierstream.str();
		}
		if (strcmp(a->getVR(),"SQ") == 0) {
			AttributeList **al;
			int n;
			if ((n=a->getLists(&al)) > 0) {
				if (n>0 && (*al[0])[TagFromName(ValueType)]) {
					indent(log,depth,true,false/*showgt*/);
					if (attributeidentifierstring) {
						log << attributeidentifierstring << ": ";
					}
					writeTagNameToLog(a,list->getDictionary(),log);
					log << endl;
				}
				int i;
				for (i=0; i<n; ++i) {
					char *childidentifierstring = NULL;
					if (attributeidentifierstring) {
						ostrstream childidentifierstream;
						childidentifierstream << attributeidentifierstring << "[" << (i+1) << "]" << ends;	// distinguish items in brackets from attribute path separated by periods
						childidentifierstring = childidentifierstream.str();
					}
					success&=processContentItem(al[i],log,verbose,veryverbose,depth+1,childidentifierstring);
					if (childidentifierstring) {
						delete [] childidentifierstring;
					}
				}
				delete [] al;
			}
		}
		if (attributeidentifierstring) {
			delete [] attributeidentifierstring;
		}
		++listi;
		++attributeCount;
	}
	return success;
}

static bool
parseContentItems(ManagedAttributeList &list,TextOutputStream &log,bool verbose,bool veryverbose,bool showidentifier)
{
	bool result = processContentItem(&list,log,verbose,veryverbose,0,showidentifier ? "1" : NULL);
	return result;
}

int
main(int argc, char *argv[])
{
	GetNamedOptions 	options(argc,argv);
	DicomInputOptions 	dicom_input_options(options);

	bool veryverbose=options.get("veryverbose") || options.get("vv");
	bool verbose=options.get("verbose") || options.get("v") || veryverbose;
	bool showfilename=options.get("filename");
	bool showidentifier=options.get("identifier");

	dicom_input_options.done();
	options.done();

	DicomInputOpenerFromOptions input_opener(
		options,dicom_input_options.filename,cin);

	cerr << dicom_input_options.errors();
	cerr << options.errors();
	cerr << input_opener.errors();

	if (!dicom_input_options.good()
	 || !options.good()
	 || !input_opener.good()
	 || !options) {
		cerr 	<< MMsgDC(Usage) << ": " << options.command()
			<< dicom_input_options.usage()
			<< " [-v|-verbose|-vv|-veryverbose]"
			<< " [-filename]"
			<< " [-identifier]"
			<< " [" << MMsgDC(InputFile) << "]"
			<< " <" << MMsgDC(InputFile)
			<< endl;
		exit(1);
	}

	DicomInputStream din(*(istream *)input_opener,
		dicom_input_options.transfersyntaxuid,
		dicom_input_options.usemetaheader);

	ManagedAttributeList list;

	bool success=true;
	TextOutputStream log(cerr);
	
	if (showfilename) {
		const char *filenameused = input_opener.getFilename();
		cerr << "Filename: \"" << (filenameused && strlen(filenameused) > 0 ? filenameused : "-") << "\"" << endl;
	}

	if (veryverbose) log << "******** While reading ... ********" << endl; 
	list.read(din,false/*newformat*/,&log,veryverbose,0xffffffff,true,dicom_input_options.uselengthtoend,dicom_input_options.ignoreoutofordertags,dicom_input_options.useUSVRForLUTDataIfNotExplicit);

	const char *errors=list.errors();
	if (errors) log << errors << flush;
	if (!list.good()) {
		log << EMsgDC(DatasetReadFailed) << endl;
		success=false;
	}

	//if (veryverbose) log << "******** As read ... ********" << endl; 
	//log << list;
	//list.write(log,veryverbose);

	{
		bool parseSuccess = parseContentItems(list,log,verbose,veryverbose,showidentifier);		// parse regardless of read success or failure
		success=success&&parseSuccess;
	}

	return success ? 0 : 1;
}




