/* Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved. */

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Blob;
import java.sql.SQLException;
import oracle.jdbc.OracleResultSet;
import oracle.ord.dicom.OrdDicom;
import oracle.ord.im.OrdImage;

public class DICOMStream {
	/**
	 * DICOM stream is derived from an one-to-one Oracle result set. 
	 */
	private OracleResultSet dicomSet;
	
	/**
	 * All retrieved attributes are listed here. 
	 */
	private String[] attsName = {
				     "Accession Number",
				     "Acquisition Date",
				     "Acquisition Time", 
				     "Body Part Examined",
				     "Data Collection Diameter",
				     "Device Serial Number",
				     "Distance Source to Detector",
				     "Distance Source to Patient",
				     "Image Comments",
				     "Institution Address",
				     "Institution Name",
				     "Institutional Department Name",
				     "KVP",
				     "Manufacturer",
				     "Manufacturer's Model Name",
				     "Modality", 
				     "Name of Physician(s) Reading Study",
				     "Operators' Name",
				     "Patient's Age",
				     "Patient ID",
				     "Patient's Birth Date",
				     "Patient's Name",
				     "Patient's Sex",
				     "Referring Physician's Name",
				     "Performing Physician's Name",
				     "Series Date",
				     "Series Instance UID",
				     "Slice Thickness",
				     "Software Version(s)",
				     "SOP Class UID",
				     "SOP Instance UID",
				     "Source Type",
				     "Spatial Resolution",
				     "Station Name",
				     "Study Date",
				     "Study Description"
	};
	
	/**
	 * Constructor with an Oracle result set.
	 */
	public DICOMStream(OracleResultSet set) throws SQLException {
		dicomSet = set;
	}
	
	/**
	 * Convert DICOM attributes from Oracle DICOM to a byte array. 
	 */
	private byte[] convertAttributeToByte(OrdDicom dicom, String attName) 
	    throws SQLException {
		String attValue = dicom.getAttributeByName(attName, "DICOM");
		//System.out.println(
		    //"[LOG] Attribute " + attName + " : " + attValue);
		return attValue.getBytes();	
	}
	
	/**
	 * Convert DICOM content from Oracle DICOM to a byte array. 
	 */ 
	private byte[] convertBlobToByte(Blob blob) throws SQLException, 
                                                           IOException {
		ByteArrayOutputStream baoStream = new ByteArrayOutputStream();
		InputStream iStream;
		byte[] buf = new byte[1024];
		int num = 0;

		/* Get a binary input stream from DICOM content. */
		iStream = blob.getBinaryStream();
		
		try {
			/* Read bytes from input stream to a byte array output
			 * stream.
			 */ 			 
			while ((num=iStream.read(buf)) >= 0)
				baoStream.write(buf, 0, num);
		} finally {
			iStream.close();
		}
		
		/* Return all bytes in the byte array output stream. */
		return baoStream.toByteArray();
	}
	
	/**
	 * Convert integer to byte array.	 
	 */
	private byte[] convertIntToByte(int value) {
		  byte[] result = new byte[4];   
		  result[0] = (byte)((value >> 24) & 0xFF);
		  result[1] = (byte)((value >> 16) & 0xFF);
		  result[2] = (byte)((value >> 8) & 0xFF); 
		  result[3] = (byte)(value & 0xFF);
		  return result;
	}

	/**
	 * Write the length of byte array and the array itself to the output 
	 * stream.
	 */
	private void writeBytesToStream(OutputStream oStream, byte[] bytes) 
	    throws IOException {
		if (bytes != null) { 
			oStream.write(convertIntToByte(bytes.length), 0, 4);
	    		oStream.write(bytes, 0, bytes.length);
		} else {
			oStream.write(convertIntToByte(0), 0, 4);
		}
	}
	
	/**
	 * Write byte array to the output file.
	 */
	private void writeBytesToFile(File file, byte[] bytes) {	
		int len = 0;
		
		try {
			OutputStream fout = new FileOutputStream(file);
			ByteArrayInputStream iStream = 
			    new ByteArrayInputStream(bytes); 
		
			try {
				while ((len = iStream.read(bytes)) != -1) 
					fout.write(bytes, 0, len);
				
			} catch(IOException e) {
				
			} finally{
				fout.close();
				iStream.close();
			}
		} catch (IOException e) {
			System.out.println(
			    "[ERROR] Cache downloaded DICOM and thumbnail");
			e.printStackTrace();
		}
	}

