import com.ardor3d.bounding.BoundingBox;
import com.ardor3d.framework.DisplaySettings;
import com.ardor3d.framework.FrameHandler;
import com.ardor3d.framework.Scene;
import com.ardor3d.framework.Updater;
import com.ardor3d.framework.jogl.JoglCanvasRenderer;
import com.ardor3d.framework.jogl.awt.JoglAwtCanvas;
import com.ardor3d.framework.jogl.awt.JoglSwingCanvas;
import com.ardor3d.framework.jogl.awt.JoglNewtAwtCanvas;
import com.ardor3d.image.Image;
import com.ardor3d.image.ImageDataFormat;
import com.ardor3d.image.PixelDataType;
import com.ardor3d.image.Texture;
import com.ardor3d.input.PhysicalLayer;
import com.ardor3d.input.awt.AwtFocusWrapper;
import com.ardor3d.input.awt.AwtKeyboardWrapper;
import com.ardor3d.input.awt.AwtMouseManager;
import com.ardor3d.input.awt.AwtMouseWrapper;
import com.ardor3d.input.logical.DummyControllerWrapper;
import com.ardor3d.input.logical.LogicalLayer;
import com.ardor3d.intersection.PickResults;
import com.ardor3d.light.PointLight;
import com.ardor3d.math.ColorRGBA;
import com.ardor3d.math.Ray3;
import com.ardor3d.math.Vector3;
import com.ardor3d.renderer.Camera;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.queue.RenderBucketType;
import com.ardor3d.renderer.state.LightState;
import com.ardor3d.renderer.state.TextureState;
import com.ardor3d.renderer.state.ZBufferState;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.MeshData;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.hint.TextureCombineMode;
import com.ardor3d.scenegraph.shape.Box;
import com.ardor3d.scenegraph.shape.Quad;
import com.ardor3d.scenegraph.shape.Teapot;
import com.ardor3d.util.ReadOnlyTimer;
import com.ardor3d.util.TextureManager;
import com.ardor3d.util.Timer;
import com.ardor3d.util.geom.BufferUtils;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

/**
 *
 * Based on Andreas Hauffe example
 */
public class DisplayA3D implements Scene, Updater {

    private final Node root;
    private final Node transform;
    private final Node branch;
    
    //private final JoglSwingCanvas canvas;
    private final JoglAwtCanvas canvas;
    //private final JoglNewtAwtCanvas canvas;
    
    private final Timer timer = new Timer();
    public final FrameHandler frameWork = new FrameHandler(timer);
    private final LogicalLayer logicalLayer = new LogicalLayer();
    private volatile boolean exit = false;
    private final AwtMouseManager mouseManager;
    private final PhysicalLayer pl;
    private PointLight light;

    private LightState lightState;

    private final Mesh targetMesh = new Teapot("target");

    private MouseControl control;
    
    Box box;
    Quad quad;
    
    boolean firstDraw = false;
    
    public boolean frameHandlerInitialized = false;
    
