/*
 * @(#)DQTMarkerSegment.java	1.5 03/12/19
 *
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.imageio.plugins.jpeg;

import javax.imageio.IIOException;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.plugins.jpeg.JPEGQTable;

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

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;

/**
 * A DQT (Define Quantization Table) marker segment.
 */
class DQTMarkerSegment extends MarkerSegment {
    List tables = new ArrayList();  // Could be 1 to 4
        
    DQTMarkerSegment(float quality, boolean needTwo) {
        super(JPEG.DQT);
        tables.add(new Qtable(true, quality));
        if (needTwo) {
            tables.add(new Qtable(false, quality));
        }
    }

    DQTMarkerSegment(JPEGBuffer buffer) throws IOException {
        super(buffer);
        int count = length;
        while (count > 0) {
            Qtable newGuy = new Qtable(buffer);
            tables.add(newGuy);
            count -= newGuy.data.length+1;
        }
        buffer.bufAvail -= length;
    }

    DQTMarkerSegment(JPEGQTable[] qtables) {
        super(JPEG.DQT);
        for (int i = 0; i < qtables.length; i++) {
            tables.add(new Qtable(qtables[i], i));
        }
    }

    DQTMarkerSegment(Node node) throws IIOInvalidTreeException {
        super(JPEG.DQT);
        NodeList children = node.getChildNodes();
        int size = children.getLength();
        if ((size < 1) || (size > 4)) {
            throw new IIOInvalidTreeException("Invalid DQT node", node);
        }
        for (int i = 0; i < size; i++) {
            tables.add(new Qtable(children.item(i)));
        }
    }

    protected Object clone() {
        DQTMarkerSegment newGuy = (DQTMarkerSegment) super.clone();
        newGuy.tables = new ArrayList(tables.size());
        Iterator iter = tables.iterator();
        while (iter.hasNext()) {
            Qtable table = (Qtable) iter.next();
            newGuy.tables.add(table.clone());
        }
        return newGuy;
    }

    IIOMetadataNode getNativeNode() {
        IIOMetadataNode node = new IIOMetadataNode("dqt");
        for (int i= 0; i<tables.size(); i++) {
            Qtable table = (Qtable) tables.get(i);
            node.appendChild(table.getNativeNode());
        }
        return node;
    }

    /**
     * Writes the data for this segment to the stream in
     * valid JPEG format.
     */
    void write(ImageOutputStream ios) throws IOException {
        // We don't write DQT segments; the IJG library does.
    }

    void print() {
        printTag("DQT");
        System.out.println("Num tables: " 
                           + Integer.toString(tables.size()));
        for (int i= 0; i<tables.size(); i++) {
            Qtable table = (Qtable) tables.get(i);
            table.print();
        }
        System.out.println();
    }

    /**
     * Assuming the given table was generated by scaling the "standard"
     * visually lossless luminance table, extract the scale factor that
     * was used.
     */
    Qtable getChromaForLuma(Qtable luma) {
        Qtable newGuy = null;
        // Determine if the table is all the same values
        // if so, use the same table
        boolean allSame = true;
        for (int i = 1; i < luma.QTABLE_SIZE; i++) {
            if (luma.data[i] != luma.data[i-1]) {
                allSame = false;
                break;
            }
        }
        if (allSame) {
            newGuy = (Qtable) luma.clone();
            newGuy.tableID = 1;
        } else {
            // Otherwise, find the largest coefficient less than 255.  This is
            // the largest value that we know did not clamp on scaling.
            int largestPos = 0;
            for (int i = 1; i < luma.QTABLE_SIZE; i++) {
                if (luma.data[i] > luma.data[largestPos]) {
                    largestPos = i;
                }
            }
            // Compute the scale factor by dividing it by the value in the
            // same position from the "standard" table.
            // If the given table was not generated by scaling the standard,
            // the resulting table will still be reasonable, as it will reflect
            // a comparable scaling of chrominance frequency response of the
            // eye.
            float scaleFactor = ((float)(luma.data[largestPos])) 
                / ((float)(JPEGQTable.K1Div2Luminance.getTable()[largestPos]));
            //    generate a new table
            JPEGQTable jpegTable = 
                JPEGQTable.K2Div2Chrominance.getScaledInstance(scaleFactor, 
                                                               true);
            newGuy = new Qtable(jpegTable, 1);
        }
        return newGuy;
    }

    Qtable getQtableFromNode(Node node) throws IIOInvalidTreeException {
        return new Qtable(node);
    }

