/**
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info:  http://www.jpedal.org
* (C) Copyright 1997-2008, IDRsolutions and Contributors.
* Main Developer: Simon Barnett
*
* 	This file is part of JPedal
*
* Copyright (c) 2008, IDRsolutions
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the IDRsolutions nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY IDRsolutions ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL IDRsolutions BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Other JBIG2 image decoding implementations include
* jbig2dec (http://jbig2dec.sourceforge.net/)
* xpdf (http://www.foolabs.com/xpdf/)
* 
* The final draft JBIG2 specification can be found at http://www.jpeg.org/public/fcd14492.pdf
* 
* All three of the above resources were used in the writing of this software, with methodologies,
* processes and inspiration taken from all three.
*
* ---------------
* SymbolDictionarySegment.java
* ---------------
*/
package org.jpedal.jbig2.segment.symboldictionary;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.jpedal.jbig2.JBIG2Exception;
import org.jpedal.jbig2.decoders.ArithmeticDecoderStats;
import org.jpedal.jbig2.decoders.DecodeIntResult;
import org.jpedal.jbig2.decoders.HuffmanDecoder;
import org.jpedal.jbig2.decoders.JBIG2StreamDecoder;
import org.jpedal.jbig2.image.JBIG2Bitmap;
import org.jpedal.jbig2.segment.Segment;
import org.jpedal.jbig2.segment.tables.JBIG2CodeTable;
import org.jpedal.jbig2.util.BinaryOperation;

public class SymbolDictionarySegment extends Segment {

	private int noOfExportedSymbols;
	private int noOfNewSymbols;

	short[] symbolDictionaryAdaptiveTemplateX = new short[4], symbolDictionaryAdaptiveTemplateY = new short[4]; 
	short[] symbolDictionaryRAdaptiveTemplateX = new short[2], symbolDictionaryRAdaptiveTemplateY = new short[2];

	private JBIG2Bitmap[] bitmaps;

	private SymbolDictionaryFlags symbolDictionaryFlags = new SymbolDictionaryFlags();

	private ArithmeticDecoderStats genericRegionStats;
	private ArithmeticDecoderStats refinementRegionStats;

	public SymbolDictionarySegment(JBIG2StreamDecoder streamDecoder) {
		super(streamDecoder);
	}

