/*
 * @(#)JFIFStructView.java  1.0  2008-10-14
 *
 * Copyright (c) 1999-2000 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */
package ch.randelshofer.view;

import org.monte.media.ParseException;
import org.monte.media.binary.*;
import org.monte.media.jpeg.*;
import ch.randelshofer.util.*;

import java.awt.*;
import java.io.*;
import java.text.NumberFormat;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;

import javax.swing.*;
import javax.swing.tree.*;

/**
 * Displays the structure of a JPEG File Interchange Format (JFIF) or a
 * JPEG Exchangeable Image File Format (EXIF) file, and parses well known chunks.
 *
 * EXIF Reference:
 * http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf 
 * 
 * @author  Werner Randelshofer, Hausmatt 10, CH-6405 Immensee, Switzerland
 * @version 1.0  2008-10-13 Created.
 */
public class JFIFStructView extends AbstractStructView {

    private InputStream inputStream;
    private static NumberFormat numFormat = NumberFormat.getInstance();

    protected static class ImageSegment implements AbstractStructView.StructNode {

        private int index;
        private long offset;
        private long size;

        public ImageSegment(int index, long offset) {
            this.index = index;
            this.offset = offset;
        }

        @Override
        public String toString() {
            return getName();
        }

        @Override
        public String getName() {
            return "Image " + index;
        }

        @Override
        public String getId() {
            return "Image";
        }

        @Override
        public long getSize() {
            return size;
        }

        @Override
        public long getOffset() {
            return offset;
        }

        @Override
        public String getDescription() {
            return "";
        }

        @Override
        public StructTableModel getStructTableModel() {
            return null;
        }

        @Override
        public byte[] getRawData(long offset, int length) {
            return null;
        }

        @Override
        public java.util.List<BinaryPanel.Highlight> getRawDataHighlights() {
            return null;
        }

        @Override
        public int getIndexInParent() {
            return index;
        }

        @Override
        public boolean hasRawData() {
            return false;
        }

        @Override
        public File getFile() {
            return null;
        }
    }

    protected static class JFIFSegment
            implements AbstractStructView.StructNode {

        private JFIFModel model;
        private int marker;
        private long size;
        private long offset;
        private byte[] data;
        private int indexInParent;
        private File file;

        public JFIFSegment(JFIFModel model, int marker, long offset, long length, byte[] data, File file) {
            this.model = model;
            this.marker = marker;
            this.size = length;
            this.offset = offset;
            this.data = data;
            this.file = file;
        }

        @Override
        public String getName() {
            return model.getName(this);
        }

        public String getMarkerHexString() {
            return Integer.toHexString(marker);
        }

        @Override
        public long getSize() {
            return size;
        }

        @Override
        public long getOffset() {
            return offset;
        }

        @Override
        public String getDescription() {
            return model.getDescription(this);
        }

        @Override
        public StructTableModel getStructTableModel() {
            return model.getStructTableModel(this);
        }

        @Override
        public byte[] getRawData(long offset, int length) {
            if (file != null && data == null && size != 0) {
                RandomAccessFile raf = null;
                try {
                    raf = new RandomAccessFile(file, "r");
                    data = new byte[(int) size];
                    raf.seek(this.offset);
                    raf.readFully(data);
                } catch (IOException ex3) {
                    ex3.printStackTrace();
                    file = null;
                } finally {
                    if (raf != null) {
                        try {
                            raf.close();
                        } catch (IOException ex2) {
                        }
                    }
                }
            }

            if (data == null || offset == 0 && length == data.length) {
                return data;
            } else {
                byte[] bytes = new byte[length];
                System.arraycopy(data, (int) offset, bytes, 0, length);
                return bytes;
            }
        }

        @Override
        public java.util.List<BinaryPanel.Highlight> getRawDataHighlights() {
            return null;
        }

        @Override
        public String toString() {
            return model.toString(this);
        }

        @Override
        public int getIndexInParent() {
            return indexInParent;
        }

        public void setIndexInParent(int cc) {
            indexInParent = cc;
        }

        @Override
        public boolean hasRawData() {
            return (data != null || file != null) && size != 0;
        }

        @Override
        public String getId() {
            return getMarkerHexString();
        }

        @Override
        public File getFile() {
            return null;
        }
    }

