/*******************************************************************************
 *  Copyright 2017-2018 Centre for Development of Advanced Computing(C-DAC), Pune
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *   http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package cdac.medinfo.sdk.dcm30_04.baselibs;

import java.io.IOException;

import cdac.medinfo.sdk.dcm30_04.commoninterface.IDicomStream;
import cdac.medinfo.sdk.dcm30_04.commoninterface.ITag;
import cdac.medinfo.sdk.dcm30_04.commoninterface.ITransferSyntax;
import cdac.medinfo.sdk.dcm30_04.exceptions.InvalidDataTypeException;
import cdac.medinfo.sdk.dcm30_04.exceptions.InvalidValueException;
import cdac.medinfo.sdk.dcm30_04.exceptions.ItemLimitException;
import cdac.medinfo.sdk.dcm30_04.exceptions.ValidationException;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConfig;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConstants;
import cdac.medinfo.sdk.dcm30_04.utils.DicomLogger;

import cdac.medinfo.sdk.dcm30_04.utils.ISingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.SingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.StandardMethods;

/**
 * OW represents  the  string of bytes where the encoding of the contents is specified by the negotiated Transfer Syntax.
 * OW is a VR which is sensitive to Little/Big Endian byte ordering.
 * The string of bytes shall be padded with a single trailing NULL byte value (00H) when necessary to achieve even length.
 */
public class OW extends DicomValue  
{
	private boolean isUndefinedLength;

	

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	int iGroupNumber = 0;
	int iElementNumber = 0;
	ITransferSyntax objITransferSyntax;
	ISingleCollection<byte[]> objSingleCollection = new SingleCollection<byte[]>();

	OW()
	{
		iVRType = DicomConstants.DCM_DT_OW;
	}

	/**
	 * Retrieves the value length of the item. Note that this length excludes the Item major and minor tags 
	 * @return int - value length of the item
	 */
	@SuppressWarnings("unchecked")
	public int getValueLength()
	{
		if(isUndefinedLength)
			return DicomConstants.DCM_UNDEFINED_LENGTH;

		int iValueLength =0;
		ISingleCollection<byte[]> objSingleCollection = null;
		if(value != null)
		{
			if(value instanceof ISingleCollection)
			{
				objSingleCollection = (ISingleCollection<byte[]>) value;
				for(int index=0; index < objSingleCollection.size(); index++)
				{
					if(objSingleCollection.get(index) != null)
						iValueLength += objSingleCollection.get(index).length;
				}
			}
		}
		return iValueLength;
	}