	public void readSegment() throws IOException, JBIG2Exception {

		if (JBIG2StreamDecoder.debug)
			System.out.println("==== Read Segment Symbol Dictionary ====");

		/** read symbol dictionary flags */
		readSymbolDictionaryFlags();

		List codeTables = new ArrayList();
		int numberOfInputSymbols = 0;
		int noOfReferredToSegments = segmentHeader.getReferredToSegmentCount();
		int[] referredToSegments = segmentHeader.getReferredToSegments();

		for (int i = 0; i < noOfReferredToSegments; i++) {
			Segment seg = decoder.findSegment(referredToSegments[i]);
			int type = seg.getSegmentHeader().getSegmentType();

			if (type == Segment.SYMBOL_DICTIONARY) {
				numberOfInputSymbols += ((SymbolDictionarySegment) seg).noOfExportedSymbols;
			} else if (type == Segment.TABLES) {
				codeTables.add(seg);
			}
		}

		int symbolCodeLength = 0;
		int i = 1;
		while (i < numberOfInputSymbols + noOfNewSymbols) {
			symbolCodeLength++;
			i <<= 1;
		}

		JBIG2Bitmap[] bitmaps = new JBIG2Bitmap[numberOfInputSymbols + noOfNewSymbols];

		int k = 0;
		SymbolDictionarySegment inputSymbolDictionary = null;
		for (i = 0; i < noOfReferredToSegments; i++) {
			Segment seg = decoder.findSegment(referredToSegments[i]);
			if (seg.getSegmentHeader().getSegmentType() == Segment.SYMBOL_DICTIONARY) {
				inputSymbolDictionary = (SymbolDictionarySegment) seg;
				for (int j = 0; j < inputSymbolDictionary.noOfExportedSymbols; j++) {
					bitmaps[k++] = inputSymbolDictionary.bitmaps[j];
				}
			}
		}

		int[][] huffmanDHTable = null;
		int[][] huffmanDWTable = null;

		int[][] huffmanBMSizeTable = null;
		int[][] huffmanAggInstTable = null;

		boolean sdHuffman = symbolDictionaryFlags.getFlagValue(SymbolDictionaryFlags.SD_HUFF) != 0;
		int sdHuffmanDifferenceHeight = symbolDictionaryFlags.getFlagValue(SymbolDictionaryFlags.SD_HUFF_DH);
		int sdHuffmanDiferrenceWidth = symbolDictionaryFlags.getFlagValue(SymbolDictionaryFlags.SD_HUFF_DW);
		int sdHuffBitmapSize = symbolDictionaryFlags.getFlagValue(SymbolDictionaryFlags.SD_HUFF_BM_SIZE);
		int sdHuffAggregationInstances = symbolDictionaryFlags.getFlagValue(SymbolDictionaryFlags.SD_HUFF_AGG_INST);

		i = 0;
		if (sdHuffman) {
			if (sdHuffmanDifferenceHeight == 0) {
				huffmanDHTable = HuffmanDecoder.huffmanTableD;
			} else if (sdHuffmanDifferenceHeight == 1) {
				huffmanDHTable = HuffmanDecoder.huffmanTableE;
			} else {
				huffmanDHTable = ((JBIG2CodeTable) codeTables.get(i++)).getHuffTable();
			}
			
			if (sdHuffmanDiferrenceWidth == 0) {
				huffmanDWTable = HuffmanDecoder.huffmanTableB;
			} else if (sdHuffmanDiferrenceWidth == 1) {
				huffmanDWTable = HuffmanDecoder.huffmanTableC;
			} else {
				huffmanDWTable = ((JBIG2CodeTable) codeTables.get(i++)).getHuffTable();
			}
			
			if (sdHuffBitmapSize == 0) {
				huffmanBMSizeTable = HuffmanDecoder.huffmanTableA;
			} else {
				huffmanBMSizeTable = ((JBIG2CodeTable) codeTables.get(i++)).getHuffTable();
			}
			
			if (sdHuffAggregationInstances == 0) {
				huffmanAggInstTable = HuffmanDecoder.huffmanTableA;
			} else {
				huffmanAggInstTable = ((JBIG2CodeTable) codeTables.get(i++)).getHuffTable();
			}
		}

		int contextUsed = symbolDictionaryFlags.getFlagValue(SymbolDictionaryFlags.BITMAP_CC_USED);
		int sdTemplate = symbolDictionaryFlags.getFlagValue(SymbolDictionaryFlags.SD_TEMPLATE);

		if (!sdHuffman) {
			if (contextUsed != 0 && inputSymbolDictionary != null) {
				arithmeticDecoder.resetGenericStats(sdTemplate, inputSymbolDictionary.genericRegionStats);
			} else {
				arithmeticDecoder.resetGenericStats(sdTemplate, null);
			}
			arithmeticDecoder.resetIntStats(symbolCodeLength);
			arithmeticDecoder.start();
		}

		int sdRefinementAggregate = symbolDictionaryFlags.getFlagValue(SymbolDictionaryFlags.SD_REF_AGG);
		int sdRefinementTemplate = symbolDictionaryFlags.getFlagValue(SymbolDictionaryFlags.SD_R_TEMPLATE);
		if (sdRefinementAggregate != 0) {
			if (contextUsed != 0 && inputSymbolDictionary != null) {
				arithmeticDecoder.resetRefinementStats(sdRefinementTemplate, inputSymbolDictionary.refinementRegionStats);
			} else {
				arithmeticDecoder.resetRefinementStats(sdRefinementTemplate, null);
			}
		}

		int deltaWidths[] = new int[noOfNewSymbols];

		int deltaHeight = 0;
		i = 0;

		while (i < noOfNewSymbols) {

			int instanceDeltaHeight = 0;

			if (sdHuffman) {
				instanceDeltaHeight = huffmanDecoder.decodeInt(huffmanDHTable).intResult();
			} else {
				instanceDeltaHeight = arithmeticDecoder.decodeInt(arithmeticDecoder.iadhStats).intResult();
			}

			if (instanceDeltaHeight < 0 && -instanceDeltaHeight >= deltaHeight) {
				if(JBIG2StreamDecoder.debug)
					System.out.println("Bad delta-height value in JBIG2 symbol dictionary");
			}

			deltaHeight += instanceDeltaHeight;
			int symbolWidth = 0;
			int totalWidth = 0;
			int j = i;

			while (true) {

				int deltaWidth = 0;

				DecodeIntResult decodeIntResult;
				if (sdHuffman) {
					decodeIntResult = huffmanDecoder.decodeInt(huffmanDWTable);
				} else {
					decodeIntResult = arithmeticDecoder.decodeInt(arithmeticDecoder.iadwStats);
				}
				
				if (!decodeIntResult.booleanResult())
					break;

				deltaWidth = decodeIntResult.intResult();

				if (deltaWidth < 0 && -deltaWidth >= symbolWidth) {
					if(JBIG2StreamDecoder.debug)
						System.out.println("Bad delta-width value in JBIG2 symbol dictionary");
				}
				
				symbolWidth += deltaWidth;

				if (sdHuffman && sdRefinementAggregate == 0) {
					deltaWidths[i] = symbolWidth;
					totalWidth += symbolWidth;

				} else if (sdRefinementAggregate == 1) {

					int refAggNum = 0;

					if (sdHuffman) {
						refAggNum = huffmanDecoder.decodeInt(huffmanAggInstTable).intResult();
					} else {
						refAggNum = arithmeticDecoder.decodeInt(arithmeticDecoder.iaaiStats).intResult();
					}

					if (refAggNum == 1) {

						int symbolID = 0, referenceDX = 0, referenceDY = 0;

						if (sdHuffman) {
							symbolID = decoder.readBits(symbolCodeLength);
							referenceDX = huffmanDecoder.decodeInt(HuffmanDecoder.huffmanTableO).intResult();
							referenceDY = huffmanDecoder.decodeInt(HuffmanDecoder.huffmanTableO).intResult();
							
							decoder.consumeRemainingBits();
							arithmeticDecoder.start();
						} else {
							symbolID = (int) arithmeticDecoder.decodeIAID(symbolCodeLength, arithmeticDecoder.iaidStats);
							referenceDX = arithmeticDecoder.decodeInt(arithmeticDecoder.iardxStats).intResult();
							referenceDY = arithmeticDecoder.decodeInt(arithmeticDecoder.iardyStats).intResult();
						}
						
						JBIG2Bitmap referredToBitmap = bitmaps[symbolID];

						JBIG2Bitmap bitmap = new JBIG2Bitmap(symbolWidth, deltaHeight, arithmeticDecoder, huffmanDecoder, mmrDecoder);
						bitmap.readGenericRefinementRegion(sdRefinementTemplate, false, referredToBitmap, referenceDX, referenceDY, symbolDictionaryRAdaptiveTemplateX, 
								symbolDictionaryRAdaptiveTemplateY);

						bitmaps[numberOfInputSymbols + i] = bitmap;

					} else {
						JBIG2Bitmap bitmap = new JBIG2Bitmap(symbolWidth, deltaHeight, arithmeticDecoder, huffmanDecoder, mmrDecoder);
						bitmap.readTextRegion(sdHuffman, true, refAggNum, 0, numberOfInputSymbols + i, null, symbolCodeLength, bitmaps, 0, 0, false, 1, 0, 
								HuffmanDecoder.huffmanTableF, HuffmanDecoder.huffmanTableH, HuffmanDecoder.huffmanTableK, HuffmanDecoder.huffmanTableO, HuffmanDecoder.huffmanTableO, 
								HuffmanDecoder.huffmanTableO, HuffmanDecoder.huffmanTableO, HuffmanDecoder.huffmanTableA, sdRefinementTemplate, symbolDictionaryRAdaptiveTemplateX, 
								symbolDictionaryRAdaptiveTemplateY, decoder);
						
						bitmaps[numberOfInputSymbols + i] = bitmap;
					}
				} else {
					JBIG2Bitmap bitmap = new JBIG2Bitmap(symbolWidth, deltaHeight, arithmeticDecoder, huffmanDecoder, mmrDecoder);
					bitmap.readBitmap(false, sdTemplate, false, false, null, symbolDictionaryAdaptiveTemplateX, symbolDictionaryAdaptiveTemplateY, 0);
					bitmaps[numberOfInputSymbols + i] = bitmap;
				}

				i++;
			}

			if (sdHuffman && sdRefinementAggregate == 0) {
				int bmSize = huffmanDecoder.decodeInt(huffmanBMSizeTable).intResult();
				decoder.consumeRemainingBits();

				JBIG2Bitmap collectiveBitmap = new JBIG2Bitmap(totalWidth, deltaHeight, arithmeticDecoder, huffmanDecoder, mmrDecoder);

				if (bmSize == 0) {

					int padding = totalWidth % 8;
					int bytesPerRow = (int) Math.ceil(totalWidth / 8d);

					//short[] bitmap = new short[totalWidth];
					//decoder.readByte(bitmap);
                    int size = deltaHeight * ((totalWidth + 7) >> 3);
                    short[] bitmap = new short[size];
                    decoder.readByte(bitmap);

					short[][] logicalMap = new short[deltaHeight][bytesPerRow];
					int count = 0;
					for (int row = 0; row < deltaHeight; row++) {
						for (int col = 0; col < bytesPerRow; col++) {
							logicalMap[row][col] = bitmap[count];
							count++;
						}
					}

					int collectiveBitmapRow = 0, collectiveBitmapCol = 0;

					for (int row = 0; row < deltaHeight; row++) {
						for (int col = 0; col < bytesPerRow; col++) {
							if (col == (bytesPerRow - 1)) { // this is the last
								// byte in the row
								short currentByte = logicalMap[row][col];
								for (int bitPointer = 7; bitPointer >= padding; bitPointer--) {
									short mask = (short) (1 << bitPointer);
									int bit = (currentByte & mask) >> bitPointer;
									
									collectiveBitmap.setPixel(collectiveBitmapCol, collectiveBitmapRow, bit);
									collectiveBitmapCol++;
								}
								collectiveBitmapRow++;
								collectiveBitmapCol = 0;
							} else {
								short currentByte = logicalMap[row][col];
								for (int bitPointer = 7; bitPointer >= 0; bitPointer--) {
									short mask = (short) (1 << bitPointer);
									int bit = (currentByte & mask) >> bitPointer;
									
									collectiveBitmap.setPixel(collectiveBitmapCol, collectiveBitmapRow, bit);
									collectiveBitmapCol++;
								}
							}
						}
					}

				} else {
					collectiveBitmap.readBitmap(true, 0, false, false, null, null, null, bmSize);
				}

				int x = 0;
				while (j < i){
					bitmaps[numberOfInputSymbols + j] = collectiveBitmap.getSlice(x, 0, deltaWidths[j], deltaHeight);
					x += deltaWidths[j];
					
					j++;
				}
			}
		}

		this.bitmaps = new JBIG2Bitmap[noOfExportedSymbols];

		int j = i = 0;
		boolean export = false;
		while (i < numberOfInputSymbols + noOfNewSymbols) {

			int run = 0;
			if (sdHuffman) {
				run = huffmanDecoder.decodeInt(HuffmanDecoder.huffmanTableA).intResult();
			} else {
				run = arithmeticDecoder.decodeInt(arithmeticDecoder.iaexStats).intResult();
			}
			
			if (export) {
				for (int cnt = 0; cnt < run; cnt++) {
					this.bitmaps[j++] = bitmaps[i++];
				}
			} else {
				i += run;
			}
			
			export = !export;
		}

		int contextRetained = symbolDictionaryFlags.getFlagValue(SymbolDictionaryFlags.BITMAP_CC_RETAINED);
		if (!sdHuffman && contextRetained == 1) {
            genericRegionStats = genericRegionStats.copy();
			if (sdRefinementAggregate == 1) {
                refinementRegionStats = refinementRegionStats.copy();
			}
		}

		/** consume any remaining bits */
		decoder.consumeRemainingBits();
	}

