/*
 * @(#)IFFStructView.java  2.2.1  2009-03-27
 *
 * Copyright (c) 1999-2007 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.AbortException;
import org.monte.media.ParseException;
import org.monte.media.iff.*;
import org.monte.media.binary.*;
import ch.randelshofer.util.*;

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

import javax.activation.DataHandler;

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


/**
 * Displays the structure of an IFF file and parses well known chunks.
 *
 * @author  Werner Randelshofer
 * @version 2.2.1 2009-03-07 File IFFChunks.txt is now encoded with UTF-8.
 * <br>2.2 2005-06-19 indexInParent property added.
 * <br>2.1.1 2004-12-26 IFFModel left file IFFChunks.txt open.
 * <br>2.1 2004-04-27 Display child count in tree. 
 * <br>2.0.1 2003-04-01 Revised.
 * <br>2.0 2000-10-10  Parses now several well known IFF chunks.
 * <br>1.0  1999-10-19
 */
public class IFFStructView extends AbstractStructView {
    private InputStream inputStream;

    protected static class IFFChunkNode
    implements AbstractStructView.StructNode {
        private static NumberFormat numFormat = NumberFormat.getInstance();
        private IFFModel model;
        private int type;
        private int id;
        private int size;
        private int offset;
        private byte[] data;
        private int childCount;
        private int indexInParent;

        public IFFChunkNode(IFFModel model, int type, int id, int size, int offset, byte[] data) {
            this.model = model;
            this.type = type;
            this.id = id;
            this.size = size;
            this.offset = offset;
            this.data = data;
        }
        
        
        @Override
        public String getName() {
            return model.getName(this);
        }
        public String getType() {
            return IFFParser.idToString(type);
        }
        @Override
        public String getId() {
            return IFFParser.idToString(id);
        }
        @Override
        public long getSize() {
            return size;
        }
        @Override
        public long getOffset() {
            return offset;
        }
        public void setChildCount(int cc) {
            childCount = cc;
        }
        @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 (data == null || offset == 0 && length == data.length) {
                return data;
            } else {
                byte[] bytes = new byte[size];
                System.arraycopy(data, 0, bytes, (int) offset, size);
                return bytes;
            }
        }

        @Override
        public String toString() {
            if (data == null) {
                return IFFParser.idToString(type)+" "+IFFParser.idToString(id)+" "+numFormat.format(size)+" ("+childCount+")";
            } else {
                return IFFParser.idToString(id)+" "+numFormat.format(size);
            }
        }
        
        @Override
        public int getIndexInParent() {
            return indexInParent; 
        }
        public void setIndexInParent(int cc) {
            indexInParent = cc;
        }

        @Override
        public boolean hasRawData() {
            return data != null;
        }

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

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

    
    protected static class IFFModel
    extends DefaultTreeModel
    implements IFFVisitor {
        private static StructParser structParser;
        private DefaultMutableTreeNode decodeNode;

        public IFFModel(InputStream in)
        throws IOException {
            super(new DefaultMutableTreeNode());
            DefaultMutableTreeNode root = (DefaultMutableTreeNode) getRoot();

            if (structParser == null) {
                BufferedReader reader = null;
                try {
                    reader = new BufferedReader(
                        new InputStreamReader(
                            IFFStructView.class.getResourceAsStream("IFFChunks.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();
                        }
                    }
            }

            try {
                IFFParser iff = new IFFParser();
                decodeNode = root;
                iff.parse(in, this);
            } catch (ParseException e) {
                throw new IOException(e.getMessage());
            } catch (AbortException e) {
                throw new IOException(e.getMessage());
            } finally {
                in.close(); 
            }
        }

        public void registerChunks(IFFParser iff) {
        }

        @Override
        public void enterGroup(IFFChunk group) {
            IFFChunkNode groupNode = new IFFChunkNode(
                this,
                group.getID(),
                group.getType(),
                (int) group.getSize(),
                (int) group.getScan(),
                null
            );
            groupNode.setIndexInParent(decodeNode.getChildCount());
            DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(groupNode);
            decodeNode.add(newNode);
            decodeNode = newNode;
        }

        @Override
        public void leaveGroup(IFFChunk group) {
            IFFChunkNode groupNode = (IFFChunkNode) decodeNode.getUserObject();
            groupNode.setChildCount(decodeNode.getChildCount());
            decodeNode = (DefaultMutableTreeNode) decodeNode.getParent();
        }

        @Override
        public void visitChunk(IFFChunk group, IFFChunk chunk) {
//System.out.println("IFFStructView.visitChunk group ID:"+group.getId()+" type:"+group.getType()+" chunk:"+chunk.getId());
            IFFChunkNode chunkNode = new IFFChunkNode(
                this,
                group.getType(),
                chunk.getID(),
                (int) chunk.getSize(),
                (int) chunk.getScan(),
                chunk.getData()
            );
            
            chunkNode.setIndexInParent(decodeNode.getChildCount());
            decodeNode.add(new DefaultMutableTreeNode(chunkNode));
        }

        private String getMagic(IFFChunkNode chunkNode) {
            String type = chunkNode.getType();
            String id = chunkNode.getId();
            String magic = type+id;
            if (structParser.isMagicDeclared(magic) == false) {
//System.out.println("IFFStructView.getMagic(...): no magic for:"+magic);                
                if (IFFParser.isGroupID(IFFParser.stringToID(type))) {
                    magic = type+"????";
                } else {
                    magic = "????"+id;
                }
            }
            return magic;
        }


        public String getName(IFFChunkNode chunkNode) {
            String name = structParser.getName(getMagic(chunkNode));
            return (name == null) ? chunkNode.getType()+" "+chunkNode.getId() : name;
        }

        public String getDescription(IFFChunkNode chunkNode) {
            return structParser.getDescription(getMagic(chunkNode));
        }

        public StructTableModel getStructTableModel(IFFChunkNode chunkNode) {
            try {
                byte[] data = chunkNode.getRawData(0, (int) chunkNode.getSize());
                return (data == null) ? null : structParser.readStruct(getMagic(chunkNode), chunkNode.getRawData(0, (int) chunkNode.getSize()));
            } catch (IOException e) {
//System.err.println("IFFStructView.IFFModel.getStructTableModel(...):"+e);
//e.printStackTrace();
                return null;
            }
        }
    }

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

    @Override
    public void setCommandContext(String verb, DataHandler dh)
    throws IOException {
        inputStream = new BufferedInputStream(dh.getInputStream());
        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

        ch.randelshofer.gui.SwingWorker worker = new ch.randelshofer.gui.SwingWorker() {
            @Override
            public Object construct() {
                Object result;

                try {
                    result = new IFFModel(inputStream);
                } catch (IOException e) {
                    result = e;
                }
                try {
                    inputStream.close();
                } catch (IOException e) {}

                return result;
            }
            @Override
            public void finished() {
                Object result = get();
                if (result instanceof IFFModel) {
                     getTree().setModel((IFFModel) 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 "IFF 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

}