    private MyRunner myRunner;
    
    
    public DisplayA3D() {
        System.setProperty("ardor3d.useMultipleContexts", "true");
        
        root = new Node("root");
        transform = new Node("transform");
        branch = new Node("branch1");
        
        final JoglCanvasRenderer canvasRenderer = new JoglCanvasRenderer(this);
        final DisplaySettings settings = new DisplaySettings(400, 400, 24, 0, 0, 16, 0, 0, false, false);
        
        
        //canvas = new JoglSwingCanvas(settings, canvasRenderer);
        canvas = new JoglAwtCanvas(settings, canvasRenderer);
        //canvas = new JoglNewtAwtCanvas(settings, canvasRenderer);
        
        canvas.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                super.componentResized(e);
                resizeCanvas();
            }
        });

        mouseManager = new AwtMouseManager(canvas);
        pl = new PhysicalLayer(new AwtKeyboardWrapper(canvas),
                new AwtMouseWrapper(canvas, mouseManager),
                DummyControllerWrapper.INSTANCE,
                new AwtFocusWrapper(canvas));

        logicalLayer.registerInput(canvas, pl);
        

        frameWork.addUpdater(this);
        frameWork.addCanvas(canvas);
        
        start();
    }
    
    public void start() {
       myRunner = new MyRunner(this);
    }
    
    @Override
    public boolean renderUnto(Renderer renderer) {
           root.draw(renderer);
           return true;
    }    

    @Override
    public void update(ReadOnlyTimer rot) {
        logicalLayer.checkTriggers(rot.getTimePerFrame());
        root.updateGeometricState(rot.getTimePerFrame(), true);
    }
    
    @Override
    public PickResults doPick(Ray3 ray3) {
        return null;
    }    
    
    
    public void init() {
       
        /**
         * Create a ZBuffer to display pixels closest to the camera above
         * farther ones.
         */
        final ZBufferState buf = new ZBufferState();
        buf.setEnabled(true);
        buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
        root.setRenderState(buf);

        // ---- LIGHTS
        /**
         * Set up a basic, default light.
         */
        light = new PointLight();

        final Random random = new Random();

        final float r = random.nextFloat();
        final float g = random.nextFloat();
        final float b = random.nextFloat();
        final float a = random.nextFloat();

        light.setDiffuse(new ColorRGBA(0.8f, 0.2f, 0.8f, 1.0f));
        light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
        light.setLocation(new Vector3(100, 100, 100));
        light.setEnabled(true);

        /**
         * Attach the light to a lightState and the lightState to rootNode.
         */
        lightState = new LightState();
        lightState.setEnabled(true);
        lightState.attach(light);
        //root.setRenderState(lightState);

        root.getSceneHints().setRenderBucketType(RenderBucketType.Opaque);

        control = new MouseControl(transform);
        control.setupMouseTriggers(logicalLayer);

        // setup some basics on the teapot.
        targetMesh.setModelBound(new BoundingBox());
        Vector3 transCent = targetMesh.getModelBound().getCenter().clone();
        transCent.multiplyLocal(-1);
        targetMesh.getMeshData().translatePoints(transCent);
        
        
//        MeshData meshData = new MeshData();
//        meshData.setIndexMode(IndexMode.Lines);
//        final float[] verts = new float[] {0f, 0f, 0f, 0f, 1f, 0f, 1f, 1f, 0f, 1f, 0f, 0f, 0f, 0f, 0f};
//        meshData.setVertexBuffer(BufferUtils.createFloatBuffer(verts));
//        //targetMesh.setMeshData(meshData);
        
        targetMesh.setRenderState(lightState);
        transform.attachChild(targetMesh);
        

        // Make a box...
        box = new Box("Box", Vector3.ZERO, 12, 12, 0);
        quad = new Quad("test", 12f, 12f);
        quad.setDefaultColor(new ColorRGBA(0.4f, 0.6f, 0.6f, 1.0f));

        // Setup a bounding box for it.
        //box.setModelBound(new BoundingBox());

        // Set its location in space.
        box.setTranslation(new Vector3(0, 0, -2));
        branch.attachChild(quad);
        
        transform.attachChild(branch);
        root.attachChild(transform);

        root.updateGeometricState(0);
        
    }
    
    private void resizeCanvas() {
    //private static void resizeCanvas(JoglSwingCanvas canvas) {
        int w = canvas.getWidth();
        int h = canvas.getHeight();
        double r = (double) w / (double) h;

        Camera cam = canvas.getCanvasRenderer().getCamera();
        if (null != cam) {
            cam.resize(w, h);

            cam.setFrustumPerspective(cam.getFovY(), r, cam.getFrustumNear(),
                    cam.getFrustumFar());
        }
    }

    public void addImage(java.awt.image.BufferedImage awtImage) {
        java.awt.image.Raster raster = awtImage.getData();
        DataBuffer db = raster.getDataBuffer();
        byte[] byteData = ((DataBufferByte)db).getData();  
        int width = raster.getWidth();
        int height = raster.getHeight();
        
        ByteBuffer bbuf = ByteBuffer.wrap(byteData);
        Image aImage = new Image(ImageDataFormat.BGR, PixelDataType.UnsignedByte, width, height, bbuf, null);
       
        final TextureState ts = new TextureState();
        ts.setEnabled(true);
        
        Texture texture = TextureManager.loadFromImage(aImage, Texture.MinificationFilter.Trilinear);
        ts.setTexture(texture);
        
        branch.setRenderState(ts);
    }
    
    public static DisplayA3D createDisplay() {
       DisplayInit dspInitializer = new DisplayInit();
       
       if (!SwingUtilities.isEventDispatchThread()) {
          try {
             SwingUtilities.invokeAndWait(dspInitializer);
          }
          catch (Exception e) {
             e.printStackTrace();
          }
       }
       else {
          dspInitializer.run();
       }
       
       return dspInitializer.getTheDisplay();
    }  
    
    public Component getComponent() {
       return canvas;
    }    
    
    
    private static void createAndShowGUI() {
       JFrame frame = new JFrame();
       frame.setPreferredSize(new Dimension(500, 500));
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       
       final JComponent outerComp = new JPanel(new BorderLayout());
       JPanel cntrlPanel = new JPanel(new FlowLayout());
       
       JButton button = new JButton("New JFrame");
       button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                   JFrame frame = new JFrame();
                   frame.setPreferredSize(new Dimension(500,500));
                   frame.pack();
                   frame.setVisible(true);
               
                   DisplayA3D dspA3d = DisplayA3D.createDisplay();
                   Component comp = dspA3d.getComponent();
                   frame.getContentPane().add(comp);  
                   //dspA3d.start();
            }
        });
        cntrlPanel.add(button);
       
       
       JPanel panel = new JPanel();
       panel.setMinimumSize(new Dimension(400,400));
       
       
       
       outerComp.add(panel, BorderLayout.CENTER);
       outerComp.add(cntrlPanel, BorderLayout.SOUTH);
       frame.getContentPane().add(outerComp);       
       
       

       //Display the window.
       frame.pack();
       frame.setVisible(true);
       
    }
    
    public static void main(String[] args) {
         SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                //Turn off metal's use of bold fonts
                UIManager.put("swing.boldMetal", Boolean.FALSE);
                createAndShowGUI();
            }
        });      
    }
}

class DisplayInit implements Runnable {
   
      private DisplayA3D display;

      @Override
      public void run() {
         display = new DisplayA3D();
      }
      
      DisplayA3D getTheDisplay() {
         return display;
      }
}

class MyScene implements Scene {
   
   private final Node root;
   private final Node transform;
   private final Node branch;
    
   public MyScene() {
      root = new Node();
      transform = new Node();
      branch = new Node();
   }

   @Override
   public boolean renderUnto(Renderer renderer) {
      throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
   }

   @Override
   public PickResults doPick(Ray3 pickRay) {
      return null;
   }

}

class MyRunner implements Runnable {
   DisplayA3D display;
   private Thread thread;
   
   public MyRunner(DisplayA3D display) {
      this.display = display;
      thread = new Thread(this);
      thread.start();
   }
   
   public void run() {
        display.frameWork.init();
        while (true) {
           display.frameWork.updateFrame();
           delay(20);
        }
   }
   
   private void delay(long millis) {
      try {
         java.lang.Thread.sleep(millis);
      }
      catch (Exception e) {
         e.printStackTrace();
      }      
   }
}