/**
 * *************************************************************************
 * Copyright (C) 2009 by Paul Lutus * lutusp@arachnoid.com * * This program is
 * free software; you can redistribute it and/or modify * it under the terms of
 * the GNU General Public License as published by * the Free Software
 * Foundation; either version 2 of the License, or * (at your option) any later
 * version. * * This program is distributed in the hope that it will be useful,
 * * but WITHOUT ANY WARRANTY; without even the implied warranty of *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General
 * Public License for more details. * * You should have received a copy of the
 * GNU General Public License * along with this program; if not, write to the *
 * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA. *
 * *************************************************************************
 */
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * Graphic3DParent.java
 *
 * Created on Mar 7, 2009, 8:09:29 AM
 */
package tankcalc;

import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.event.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;

/**
 *
 * @author lutusp
 */
public class TankImageView extends javax.swing.JPanel implements MouseListener, MouseMotionListener, MouseWheelListener {

    TankCalc parent;
    Dimension oldSize;
    BufferedImage imagea = null;
    BufferedImage imageb = null;
    CartesianPoint oldP, newP;
    ImageGenerator imageGenHi, imageGenLo;
    int oldxs = -1, oldys = -1;
    int xsize, ysize, xCenter, yCenter;
    double threeDFactor = .03;
    double mouseScaleGainFactor = .3;
    int pressX = 0, pressY = 0;
    double defaultScale = 40;
    double paintScale;
    double polygonTransparency = 0;

    TankProperties sharedTP;
    Transform3D graphicTransform;
    TankProcessor tvi;

    int counter = 0;

    boolean lowResMode = false;
    javax.swing.Timer timer;
    int timeDelay = 500;

    /**
     * Creates new form Graphic3DParent
     */
    public TankImageView(TankCalc p, TankProperties stp) {
        //super(p);
        parent = p;
        tvi = p.tankProcessor;
        imageGenHi = p.imageGenHi;
        imageGenLo = p.imageGenLo;
        sharedTP = stp;
        initComponents();
        setup();
    }

    private void setup() {
        addMouseListener(this);
        addMouseMotionListener(this);
        addMouseWheelListener(this);
        graphicTransform = new Transform3D();
        updateSettings(sharedTP);
    }

    public void setPaintChipColors() {
        setChipColor(parent.LVariableLabel, Constants.cylinderColor);
        setChipColor(parent.RVariableLabel, Constants.cylinderColor);
        setChipColor(parent.lbVariableLabel1, Constants.leftCapColor);
        setChipColor(parent.lbVariableLabel2, Constants.leftCapColor);
        setChipColor(parent.rtVariableLabel1, Constants.rightCapColor);
        setChipColor(parent.rtVariableLabel2, Constants.rightCapColor);
        setChipColor(parent.sensorPathLabel, Constants.sensorColor);
        setChipColor(parent.xCoordLabel, Constants.sensorColor);
        setChipColor(parent.yCoordLabel, Constants.sensorColor);
        setChipColor(parent.zCoordLabel, Constants.sensorColor);
    }

    void setChipColor(JLabel label, IColor col) {
        label.setOpaque(true);
        label.setBackground(new Color(col.getRGB()));
    }

    protected void createTankImage(TankProperties sp) {
        sharedTP = sp;
        setMode();
        //parent.p("createtankimage");
        // shalll we reset the view rotations and translations?
        double tolerance = 2;
        double me = tvi.maxExtent;
        double rf = parent.tankProcessor.g_R * 2;
        me = (me < rf) ? rf : me;
        if (sharedTP.modelScaleFactor == 0 || me > sharedTP.modelOldScaleFactor * tolerance || me < sharedTP.modelOldScaleFactor / tolerance) {
            sharedTP.modelScaleFactor = me;
            sharedTP.modelScale = 800.0 / sharedTP.modelScaleFactor;
            sharedTP.modelTranslateX = 0;
            sharedTP.modelTranslateY = 0;
            sharedTP.modelTranslateZ = 0;
            setTranslate();
            sharedTP.modelOldScaleFactor = sharedTP.modelScaleFactor;
        }
        sharedTP.graphicPerspectiveFactor = (sharedTP.modelScaleFactor * 3);
        rebuildModel();
        updateSettings(sharedTP);
    }

    void rebuildModel() {
        imageGenHi.rebuildModel(sharedTP);
        imageGenLo.rebuildModel(sharedTP);
    }