	/**
	 * This method reads OW in the structure -> OW Basic Offset Table with NO Item Value(Tag and Length) 
	 * then Item Tag, length and value for each frame specified in the offset table3
	 * @param objIDicomStream stream from which the Item is to be written
	 * @param iOWLength length of the DicomValue to be read from the stream
	 * @return integer - returns status success/failure
	 * @throws InvalidDataTypeException If data type validation fails.
	 * @throws IOException If Input/Output operation fails.
	 */
	public boolean read(IDicomStream objIDicomStream,int iOWLength) throws InvalidDataTypeException, IOException
	{
		boolean bSequenceDelimitationExpected = false;
		if(DicomConstants.DCM_UNDEFINED_LENGTH == iOWLength)
		{
			bSequenceDelimitationExpected = true;
			isUndefinedLength = true;
		}

		if(bSequenceDelimitationExpected == true)
		{
			int[] arrFrameCount;

			iGroupNumber = objIDicomStream.readShort();
			iElementNumber = objIDicomStream.readShort();
			//Modified since the GroupNumber
			//was getting changed even if it was greater than 0.
			if(iGroupNumber < 0 )
			{
				iGroupNumber+= 2;	
				iGroupNumber = (65534 + iGroupNumber);
			}
			if(iElementNumber < 0)
			{
				iElementNumber+= 2;
				iElementNumber = (65534 + iElementNumber);
			}
			if(iGroupNumber == DicomConstants.DCM_NESTED_ITEM_DATA_MAJOR_TAG 
					&& iElementNumber == DicomConstants.DCM_NESTED_ITEM_DATA_MINOR_TAG)
			{
				int iItemLength = objIDicomStream.readInt();
				int iNumberOfFrames = 0;
				if(iItemLength != 0)
				{
					iNumberOfFrames = iItemLength/4;
					arrFrameCount = new int[iNumberOfFrames];
					for(int iFrameCount=0 ; iFrameCount < iNumberOfFrames; iFrameCount++)
					{
						arrFrameCount[iFrameCount] = objIDicomStream.readInt();
					}

				}
			}

			while(true)
			{
				iGroupNumber = objIDicomStream.readShort();
				iElementNumber = objIDicomStream.readShort();

				//Modified since the GroupNumber
				//was getting changed even if it was greater than 0.
				if(iGroupNumber < 0 )
				{
					iGroupNumber+= 2;	
					iGroupNumber = (65534 + iGroupNumber);
				}
				if(iElementNumber < 0)
				{
					iElementNumber+= 2;
					iElementNumber = (65534 + iElementNumber);
				}

				if(iGroupNumber == DicomConstants.DCM_NESTED_ITEM_DATA_MAJOR_TAG 
						&& iElementNumber == DicomConstants.DCM_NESTED_ITEM_DATA_MINOR_TAG)
				{
					int iItemLength = objIDicomStream.readInt();

					if(iItemLength != 0)
					{
						byte[] buffer = new byte[iItemLength];
						int iCount = 0;
						byte[] temp;
						while(iCount != iItemLength)
						{
							temp = objIDicomStream.readWord();
							buffer[iCount++] = temp[0];
							buffer[iCount++] = temp[1];
						}

						objSingleCollection.add(buffer);
						// the buffer should be added in image collection 
						//and the image collection should be set as value
						setValue(objSingleCollection);
					}
				}

				if(iGroupNumber == DicomConstants.DCM_SQ_DELIMITATION_DATA_MAJOR_TAG 
						&& iElementNumber == DicomConstants.DCM_SQ_DELIMITATION_DATA_MINOR_TAG)
				{
					objIDicomStream.readInt();
					break;
				}
			}
		}
		else if(iOWLength > 0)
		{
			byte[] buffer = new byte[iOWLength];
			int iCount = 0;
			byte[] temp=null;
			while(iCount != iOWLength)
			{
				temp = objIDicomStream.readWord();
				buffer[iCount++] = temp[0];
				buffer[iCount++] = temp[1];
			}

			objSingleCollection.add(buffer);
			setValue(objSingleCollection);
//			return true;//previously the read function was only returning 0
		}
		return true;
	}

	/**
	 * Retrieves the max length for OW DataType
	 * @return integer - max length for OW DataType
	 */
	public long getMaxLength()
	{
		return 0;
	}

	/**
	 * This method writes the series of byte buffers present in the OW datatype.
	 * For this purpose it writes them in the structure specified in the Dicom standard.
	 * @param objIDicomStream stream on which to write the DataElements
	 * @return integer - status indicating success/failure
	 * @throws IOException If Input/Output operation fails.
	 */
	@SuppressWarnings("unchecked")
	public int write(IDicomStream objIDicomStream) throws IOException
	{
		objSingleCollection = (ISingleCollection<byte[]>)value;
		if(getValueLength() == DicomConstants.DCM_UNDEFINED_LENGTH)
		{
			ITag objStartTag = new Tag(DicomConstants.DCM_NESTED_ITEM_DATA_MAJOR_TAG,DicomConstants.DCM_NESTED_ITEM_DATA_MINOR_TAG);
			//Writing tag for multiframe image offset
			objStartTag.write(objIDicomStream);

			int iItemLength =0;
			for(byte[] arrByte : objSingleCollection)
			{
				if(arrByte != null)
					iItemLength = iItemLength + 4;
			}
			
			//Writing length of offset table
			objIDicomStream.writeInt(iItemLength);

			//Writing offset for each image byte array starting counter
			objIDicomStream.writeInt(0);   //writing first array offset
			byte[] eachImageArray = null;
			for(int iFrameCount = 1; iFrameCount<objSingleCollection.size();iFrameCount++)
			{
				if(objSingleCollection.get(iFrameCount-1) != null)
				{
					eachImageArray = objSingleCollection.get(iFrameCount-1);
					objIDicomStream.writeInt(eachImageArray.length+1);
				}
			}

			//Writing actual pixel data of images
			for(int iDataCount = 0;iDataCount<objSingleCollection.size();iDataCount++)
			{
				if(objSingleCollection.get(iDataCount) != null)
				{
					objStartTag.write(objIDicomStream);                                     //writing tag number
					objIDicomStream.writeInt(objSingleCollection.get(iDataCount).length); 
					byte[] shArray = objSingleCollection.get(iDataCount); 
					byte[] tempArray = new byte[2];
					for(int index = 0; index < shArray.length; index+=2)
					{
						tempArray[0] = shArray[index];
						if((index + 1) < shArray.length)
							tempArray[1] = shArray[index + 1];
						objIDicomStream.writeWord(tempArray);
//						}
					}
				}
			}
			//If OBLength is undefined then write SQ delimiter and zero length at the end
			if(getValueLength() == DicomConstants.DCM_UNDEFINED_LENGTH)
			{
				ITag objDelimitaionTag = new Tag(DicomConstants.DCM_SQ_DELIMITATION_DATA_MAJOR_TAG,DicomConstants.DCM_SQ_DELIMITATION_DATA_MINOR_TAG);
				//writes the Tag information on the stream
				objDelimitaionTag.write(objIDicomStream);
				//writes length as zero as specified in the Dicom Standard
				objIDicomStream.writeInt(0);
			}
		}
		else
		{
			if(objSingleCollection.size() > 0)
			{
				//If the Length is Fixed then get the buffer from the Image collection and
				//write it to stream
				byte[] shArray = objSingleCollection.get(0); 
				if( shArray != null)
				{
					byte[] tempArray = new byte[2];
					for(int index = 0; index < shArray.length; index+=2)
					{
						tempArray[0] = shArray[index];
						if((index + 1) < shArray.length)
							tempArray[1] = shArray[index + 1];
						objIDicomStream.writeWord(tempArray);
					}
				}
			}
		}
		return 1;
	}

