/*******************************************************************************
 *  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.tools.imageconverter;

import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.swing.ImageIcon;

import com.sun.media.imageioimpl.plugins.jpeg2000.J2KImageReaderCodecLib;

import cdac.medinfo.sdk.dcm30_04.enums.EnumPhotometricInterpretation;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConfig;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConstants;
import cdac.medinfo.sdk.dcm30_04.utils.ISingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.StandardMethods;

public class DICOMImageConvertor 
{
	DICOMImageInfoReader objDicomImageInfo;
	BufferedImage objBufferedImage;
	byte[] arrImageBuffer;

	public BufferedImage processImage(DICOMImageInfoReader objImageInfo, int iFramesCount, byte[] arrImageBuffer)
	{		
		try
		{
			this.objDicomImageInfo = objImageInfo;
			this.arrImageBuffer = arrImageBuffer;

			if(DicomConstants.TS_DEFAULT_IMPLICIT_VR_LITTLE_ENDIAN.equals(objDicomImageInfo.getTSUID()) || DicomConstants.TS_EXPLICIT_VR_BIG_ENDIAN.equals(objDicomImageInfo.getTSUID()) || DicomConstants.TS_EXPLICIT_VR_LITTLE_ENDIAN.equals(objDicomImageInfo.getTSUID()))
			{
				if(objDicomImageInfo.getPhotometricInterpretation().equals(EnumPhotometricInterpretation.MONOCHROME2.getValue()))
				{
					if(8 == objDicomImageInfo.getBitsAllocated())
					{
						ColorModel objColorModel = grayColorModel();
						Image img = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(objDicomImageInfo.getRows(), objDicomImageInfo.getColumns(),objColorModel, arrImageBuffer, 0, objDicomImageInfo.getColumns()));
						return toBufferedImage(img);
					}
					if(16 == objDicomImageInfo.getBitsAllocated())
					{
						ColorModel objColorModel = grayColorModel();
						byte[] arrDestPixels = to8PerPix(arrImageBuffer);
						Image img = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(objDicomImageInfo.getColumns(), objDicomImageInfo.getRows(),objColorModel, arrDestPixels, 0, objDicomImageInfo.getRows()));
						return toBufferedImage(img);
					}
				}
				else if(objDicomImageInfo.getPhotometricInterpretation().equalsIgnoreCase(EnumPhotometricInterpretation.RGB.getValue()))
				{
					return readDicomRGBImage();
				}
				else if(objDicomImageInfo.getPhotometricInterpretation().equalsIgnoreCase(EnumPhotometricInterpretation.PALETTE_COLOR.getValue()))
				{
					return readDicomPaletteImage(arrImageBuffer);
				}
				else
				{
					return readDicomPaletteImage(arrImageBuffer);
				}
			}

			else if(DicomConstants.TS_LOSSY_JPEG_8_BIT.equalsIgnoreCase(objDicomImageInfo.getTSUID()))
			{
				if(objDicomImageInfo.getPhotometricInterpretation().equalsIgnoreCase(EnumPhotometricInterpretation.MONOCHROME2.getValue()))
				{
					ByteArrayInputStream objInputStream = new ByteArrayInputStream(this.arrImageBuffer);
					objBufferedImage = ImageIO.read((InputStream)objInputStream);
					if(null == objBufferedImage)
						return decompressJPEGImageFrame();
					else
						return objBufferedImage;
				}
				else if(objDicomImageInfo.getPhotometricInterpretation().equalsIgnoreCase(EnumPhotometricInterpretation.RGB.getValue()))
				{
					return readDicomRGBImage();
				}
				else
				{
					ByteArrayInputStream objInputStream = new ByteArrayInputStream(this.arrImageBuffer);
					objBufferedImage = ImageIO.read((InputStream)objInputStream);
					return objBufferedImage;
				}
			}	
			else if ( DicomConstants.TS_RLE_LOSSLESS.equalsIgnoreCase(objDicomImageInfo.getTSUID()))
			{
				int iLengthOfArray = objDicomImageInfo.getImageDataLength();
				arrImageBuffer = (byte[])objDicomImageInfo.getFrame(iFramesCount);
				byte[] arrImage = null;
				if(iFramesCount==0)
				{
					arrImage = decompressRLEFrame(arrImageBuffer.length*50);
				}
				else
				{
					arrImage = decompressRLEFrame(iLengthOfArray);

				}
				if(objDicomImageInfo.getPhotometricInterpretation().equalsIgnoreCase(EnumPhotometricInterpretation.RGB.getValue()))
				{
					ColorModel objColorModel = grayColorModel();
					Image img = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(objDicomImageInfo.getColumns(), objDicomImageInfo.getRows(),objColorModel, arrImage, 0, objDicomImageInfo.getColumns()));
					return toBufferedImage(img);
				}
				else
				{
					return readDicomPaletteImage(arrImage);
				}
			}
			else if(DicomConstants. TS_JPEG_2000_LOSSLESS_ONLY.equalsIgnoreCase(objDicomImageInfo.getTSUID()))
			{
				Iterator iter = ImageIO.getImageReadersByFormatName("JPEG2000");
				ImageReader reader = (ImageReader)iter.next();
				J2KImageReaderCodecLib objJ2kImageReader = (J2KImageReaderCodecLib)reader;
				ImageInputStream objiImageInputStream = ImageIO.createImageInputStream(new java.io.File("F:\\Test"));
				objJ2kImageReader.setInput(objiImageInputStream);
				objBufferedImage = objJ2kImageReader.read(0);
				return objBufferedImage;
			}
			else if(DicomConstants. TS_DEFAULT_LOSSLESS_JPEG.equalsIgnoreCase(objDicomImageInfo.getTSUID()))
			{
				Iterator iter = ImageIO.getImageReadersByFormatName("JPEG-LS");
				ImageReader reader = (ImageReader)iter.next();
				J2KImageReaderCodecLib objJ2kImageReader = (J2KImageReaderCodecLib)reader;
				ImageInputStream objiImageInputStream = ImageIO.createImageInputStream(new java.io.File("F:\\Test"));
				objJ2kImageReader.setInput(objiImageInputStream);
				objBufferedImage = objJ2kImageReader.read(0);
				return objBufferedImage;
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		return null;
	}

	private byte[] decompressRLEFrame(int iLengthofArray)
	{
		byte[] arrExpandedImage = new byte[iLengthofArray];

		int iImageCount = 0;
		byte byBitsPerPixel = 1;
		if(objDicomImageInfo.getPhotometricInterpretation().equalsIgnoreCase(EnumPhotometricInterpretation.RGB.getValue()))
			byBitsPerPixel = 3;

		int iSizeofUncompressedImage = objDicomImageInfo.getRows() * objDicomImageInfo.getColumns() * byBitsPerPixel;
		int  iBitsPerPixel = objDicomImageInfo.getBitsAllocated() /8;
		iSizeofUncompressedImage *= iBitsPerPixel;
		int  iSizeReadsoFar = 0;

		int  iSizeofUncompressedRLEFrame = 0;

		if(objDicomImageInfo.getPhotometricInterpretation().equalsIgnoreCase(EnumPhotometricInterpretation.RGB.getValue()))
			iSizeofUncompressedRLEFrame = iSizeofUncompressedImage/3;
		else
			iSizeofUncompressedRLEFrame = iSizeofUncompressedImage;

		int arrdwNumSegments[] = new int[16];
		for(int i=0; i< 16; i++)
			arrdwNumSegments[i] = arrImageBuffer[i];
		if(arrImageBuffer[0]!=0)
			arrdwNumSegments[0]=1;


		arrImageBuffer[0] += arrdwNumSegments[4];
		iSizeReadsoFar += arrdwNumSegments[4];
		int  iSizeReadsoFarInCurrentRLEFrame = arrdwNumSegments[4];
		int  iNumOutputBytes = 0;

		for (int i=0;i<arrdwNumSegments[0];i++)
		{
			int  iNumIter = arrdwNumSegments[i+2];
			int iNumOutputFrameSize = 0;
			if(iNumIter ==0)
			{
				while(iNumOutputBytes <iSizeofUncompressedImage)
				{
					for(int j=64;j<arrImageBuffer.length;j++)
					{				
						int  iData =arrImageBuffer[j];

						iSizeReadsoFar++;
						if(iSizeReadsoFar > iLengthofArray )
						{
							break;
						}
						if((iData >= 0) && (iData <= 127))
						{
							iSizeReadsoFar += (iData + 1);
							iNumOutputBytes += (iData + 1);
							iNumOutputFrameSize += (iData + 1);
							int nLength = iData + 1;
							if(j==arrImageBuffer.length-1)
							{
								break;
							}
							for(int iCount=0; iCount<nLength; iCount++, iImageCount++)
								arrExpandedImage[iImageCount] = arrImageBuffer[++j];
							arrImageBuffer[0] += (iData + 1);
						}
						else if((iData <= -1) && (iData >= -127))
						{
							int nLength = -iData + 1;
							byte  bBYte = arrImageBuffer[++j];
							for(int iCount=0; iCount<nLength; iCount++, iImageCount++)
								arrExpandedImage[iImageCount] = bBYte;   
							iSizeReadsoFar += 1; // size of byte is 1
							iNumOutputBytes += nLength;
							iNumOutputFrameSize += nLength;
						}
						else if(iData == -128)
							continue;
					}
				}
			}
		}
		return arrExpandedImage;
	}

	private BufferedImage decompressJPEGImageFrame() throws Exception
	{
		int iSizeReadSoFar = 0;
		int iImageIndex = 0;

		int iAlignedWidth = (((objDicomImageInfo.getColumns() + 3) >> 2) << 2);
		int iAlignedHeight = (((objDicomImageInfo.getRows() + 3) >> 2) << 2);
		int iBitsPerPixel = objDicomImageInfo.getBitsAllocated() / 8;
		byte[] arrTempImage = null;

		if((objDicomImageInfo.getPhotometricInterpretation().equalsIgnoreCase(EnumPhotometricInterpretation.MONOCHROME1.getValue())) || (objDicomImageInfo.getPhotometricInterpretation().equalsIgnoreCase(EnumPhotometricInterpretation.MONOCHROME2.getValue())))
		{
			arrTempImage = new byte[iAlignedHeight * iAlignedWidth * objDicomImageInfo.iNumberOfFrames];
			for(int i=0;i<arrImageBuffer.length;i++)
			{
				if(i>arrImageBuffer.length-1)
				{
					break;
				}
				else
				{
					arrTempImage[i]=arrImageBuffer[i];
				}
			}
		}
		else
		{
			arrTempImage = new byte[3 * iAlignedHeight * iAlignedWidth * objDicomImageInfo.iNumberOfFrames];
		}
		int iPrevDataAdded = 0;
		return	readDicomPaletteImage(arrTempImage);
	}


	private BufferedImage readDicomPaletteImage(byte[] arrByte) throws Exception
	{
		ColorModel objColorModel = getPaletteColorModel();
		Image img = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(objDicomImageInfo.getColumns(), objDicomImageInfo.getRows(),objColorModel, arrByte, 0, objDicomImageInfo.getColumns()));
		return toBufferedImage(img);
	}

	private BufferedImage readRGBImage(byte[] arrByte) throws Exception
	{
		int iImageLength = objDicomImageInfo.getRows()*objDicomImageInfo.getColumns()*3;
		int iComponentLength = objDicomImageInfo.getRows()*objDicomImageInfo.getColumns();
		byte[] arrRed = new byte[iComponentLength];
		byte[] arrGreen = new byte[iComponentLength];
		byte[] arrBlue = new byte[iComponentLength];
		int count = 0;

		for(int i=0, j=0; i<(iImageLength); i+=3, j++)
		{
			arrByte[i] =  arrBlue[j];
			arrByte[i + 1] = arrGreen[j];
			arrByte[i + 2] = arrRed[j];
		}
		Image image;
		int nsizeimage = objDicomImageInfo.getRows()*objDicomImageInfo.getColumns()*3;
		int nheight = objDicomImageInfo.getRows();
		int nwidth = objDicomImageInfo.getColumns();
		int npad = (nsizeimage / nheight) - nwidth ;
		int ndata[] = new int [nheight * nwidth];
		int iLength =  ( nwidth + npad) * 3 * nheight;
		byte brgb[] = new byte [iLength];
		for(int i=0; i<((nwidth + npad) * 3 * nheight); i++)
			brgb[i] = arrByte[i];
		int nindex = 0;
		for (int j = 0; j < nheight; j++)
		{
			for (int i = 0; i < nwidth; i++)
			{
				ndata [nwidth * (nheight - j - 1) + i] =(255&0xff)<<24 | (((int)brgb[nindex+2]&0xff)<<16) | (((int)brgb[nindex+1]&0xff)<<8) | (int)brgb[nindex]&0xff;
				nindex += 3;
			}
			nindex += npad;
		}
		image = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(nwidth, nheight, ndata, 0, nwidth));
		BufferedImage objBufferedImage = toBufferedImage(image);

		//Invert the Image if its of RGB Type
		if(objDicomImageInfo.getPhotometricInterpretation().equalsIgnoreCase(EnumPhotometricInterpretation.RGB.getValue()))
		{
			double dScalefactorX = 1;
			double dScalefactorY = -1;
			AffineTransform at = new AffineTransform();
			at = AffineTransform.getTranslateInstance(0, objDicomImageInfo.getRows());
			at.scale(dScalefactorX,dScalefactorY);
			BufferedImageOp bio;
			bio = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
			objBufferedImage = bio.filter(objBufferedImage, null);
		}
		return objBufferedImage;
	}

	private BufferedImage readDicomRGBImage() throws Exception
	{
		int iImageLength = objDicomImageInfo.getRows()*objDicomImageInfo.getColumns()*3;
		int iComponentLength = objDicomImageInfo.getRows()*objDicomImageInfo.getColumns();
		if(objDicomImageInfo.getPlanarConfiguration() == 1)
		{
			byte[] arrRed = new byte[iComponentLength];
			byte[] arrGreen = new byte[iComponentLength];
			byte[] arrBlue = new byte[iComponentLength];
			int count = 0;

			for(int i=0; i<iComponentLength; i++)
			{
				arrRed[i] = arrImageBuffer[i];
				count = count + 1;
			}
			for(int i=count, j=0; i<iComponentLength*2; i++, j++)
			{
				arrGreen[j] = arrImageBuffer[i];
				count = count + 1;
			}
			for(int i=count, j=0; i<iComponentLength*3; i++, j++)
			{
				arrBlue[j] = arrImageBuffer[i];
				count = count + 1;
			}
			for(int i=0, j=0; i<(iImageLength); i+=3, j++)
			{
				arrImageBuffer[i] =  arrBlue[j];
				arrImageBuffer[i + 1] = arrGreen[j];
				arrImageBuffer[i + 2] = arrRed[j];
			}
		}
		Image image;

		int nsizeimage = objDicomImageInfo.getRows()*objDicomImageInfo.getColumns()*3; 
		int nheight = objDicomImageInfo.getRows();
		int nwidth = objDicomImageInfo.getColumns();
		int npad = (nsizeimage / nheight) - nwidth * 3;
		int ndata[] = new int [nheight * nwidth];
		int iLength =  ( nwidth + npad) * 3 * nheight;
		byte brgb[] = new byte [iLength];
		for(int i=0; i<((nwidth + npad) * 3 * nheight); i++)
			brgb[i] = arrImageBuffer[i];

		int nindex = 0;
		for (int j = 0; j < nheight; j++)
		{
			for (int i = 0; i < nwidth; i++)
			{
				ndata [nwidth * (nheight - j - 1) + i] =(255&0xff)<<24 | (((int)brgb[nindex+2]&0xff)<<16) | (((int)brgb[nindex+1]&0xff)<<8) | (int)brgb[nindex]&0xff;
				nindex += 3;
			}
			nindex += npad;
		}
		image = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(nwidth, nheight, ndata, 0, nwidth));
		BufferedImage objBufferedImage = toBufferedImage(image);
		//Invert the Image if its of RGB Type
		if(objDicomImageInfo.getPhotometricInterpretation().equalsIgnoreCase(EnumPhotometricInterpretation.RGB.getValue()))
		{
			double dScalefactorX = 1;
			double dScalefactorY = -1;
			AffineTransform at = new AffineTransform();
			at = AffineTransform.getTranslateInstance(0, objDicomImageInfo.getRows());
			at.scale(dScalefactorX,dScalefactorY);
			BufferedImageOp bio;
			bio = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
			objBufferedImage = bio.filter(objBufferedImage, null);
		}
		return objBufferedImage;
	}

	private ColorModel getPaletteColorModel()
	{
		return new IndexColorModel(8, 256, objDicomImageInfo.getRedPlaette(), objDicomImageInfo.getGreenPlaette(), objDicomImageInfo.getBluePlaette());
	}

	protected ColorModel makeDefaultColorModel() 
	{
		byte[] rLUT = new byte[256];
		byte[] gLUT = new byte[256];
		byte[] bLUT = new byte[256];
		for(int i=0; i<256; i++) {
			rLUT[i]=(byte)i;
			gLUT[i]=(byte)i;
			bLUT[i]=(byte)i;
		}
		return new IndexColorModel(8, 256, rLUT, gLUT, bLUT);
	}

	private ColorModel grayColorModel()
	{		
		byte[] arrGrayScale = new byte[256];
		for (int i = 0; i <256 ; i++ )
			arrGrayScale[i] = (byte)(i & 0xff );
		return (new IndexColorModel(8,256,arrGrayScale,arrGrayScale,arrGrayScale));
	}

	private byte[] to8PerPix(byte[] arrPixelData)
	{
		int[] arrPixels = new int[objDicomImageInfo.getRows()*objDicomImageInfo.getColumns()];
		int value =0;
		int iPixelLength = arrPixels.length; 
		if(objDicomImageInfo.getHighBit() >= 8)
		{	
			for(int i = 0; i < iPixelLength; i++)
			{
				value = ((int)(arrPixelData[(2*i) + 1] & 0xff) << 8) | (int)(arrPixelData[(2*i)] & 0xff); //msb first !
				arrPixels[i] = value;
			}
		}
		else if(objDicomImageInfo.getHighBit() <= 7)
		{
			for( int i = 0 ; i < iPixelLength; i++)
			{	
				value = ((int)(arrPixelData[(2*i)] & 0xff ) << 8)| (int)(arrPixelData[(2*i) + 1] & 0xff); //lsb first !
				arrPixels[i] = value;
			}
		}
		int maxValue = 0;
		int minValue = 0xffff;
		for ( int i = 0 ; i < arrPixels.length ; i++)
		{
			if (arrPixels[i] > maxValue) 
				maxValue = arrPixels[i];

			if (arrPixels[i] < minValue)
				minValue = arrPixels[i];
		}
		int scale = maxValue - minValue;

		if(scale == 0) 
			scale = 1;
		byte[] destPixels = new byte[objDicomImageInfo.getColumns() * objDicomImageInfo.getRows()];
		for (int i = 0 ; i < arrPixels.length ; i++ )
		{
			value = ((arrPixels[i] - minValue ) * 255) / scale;
			destPixels[i] = (byte)(value&0xff);			
		}
		return destPixels;					
	}


	private static boolean hasAlpha(Image objImage)  throws Exception
	{
		if (objImage instanceof BufferedImage) 
		{
			BufferedImage bimage = (BufferedImage)objImage;
			return bimage.getColorModel().hasAlpha();
		}
		PixelGrabber pg = new PixelGrabber(objImage, 0, 0, 1, 1, false);
		pg.grabPixels();
		ColorModel cm = pg.getColorModel();
		return cm.hasAlpha();
	}

	private static BufferedImage toBufferedImage(Image objImage) throws Exception
	{
		if (objImage instanceof BufferedImage) 
		{
			return (BufferedImage)objImage;
		}
		objImage = new ImageIcon(objImage).getImage();
		int transparency = Transparency.OPAQUE;
		boolean hasAlpha = hasAlpha(objImage);
		if (hasAlpha) 
			transparency = Transparency.BITMASK;
		BufferedImage objBufferedImage = null;
		GraphicsEnvironment objGraphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
		GraphicsDevice objGraphicsDevice = objGraphicsEnvironment.getDefaultScreenDevice();
		GraphicsConfiguration objGraphicsConfiguration = objGraphicsDevice.getDefaultConfiguration();
		objBufferedImage = objGraphicsConfiguration.createCompatibleImage(objImage.getWidth(null), objImage.getHeight(null), transparency);
		if (objBufferedImage == null) 
		{
			int iImageType = BufferedImage.TYPE_INT_RGB;
			if (hasAlpha) 
			{
				iImageType = BufferedImage.TYPE_INT_ARGB;
			}
			objBufferedImage = new BufferedImage(objImage.getWidth(null), objImage.getHeight(null), iImageType);
		}
		Graphics objGraphics = objBufferedImage.createGraphics();
		objGraphics.drawImage(objImage, 0, 0, null);
		objGraphics.dispose();
		//Dispose the objects
		objGraphicsDevice = null;
		objGraphicsEnvironment = null;
		objGraphicsConfiguration = null; 
		objImage = null;        
		//Dispose the object
		return objBufferedImage;
	}
}