	private void readSymbolDictionaryFlags() throws IOException {
		/** extract symbol dictionary flags */
		short[] symbolDictionaryFlagsField = new short[2];
		decoder.readByte(symbolDictionaryFlagsField);

		int flags = BinaryOperation.getInt16(symbolDictionaryFlagsField);
		symbolDictionaryFlags.setFlags(flags);

		if (JBIG2StreamDecoder.debug)
			System.out.println("symbolDictionaryFlags = " + flags);

		// symbol dictionary AT flags
		int sdHuff = symbolDictionaryFlags.getFlagValue(SymbolDictionaryFlags.SD_HUFF);
		int sdTemplate = symbolDictionaryFlags.getFlagValue(SymbolDictionaryFlags.SD_TEMPLATE);
		if (sdHuff == 0) {
			if (sdTemplate == 0) {
				symbolDictionaryAdaptiveTemplateX[0] = readATValue();
				symbolDictionaryAdaptiveTemplateY[0] = readATValue();
				symbolDictionaryAdaptiveTemplateX[1] = readATValue();
				symbolDictionaryAdaptiveTemplateY[1] = readATValue();
				symbolDictionaryAdaptiveTemplateX[2] = readATValue();
				symbolDictionaryAdaptiveTemplateY[2] = readATValue();
				symbolDictionaryAdaptiveTemplateX[3] = readATValue();
				symbolDictionaryAdaptiveTemplateY[3] = readATValue();
			} else {
				symbolDictionaryAdaptiveTemplateX[0] = readATValue();
				symbolDictionaryAdaptiveTemplateY[0] = readATValue();
			}
		}

		// symbol dictionary refinement AT flags
		int refAgg = symbolDictionaryFlags.getFlagValue(SymbolDictionaryFlags.SD_REF_AGG);
		int sdrTemplate = symbolDictionaryFlags.getFlagValue(SymbolDictionaryFlags.SD_R_TEMPLATE);
		if (refAgg != 0 && sdrTemplate == 0) {
			symbolDictionaryRAdaptiveTemplateX[0] = readATValue();
			symbolDictionaryRAdaptiveTemplateY[0] = readATValue();
			symbolDictionaryRAdaptiveTemplateX[1] = readATValue();
			symbolDictionaryRAdaptiveTemplateY[1] = readATValue();
		}

		/** extract no of exported symbols */
		short[] noOfExportedSymbolsField = new short[4];
		decoder.readByte(noOfExportedSymbolsField);

		int noOfExportedSymbols = BinaryOperation.getInt32(noOfExportedSymbolsField);
        this.noOfExportedSymbols = noOfExportedSymbols;

		if (JBIG2StreamDecoder.debug)
			System.out.println("noOfExportedSymbols = " + noOfExportedSymbols);

		/** extract no of new symbols */
		short[] noOfNewSymbolsField = new short[4];
		decoder.readByte(noOfNewSymbolsField);

		int noOfNewSymbols = BinaryOperation.getInt32(noOfNewSymbolsField);
        this.noOfNewSymbols = noOfNewSymbols;

		if (JBIG2StreamDecoder.debug)
			System.out.println("noOfNewSymbols = " + noOfNewSymbols);
	}