	/**
	 * Validates OW DataType
	 * @param objValue object of OW to be validated
	 * @return boolean - status indicating success/failure
	 * @throws ValidationException If validation of DataType fails.
	 */
	public boolean validate(Object objValue) throws ValidationException
	{
		boolean isValid = false;
		try
		{
			if(isValid)
			{
				String strMessage = "Number of Items limit is crossed ";
				throw new ItemLimitException(strMessage);
			}
		}
		catch( ItemLimitException ile)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(ile.getMessage());
			}
			throw new ValidationException(ile.getMessage(),ile);
		}
		return true;
	}

	/**
	 * Validates OW DataType
	 * @param objValue object of OW to be validated
	 * @return boolean - status indicating success/failure
	 * @throws ValidationException If validation of DataType fails.
	 */
	public boolean validate() throws ValidationException
	{
		return validate(value);
	}

	/**
	 * Adds pixel data in the form of raw buffer
	 * @param shArrImage Byte array of raw image data.
	 */
	public void addItem(byte[] shArrImage)
	{
		objSingleCollection.add(shArrImage);
		value = objSingleCollection;
	}

	/**
	 * Sets the value in OB DataType
	 * @param objValue object of type Object representing the value of OB DataType
	 */
	@SuppressWarnings("unchecked")
	public void setValue(Object objValue) throws InvalidDataTypeException
	{
		try
		{
			value = objValue;
			/*
			 * This If block checking whether given value is valid for this Type or not
			 */
			if((value != null) && (!(objValue instanceof ISingleCollection)))
				throw new InvalidValueException(StandardMethods.getStrVRtype(iVRType));

			ISingleCollection<byte[]> objSingleCollection = (ISingleCollection<byte[]>)objValue;
			if(getValueLength() != DicomConstants.DCM_UNDEFINED_LENGTH)
				iVM = objSingleCollection.size();
		}
		catch( InvalidValueException ive)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(ive.getMessage());
			}
			throw new InvalidDataTypeException(ive.getMessage(),ive);
		}
		catch( ValidationException ive)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger.getLogger().log(ive.getMessage());
			}
			throw new InvalidDataTypeException(ive.getMessage(),ive);
		}
	}

	/**
	 *  Dumps the value of Attribute as Hidden.
	 *  @param objIDicomStream - the Stream on which the Data is to be written.
	 *  @throws IOException - Exception is thrown indicating runtime failure of writing operation.
	 */
	public void dumpDICOMInfo(IDicomStream objIDicomStream) throws IOException
	{
		objIDicomStream.writeString("Value : " + "hidden");
	}

	/**
	 * This function sets the Value length to -1.
	 * If this function is called then then length of value is undefined length(-1)
	 */
	public void changeToUndefined()
	{
		this.isUndefinedLength = true;
	}
}