    @Override
    public void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        //String s = imageGen.leftCap.show();
        //parent.p(s);
        drawSequence(g2);
    }

    public void drawSequence(Graphics2D g) {
        int w = getWidth();
        int h = getHeight();
        drawSequence(g, w, h);
    }

    public void drawSequence(Graphics2D g, int w, int h) {
        polygonTransparency = sharedTP.polygonTransparency / 10.0;

        ImageGenerator gen = (lowResMode) ? imageGenLo : imageGenHi;

        if (graphicTransform.sx * graphicTransform.cy > 0) {
            drawImage(g, gen.leftView, w, h);
        } else {
            drawImage(g, gen.rightView, w, h);
        }
    }

    class PlotElement {

        int color;
        Polygon poly;
        double avgz;

        public PlotElement(int c, Polygon p, double av) {
            color = c;
            poly = p;
            avgz = av;
            //parent.p("" + c.getRGB());
        }
    };

    class PlotComparator implements Comparator<PlotElement> {

        @Override
        public int compare(PlotElement p1, PlotElement p2) {
            return (p1.avgz == p2.avgz) ? 0 : (p1.avgz > p2.avgz) ? 1 : -1;
        }
    };

    public void drawImage(Graphics2D g, ArrayList<ImageArray> src, int xsize, int ysize) {
        if (src != null) {
            if (xsize > 0 && ysize > 0) {
                xCenter = xsize / 2;
                yCenter = ysize / 2;
                double ts = (xsize < ysize) ? xsize : ysize;
                Graphics2D cga = null;
                Graphics2D cgb = null;
                paintScale = sharedTP.modelScale * ts * .001;
                if (sharedTP.graphicAnaglyphMode) {
                    // only allocate a new image buffer
                    // if the image has changed size.
                    // this is crucial to fast drawing
                    if (xsize != oldxs || ysize != oldys || imagea == null || imageb == null) {
                        imagea = (BufferedImage) new BufferedImage(xsize, ysize, BufferedImage.TYPE_INT_RGB);
                        imageb = (BufferedImage) new BufferedImage(xsize, ysize, BufferedImage.TYPE_INT_RGB);

                        oldxs = xsize;
                        oldys = ysize;
                    }
                    cga = (Graphics2D) imagea.getGraphics();
                    cga.setColor(new Color(0));
                    cga.fillRect(0, 0, xsize, ysize);
                    cgb = (Graphics2D) imageb.getGraphics();
                    cgb.setColor(new Color(0));
                    cgb.fillRect(0, 0, xsize, ysize);
                } else {
                    g.setColor(parent.graphicBackgroundColor);
                    g.fillRect(0, 0, xsize, ysize);
                    g.setColor(parent.graphicForegroundColor);
                    g.drawRect(0, 0, xsize - 1, ysize - 1);
                }
                if (sharedTP.graphicAntialiasingMode) {
                    RenderingHints rh = new RenderingHints(
                            RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                    g.addRenderingHints(rh);
                    if (sharedTP.graphicAnaglyphMode) {
                        cga.addRenderingHints(rh);
                        cgb.addRenderingHints(rh);
                    }
                }
                ArrayList<PlotElement> plotLista = new ArrayList<PlotElement>();
                ArrayList<PlotElement> plotListb = new ArrayList<PlotElement>();
                Iterator<ImageArray> it = src.iterator();
                while (it.hasNext()) {
                    ImageArray vec = it.next();
                    if (vec.isActive()) {
                        if (sharedTP.graphicAnaglyphMode) {
                            double f = (sharedTP.graphicInverseMode) ? parent.graphicCurrentFactor : -parent.graphicCurrentFactor;
                            drawElements(cga, plotLista, vec, f, sharedTP.graphicAnaglyphMode, Constants.redColor);
                            drawElements(cgb, plotListb, vec, -f, sharedTP.graphicAnaglyphMode, Constants.cyanColor);
                        } else {
                            drawElements(g, plotLista, vec, 0, sharedTP.graphicAnaglyphMode, null);
                        }
                    }
                }
                if (sharedTP.polygonMode) {
                    Collections.sort(plotLista, new PlotComparator());
                    if (sharedTP.graphicAnaglyphMode) {
                        for (PlotElement p : plotLista) {
                            cga.setColor(new Color(p.color, true));
                            cga.fillPolygon(p.poly);
                        }
                        Collections.sort(plotListb, new PlotComparator());
                        for (PlotElement p : plotListb) {
                            cgb.setColor(new Color(p.color, true));
                            cgb.fillPolygon(p.poly);
                        }
                    } else {
                        for (PlotElement p : plotLista) {
                            g.setColor(new Color(p.color, true));
                            g.fillPolygon(p.poly);
                        }
                    }
                }
                if (sharedTP.graphicAnaglyphMode) {
                    g.drawImage(imagea, 0, 0, this);
                    g.setXORMode(new Color(sharedTP.graphicInverseMode ? 0 : 0xffffff));
                    g.drawImage(imageb, 0, 0, this);
                    cga.dispose();
                    cgb.dispose();
                }
            }
        }
    }

    private void drawElements(Graphics2D g, ArrayList<PlotElement> list, ImageArray vec, double threeDFactor, boolean anaglyphMode, IColor color) {
        if (sharedTP.polygonMode) {
            drawPolygons(list, vec, threeDFactor, anaglyphMode, color);
        } else {
            drawLines(g, vec, threeDFactor, anaglyphMode, color);
        }

    }

    private void drawLines(Graphics2D cg, ImageArray vec, double threeDFactor, boolean anaglyphMode, IColor color) {
        if (anaglyphMode) {
            cg.setColor(new Color(color.getRGB()));
        }
        cg.setStroke(new BasicStroke(sharedTP.graphicLineThickness));
        int vpx = vec.getX();
        int vpy = vec.getY();
        // axial
        for (int y = 0; y <= vpy; y++) {
            for (int x = 0; x <= vpx; x++) {
                CartesianPoint p = vec.point[x][y];
                if (p == null) {
                    break;
                }
                p = p.clone();
                if (!anaglyphMode) {
                    cg.setColor(new Color((p.icolor == null) ? color.getRGB() : p.icolor.getRGB()));
                }
                p.start = (x == 0);
                drawScaledLine(cg, p, threeDFactor);
            }
        }
        // radial
        for (int x = 0; x < vec.point.length; x++) {
            for (int y = 0; y < vec.point[x].length; y++) {
                CartesianPoint p = vec.point[x][y];
                if (p == null) {
                    break;
                }
                p = p.clone();
                if (!anaglyphMode) {
                    cg.setColor(new Color((p.icolor == null) ? color.getRGB() : p.icolor.getRGB()));
                }
                p.start = (y == 0);
                drawScaledLine(cg, p, threeDFactor);
            }
        }
    }

    int highlight(IColor color, int alpha, double v, int highValue) {
        int b = (int) (color.b + (v * (highValue - color.b)));
        int g = (int) (color.g + (v * (highValue - color.g)));
        int r = (int) (color.r + (v * (highValue - color.r)));
        return (alpha << 24 | r << 16 | g << 8 | b);
    }

    int anaglyphHighlight(IColor color, int alpha, double v) {
        int b = (int) (v * color.b);
        int g = (int) (v * color.g);
        int r = (int) (v * color.r);
        return (alpha << 24 | r << 16 | g << 8 | b);
    }

    // compAreaAdd() computes a value representing the relation
    // between the view direction and a polygon in three-space.
    // View perpendicular returns 1, on-edge returns 0
    // the "outside" value indicates whether we are viewing
    // a polygon from "outside' or "inside" an object.
    AreaResult compAreaAdd(GPolygon gpoly, Polygon poly, double threeDFactor) {
        double xya = 0, xza = 0, yza = 0;
        double avgz = 0;
        int ix, iy;
        CartesianPoint p, op = null;
        for (CartesianPoint ip : gpoly.vector) {
            p = ip.clone();
            graphicTransform.transformPoint(p);
            perspective(p, threeDFactor);
            // compute determinants for three axes
            if (op != null) {
                xya += op.x * p.y - op.y * p.x;
                yza += op.y * p.z - op.z * p.y;
                xza += op.x * p.z - op.z * p.x;
            }
            op = p;
            avgz += p.z;
            ix = scalePoint(xCenter, paintScale, p.x);
            iy = scalePoint(yCenter, -paintScale, p.y);
            poly.addPoint(ix, iy);
        }
        avgz /= gpoly.length();
        // if traversal is CCW, xya > 0
        // meaning this polygon is being viewed
        // from outside object
        boolean outside = (xya > 0);
        double xya2 = xya * xya;
        // area always 0 <= area <= 1
        double area = xya2 / (xya2 + xza * xza + yza * yza);
        return new AreaResult(area, outside, avgz);
    }

    private void drawPolygons(ArrayList<PlotElement> list, ImageArray vec, double threeDFactor, boolean anaglyphMode, IColor color) {

        int anaglyphMask = ((color == Constants.cyanColor) ? 0xff00ffff : 0xffff0000);
        int highlightValue = 240;

        for (GPolygon[] pa : vec.gpoly) {
            CartesianPoint p = pa[0].vector[0];
            for (GPolygon pb : pa) {
                IColor cc = (anaglyphMode || p == null || p.icolor == null) ? color : p.icolor;
                Polygon poly = new Polygon();
                AreaResult result = compAreaAdd(pb, poly, threeDFactor);
                double alpha = result.outside ? 1 - polygonTransparency : 1;
                int ialpha = (int) (255 * alpha);
                int hv = 0;
                if (anaglyphMode) {
                    hv = anaglyphHighlight(cc, ialpha, 1 - result.area) & anaglyphMask;
                } else {
                    hv = highlight(cc, ialpha, result.area, highlightValue);
                }
                list.add(new PlotElement(hv, poly, result.avgz));
            }
        }
    }

    private void drawScaledLine(Graphics2D cg, CartesianPoint p, double threeDFactor) {
        if (p.start) {
            oldP = p;
            graphicTransform.transformPoint(oldP);
            perspective(oldP, threeDFactor);

        } else {
            newP = p;
            graphicTransform.transformPoint(newP);
            perspective(newP, threeDFactor);
            cg.drawLine(scalePoint(xCenter, paintScale, oldP.x), scalePoint(yCenter, -paintScale, oldP.y),
                    scalePoint(xCenter, paintScale, newP.x), scalePoint(yCenter, -paintScale, newP.y));
            oldP = newP;
        }
    }

    void perspective(CartesianPoint v, double threeDFactor) {
        v.y *= (sharedTP.graphicPerspectiveFactor + v.z) / sharedTP.graphicPerspectiveFactor;
        if (sharedTP.graphicAnaglyphMode) {
            v.x += v.z * threeDFactor;
        }
        v.x *= (sharedTP.graphicPerspectiveFactor + v.z) / sharedTP.graphicPerspectiveFactor;
    }

    int scalePoint(double center, double scale, double x) {
        return (int) ((x * scale) + center);
    }

    public void setMode() {
        parent.anaglyphicLabel.setEnabled(sharedTP.graphicAnaglyphMode);
        parent.graphicForegroundColor = (sharedTP.graphicInverseMode) ? Color.white : Color.black;
        parent.graphicBackgroundColor = (sharedTP.graphicInverseMode) ? Color.black : Color.white;
        parent.graphicCurrentFactor = (sharedTP.graphicInverseMode) ? threeDFactor : -threeDFactor;
    }

    void setTranslate() {
        graphicTransform.setXtranslate(sharedTP.modelTranslateX);
        graphicTransform.setYtranslate(sharedTP.modelTranslateY);
        graphicTransform.setZtranslate(sharedTP.modelTranslateZ);
    }

    void setRotations() {
        graphicTransform.setXAngle(sharedTP.modelAngleX);
        graphicTransform.setYAngle(sharedTP.modelAngleY);
    }

    public void updateSettings(TankProperties sp) {
        sharedTP = sp;
        setTranslate();
        setRotations();
        setMode();
        repaint();
    }

    public void setDefaults(TankProperties sp) {
        sharedTP = sp;
        graphicTransform.resetAll();
        sharedTP.modelAngleX = -45;
        sharedTP.modelAngleY = -30;
        sharedTP.modelTranslateX = 0;
        sharedTP.modelTranslateY = 0;
        sharedTP.modelTranslateZ = 0;
        parent.transparencyComboBox.setSelectedIndex(5);
        parent.xLinesComboBox.setSelectedIndex((sharedTP.polygonMode) ? 16 : 16);
        parent.yLinesComboBox.setSelectedIndex((sharedTP.polygonMode) ? 64 : 16);
        parent.lineThicknessComboBox.setSelectedIndex(1);
        parent.cylinderCheckBox.setSelected(true);
        parent.leftCapCheckBox.setSelected(true);
        parent.rightCapCheckBox.setSelected(true);
        parent.sensorCheckBox.setSelected(true);
        parent.readValues();
        sharedTP.modelScaleFactor = 0;
        createTankImage(sharedTP);
    }

    void setLowMode() {
        if (!lowResMode) {
            lowResMode = true;
            if (timer != null) {
                timer.stop();
                timer = null;
            }
            timer = new javax.swing.Timer(timeDelay,
                    new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                            lowResMode = false;
                            repaint();
                        }
                    }
            );
            timer.start();
        }
    }

    @Override
    public void mousePressed(MouseEvent evt) {
        pressX = evt.getX();
        pressY = evt.getY();
        setLowMode();
    }

    @Override
    public void mouseDragged(MouseEvent evt) {
        int x = evt.getX();
        int y = evt.getY();

        if (evt.isShiftDown()) {
            double mouseTranslateGainFactor = 2.0 / sharedTP.modelScale;
            sharedTP.modelTranslateX += (pressX - x) * mouseTranslateGainFactor;
            sharedTP.modelTranslateY -= (pressY - y) * mouseTranslateGainFactor;
        } else {
            sharedTP.modelAngleX += (pressX - x) * mouseScaleGainFactor;
            sharedTP.modelAngleY += (pressY - y) * mouseScaleGainFactor;

        }
        pressX = x;
        pressY = y;
        setRotations();
        setTranslate();
        repaint();
        setLowMode();
    }

    @Override
    public void mouseWheelMoved(MouseWheelEvent evt) {
        double v = evt.getWheelRotation() * 0.1;
        v = (evt.isShiftDown()) ? v * 0.1 : v;
        v = (evt.isAltDown()) ? v * 0.01 : v;
        sharedTP.modelScale *= 1.0 - v;
        repaint();
        setLowMode();
    }

    @Override
    public void mouseClicked(MouseEvent evt) {
        requestFocus();
        setLowMode();
    }

    @Override
    public void mouseEntered(MouseEvent evt) {
    }

    @Override
    public void mouseExited(MouseEvent evt) {
    }

    @Override
    public void mouseReleased(MouseEvent evt) {
    }

    @Override
    public void mouseMoved(MouseEvent evt) {
    }

    public void handleKeyPressed(KeyEvent evt) {
        //System.out.println("Graphic3DPanel: key pressed: " + evt);
    }

    public void handleKeyReleased(KeyEvent evt) {
        double tstep = 32.0 / sharedTP.modelScale;
        int kcode = evt.getKeyCode();
        //String code = KeyEvent.getKey_Text(kcode);
        //System.out.println(code);
        if (kcode == KeyEvent.VK_DOWN) {
            sharedTP.modelTranslateY -= tstep;
        } else if (kcode == KeyEvent.VK_UP) {
            sharedTP.modelTranslateY += tstep;
        } else if (kcode == KeyEvent.VK_LEFT) {
            sharedTP.modelTranslateX -= tstep;
        } else if (kcode == KeyEvent.VK_RIGHT) {
            sharedTP.modelTranslateX += tstep;
        } else if (kcode == KeyEvent.VK_HOME) {
            sharedTP.modelTranslateZ += tstep;
        } else if (kcode == KeyEvent.VK_END) {
            sharedTP.modelTranslateZ -= tstep;
        }
        setTranslate();
        repaint();
        setLowMode();
    }

    void toClipboard() {
        if (parent.applet) {
            parent.setStatus("Sorry -- can't copy image from applet.", true);
        } else {
            int w = sharedTP.clipboardGraphicWidth;
            int h = sharedTP.clipboardGraphicHeight;
            BufferedImage im = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = (Graphics2D) im.getGraphics();
            drawSequence(g, w, h);
            g.dispose();
            ImageTransferable imt = new ImageTransferable(im);
            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            clipboard.setContents(imt, null);
        }
    }

    /**
     * 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 Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        setToolTipText("<html>\nZoom = mouse wheel<br/>\nRotate = drag mouse<br/>\nTranslate = Shift+drag mouse\n</html>");
        addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyReleased(java.awt.event.KeyEvent evt) {
                formKeyReleased(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 400, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 300, Short.MAX_VALUE)
        );
    }// </editor-fold>//GEN-END:initComponents

    private void formKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_formKeyReleased
        // TODO add your handling code here:
        handleKeyReleased(evt);
    }//GEN-LAST:event_formKeyReleased

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