    protected static class JFIFModel
            extends DefaultTreeModel {

        private static StructParser structParser;
        private DefaultMutableTreeNode decodeNode;

        public JFIFModel() {
            super(new DefaultMutableTreeNode());
        }

        ;

        public void read(InputStream in, File file)
                throws IOException {
            try {
                DefaultMutableTreeNode root = (DefaultMutableTreeNode) getRoot();
                DefaultMutableTreeNode imageNode = root;
                createStructParser();
                int imageCount = 0;
                byte[] buf = new byte[1024];
                JFIFInputStream fifi = new JFIFInputStream(new BufferedInputStream(in));
                for (JFIFInputStream.Segment seg = fifi.getSegment(); seg != null; seg = fifi.getNextSegment()) {
                    if (seg.marker == JFIFInputStream.SOI_MARKER) {
                        imageNode = new DefaultMutableTreeNode(new ImageSegment(imageCount++, seg.offset));
                        root.add(imageNode);
                    } else if (seg.marker == JFIFInputStream.EOI_MARKER) {
                        ImageSegment iseg = (ImageSegment) imageNode.getUserObject();
                        iseg.size = seg.offset - iseg.offset;
                    }

                    byte[] data;
                    long length = 0;
                    if (file == null) {
                        ByteArrayOutputStream bout = new ByteArrayOutputStream();
                        for (int count = fifi.read(buf, 0, buf.length); count >= 1; count = fifi.read(buf, 0, buf.length)) {
                            bout.write(buf, 0, count);
                            length += count;
                        }
                        data = bout.toByteArray();
                    } else {
                        for (long count = fifi.skip(2048); count >= 1; count = fifi.skip(2048)) {
                            length += count;
                        }
                        data = null;
                    }
                    JFIFSegment jfifSeg = new JFIFSegment(this, seg.marker, seg.offset, length, data, file);
                    jfifSeg.setIndexInParent(imageNode.getChildCount());

                    if (jfifSeg.marker == JFIFInputStream.JUNK_MARKER
                            && jfifSeg.size == 0) {
                        // Skip empty Junk marker at the beginning of the file
                    } else {
                        // Add segment
                        imageNode.add(new DefaultMutableTreeNode(jfifSeg));
                    }
                }
                if (root.getChildCount() == 1) {
                    root.remove(imageNode);
                    for (int i = 0, n = imageNode.getChildCount(); i < n; i++) {
                        root.add((MutableTreeNode) imageNode.getChildAt(0));
                    }
                }
            } catch (IOException e) {
                throw e;
            }
        }

        private static void createStructParser() throws IOException {
            if (structParser == null) {
                BufferedReader reader = null;
                try {
                    reader = new BufferedReader(
                            new InputStreamReader(
                            JFIFStructView.class.getResourceAsStream("JFIFSegments.txt"), "UTF-8"));
                    structParser = (reader == null) ? new StructParser() : new StructParser(reader);
                } catch (NullPointerException e) {
                    InternalError error = new InternalError(e.toString());
                    error.initCause(e);
                    throw error;
                } catch (ParseException e) {
                    InternalError error = new InternalError(e.toString());
                    error.initCause(e);
                    throw error;
                } finally {
                    if (reader != null) {
                        reader.close();
                    }
                }
            }

        }

        public String toString(JFIFSegment seg) {
            return getIdentifierName(seg) + " " + numFormat.format(seg.size);//+" ("+getIndexInParent()+")";        }
        }

        public String getName(JFIFSegment chunkNode) {
            String name = structParser.getName(chunkNode.getMarkerHexString());
            return (name == null) ? chunkNode.getMarkerHexString() : name;
        }

        public String getIdentifierName(JFIFSegment chunkNode) {
            String name = structParser.getIdentifierName(chunkNode.getMarkerHexString());
            return (name == null) ? chunkNode.getMarkerHexString() : name;
        }

        public String getDescription(JFIFSegment chunkNode) {
            return structParser.getDescription(chunkNode.getMarkerHexString());
        }

        public StructTableModel getStructTableModel(JFIFSegment chunkNode) {
            try {
                byte[] data = chunkNode.getRawData(0, (int) chunkNode.getSize());
                return (data == null) ? null : structParser.readStruct(chunkNode.getMarkerHexString(),
                        chunkNode.getRawData(0, (int) chunkNode.getSize()));
            } catch (IOException e) {
//System.out.println("JFIFStructView.JFIFModel.getStructTableModel(...):"+e);   
//e.printStackTrace();
                return null;
            }
        }
    }

    /** Initializes the Form */
    public JFIFStructView() {
        //initComponents ();
    }

    @Override
    public void setCommandContext(String verb, DataHandler dh)
            throws IOException {
        inputStream = new BufferedInputStream(dh.getInputStream());
        final File file;
        if (dh.getDataSource() instanceof FileDataSource) {
            file = ((FileDataSource) dh.getDataSource()).getFile();
        } else {
            file = null;
        }
        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

        ch.randelshofer.gui.SwingWorker worker = new ch.randelshofer.gui.SwingWorker() {

            @Override
            public Object construct() {
                JFIFModel result = new JFIFModel();
                try {
                    result.read(inputStream, file);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    inputStream.close();
                } catch (IOException e) {
                }
                return result;
            }

            @Override
            public void finished() {
                Object result = get();
                if (result instanceof JFIFModel) {
                    getTree().setModel((JFIFModel) result);
                } else {
                    removeAll();
                    add(new JLabel(result.toString()));
                }
                setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                getTree().expandRow(0);
                getTree().setSelectionRow(0);
                getSplitPane().setDividerLocation(getTree().getPreferredSize().width + 20);
            }
        };
        worker.start();
    }

    @Override
    public void dispose() {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
            }
            inputStream = null;
        }
    }

    @Override
    public String toString() {
        return "JFIF Struct View";
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the FormEditor.
     */
    private void initComponents () {//GEN-BEGIN:initComponents
        setLayout (new java.awt.BorderLayout ());

    }//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
}