    /**
     * A quantization table within a DQT marker segment.
     */
    class Qtable implements Cloneable {
        int elementPrecision;
        int tableID;
        final int QTABLE_SIZE = 64;
        int [] data; // 64 elements, in natural order

        /**
         * The zigzag-order position of the i'th element
         * of a DCT block read in natural order.
         */
        private final int [] zigzag = {
            0,  1,  5,  6, 14, 15, 27, 28,
            2,  4,  7, 13, 16, 26, 29, 42,
            3,  8, 12, 17, 25, 30, 41, 43,
            9, 11, 18, 24, 31, 40, 44, 53,
            10, 19, 23, 32, 39, 45, 52, 54,
            20, 22, 33, 38, 46, 51, 55, 60,
            21, 34, 37, 47, 50, 56, 59, 61,
            35, 36, 48, 49, 57, 58, 62, 63
        };

        Qtable(boolean wantLuma, float quality) {
            elementPrecision = 0;
            JPEGQTable base = null;
            if (wantLuma) {
                tableID = 0;
                base = JPEGQTable.K1Div2Luminance;
            } else {
                tableID = 1;
                base = JPEGQTable.K2Div2Chrominance;
            }
            if (quality != JPEG.DEFAULT_QUALITY) {
                quality = JPEG.convertToLinearQuality(quality);
                if (wantLuma) {
                    base = JPEGQTable.K1Luminance.getScaledInstance
                        (quality, true);
                } else {
                    base = JPEGQTable.K2Div2Chrominance.getScaledInstance
                        (quality, true);
                }
            }
            data = base.getTable();
        }

        Qtable(JPEGBuffer buffer) throws IIOException {
            elementPrecision = buffer.buf[buffer.bufPtr] >>> 4;
            tableID = buffer.buf[buffer.bufPtr++] & 0xf;
            if (elementPrecision != 0) {
                // IJG is compiled for 8-bits, so this shouldn't happen
                throw new IIOException ("Unsupported element precision");
            }
            data = new int [QTABLE_SIZE];
            // Read from zig-zag order to natural order
            for (int i = 0; i < QTABLE_SIZE; i++) {
                data[i] = buffer.buf[buffer.bufPtr+zigzag[i]] & 0xff;
            }
            buffer.bufPtr += QTABLE_SIZE;
        }

        Qtable(JPEGQTable table, int id) {
            elementPrecision = 0;
            tableID = id;
            data = table.getTable();
        }

        Qtable(Node node) throws IIOInvalidTreeException {
            if (node.getNodeName().equals("dqtable")) {
                NamedNodeMap attrs = node.getAttributes();
                int count = attrs.getLength();
                if ((count < 1) || (count > 2)) {
                    throw new IIOInvalidTreeException
                        ("dqtable node must have 1 or 2 attributes", node);
                }
                elementPrecision = 0;
                tableID = getAttributeValue(node, attrs, "qtableId", 0, 3, true);
                if (node instanceof IIOMetadataNode) {
                    IIOMetadataNode ourNode = (IIOMetadataNode) node;
                    JPEGQTable table = (JPEGQTable) ourNode.getUserObject();
                    if (table == null) {
                        throw new IIOInvalidTreeException
                            ("dqtable node must have user object", node);
                    }
                    data = table.getTable();
                } else {
                    throw new IIOInvalidTreeException
                        ("dqtable node must have user object", node);
                }
            } else {
                throw new IIOInvalidTreeException
                    ("Invalid node, expected dqtable", node);
            }
        }

        protected Object clone() {
            Qtable newGuy = null;
            try {
                newGuy = (Qtable) super.clone();
            } catch (CloneNotSupportedException e) {} // won't happen
            if (data != null) {
                newGuy.data = (int []) data.clone();
            }
            return newGuy;
        }

        IIOMetadataNode getNativeNode() {
            IIOMetadataNode node = new IIOMetadataNode("dqtable");
            node.setAttribute("elementPrecision", 
                              Integer.toString(elementPrecision));
            node.setAttribute("qtableId",
                              Integer.toString(tableID));
            node.setUserObject(new JPEGQTable(data));
            return node;
        }

        void print() {
            System.out.println("Table id: " + Integer.toString(tableID));
            System.out.println("Element precision: "
                               + Integer.toString(elementPrecision));
        
            (new JPEGQTable(data)).toString();
            /*
              for (int i = 0; i < 64; i++) {
              if (i % 8 == 0) {
              System.out.println();
              }
              System.out.print(" " + Integer.toString(data[i]));
              }
              System.out.println();
            */
        }
    }
}