	/**
	 * Serialize DICOMs in the Oracle result set to an output stream. 
	 * Retrieve all DICOM objects and iteratively get byte arrays from 
	 * their attributes and contents finally write them into an output 
	 * stream sequentially. 
	 * 
	 * All DICOMs are serialized and written into the output stream in the 
	 * following structure.
	 *     +-----------+-----------+-----------+-----------+
	 *     |  DICOM 1  |  DICOM 2  |    ...    |  DICOM N  |
	 *     +-----------+-----------+-----------+-----------+
	 *     |_____  ____|
	 *           ||
	 *           \/
	 *     +---------------------------+
	 *     |  Total length             |        
	 *     +---------------------------+-------------------------+
	 *     |  ID length                |  ID                     |
	 *     +---------------------------+-------------------------+
	 *     |  SOP Class UID length     |  SOP Class UID length   |
	 *     |  Patient's name length    |  Patient's name         |
	 *     |  ...                      |  ...                    |
	 *     +---------------------------+-------------------------+
	 *     |  Thumbnail format length  |  Thumbnail format       |
	 *     +---------------------------+-------------------------+
	 *     |  Thumbnail content length |  Thumbnail content      |
	 *     +---------------------------+-------------------------+
	 *     |  DICOM content length     |  DICOM content          |
	 *     +---------------------------+-------------------------+
	 *       
	 */
	public int getBytes(OutputStream oStream, String dicomColumn, 
	    String thumbColumn) throws SQLException, IOException {
		byte[] attBytes = null;
		byte[] thumbBytes = null;
		int byteLength = 0;
		
		while (dicomSet.next()) {
			/* Get the next DICOM from the Oracle result set. */
			OrdDicom dicom = (OrdDicom)dicomSet.getORAData(
			    dicomColumn, OrdDicom.getORADataFactory());
			
			/* Set properties before retrieving its properties. */
			dicom.setProperties();
 	    	
			/* Get thumbnail and convert it to a byte array. */
			OrdImage imgData = (OrdImage)dicomSet.getORAData(
			    thumbColumn, OrdImage.getORADataFactory());
		 	OrdImage image = dicom.processCopy(
		 	    "maxScale=480 600, fileFormat=JFIF", imgData);
	 	    	thumbBytes = convertBlobToByte(image.getContent());
 	    	
			/* 
			 * Get the total byte length of the current DICOM that
			 * would be written into the output stream.
			 */
			byteLength = 0;
			byteLength += 4 + 4; 
 	    		for (int i = 0; i < attsName.length; i++) {
 	    			try {
 	    				attBytes = convertAttributeToByte(
					    dicom, attsName[i]);
 	 	    			byteLength += 4 + attBytes.length;
 	 	    		} catch (NullPointerException e) {
 	 	    			byteLength += 4 + 0;
 	 	    		}
 	    		}
 	    		byteLength += 4 + "JFIF".getBytes().length;
 	    		byteLength += 4 + thumbBytes.length;
			oStream.write(convertIntToByte(byteLength));
 	    	
			/*
			 * Get ID, attributes, thumbnail format, thumbnail and 
			 * content from the current DICOM and convert them to 
			 * byte arrays. Then write each byte array to output 
			 * stream together with its length.
			 */
			String dicomIDString = dicomSet.getString(1);
			int dicomID = Integer.parseInt(dicomIDString);
			writeBytesToStream(oStream, 
			    convertIntToByte(dicomID));
			
 		    	for (int i = 0; i < attsName.length; i++) {
 	    			try {
 	    				attBytes = convertAttributeToByte(
					    dicom, attsName[i]);
 	 	    			writeBytesToStream(oStream, attBytes);
				} catch (NullPointerException e) {
					writeBytesToStream(oStream, null);
				}
 	    		}
 	    	
 	    		writeBytesToStream(oStream, "JFIF".getBytes());
 	    		writeBytesToStream(oStream, thumbBytes);
 	    	
 	    		System.out.println(
			    "[LOG] Finish sending DICOM ID:" + dicomIDString);
 	    	
 	    		/* 
 	    	 	 * To monitor the retrieved DICOM object, cache its 
 	    	 	 * thumbnail on the file system.  
 	    	 	 */
 	    		String jpegFileName = 
			     dicom.getSourceName().replaceAll("dcm", "jpeg");
 	    		File thumbFile = new File("cache//" + jpegFileName);
 	    		writeBytesToFile(thumbFile, thumbBytes);
		}
		
		return byteLength; 
	}	
}