	public int getNoOfExportedSymbols() {
		return noOfExportedSymbols;
	}

	public void setNoOfExportedSymbols(int noOfExportedSymbols) {
		this.noOfExportedSymbols = noOfExportedSymbols;
	}

	public int getNoOfNewSymbols() {
		return noOfNewSymbols;
	}

	public void setNoOfNewSymbols(int noOfNewSymbols) {
		this.noOfNewSymbols = noOfNewSymbols;
	}

	public JBIG2Bitmap[] getBitmaps() {
		return bitmaps;
	}

	public SymbolDictionaryFlags getSymbolDictionaryFlags() {
		return symbolDictionaryFlags;
	}

	public void setSymbolDictionaryFlags(SymbolDictionaryFlags symbolDictionaryFlags) {
		this.symbolDictionaryFlags = symbolDictionaryFlags;
	}

	private ArithmeticDecoderStats getGenericRegionStats() {
		return genericRegionStats;
	}

	private void setGenericRegionStats(ArithmeticDecoderStats genericRegionStats) {
		this.genericRegionStats = genericRegionStats;
	}

	private void setRefinementRegionStats(ArithmeticDecoderStats refinementRegionStats) {
		this.refinementRegionStats = refinementRegionStats;
	}

	private ArithmeticDecoderStats getRefinementRegionStats() {
		return refinementRegionStats;
	}
}
