
import java.awt.Frame;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import java.awt.GraphicsEnvironment;
import java.awt.Font;
import java.awt.Shape;
import java.awt.Rectangle;
import java.awt.geom.Area;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.text.StringCharacterIterator;

import com.hermetica.magician.GL;
import com.hermetica.magician.CoreGL;
import com.hermetica.magician.CoreGLU;
import com.hermetica.magician.GLDrawable;
import com.hermetica.magician.GLEventListener;
import com.hermetica.magician.GLU;
import com.hermetica.magician.GLComponent;
import com.hermetica.magician.GLComponentFactory;
import com.hermetica.magician.GLCapabilities;
import com.hermetica.magician.GLUQuadric;
import com.hermetica.util3d.shapes;

import com.hermetica.vecmath.Vec3f;

import net.hblok.opengl.shape.Box;
import net.hblok.javai.FlowController;

public class FontTest3 extends Frame implements GLEventListener
{
    private final double sqrt2 = Math.sqrt( 2.0 );
  private final double sqrt2_div2 = sqrt2/2;
  private final float sqrt2_div2f = (float)sqrt2_div2;
  
  // The OpenGL state machine
  private GL gl;
  private GLU glu;
  
  // The OpenGL component to render onto
  private static GLComponent glc;
  private int width = 900;
  private int height = 500;
  
  private MouseHandler mouseHandler;
  
  //mouse move variables
  private int lastx;
  private int lasty;
  private float rotateX = 180.0f;
  private float rotateY;
  private boolean mouseMoving;
  
  //key move variables
  private float transX;
  private float transY;
  private float transZ;
  
  private float eyeX = 5.0f;
  private float eyeY = -15.5f;
  private float eyeZ = 20.3f;
  
  private float centerX = 0.0f;
  private float centerY = 0.1f;
  private float centerZ = -0.2f;
  
  private float rotX;
  private float rotY;
  private float rotZ;
  
  
  //private DrawBox db;
  
  private FlowController fc;


/******************************************************/
  public static void main( String argv[] )
 {
    //System.out.println("PathIterator.WIND_EVEN_ODD="+PathIterator.WIND_EVEN_ODD);
    //System.out.println("PathIterator.WIND_NON_ZERO="+PathIterator.WIND_NON_ZERO);
  
   FontTest3 k = new FontTest3();
   //k.startDemo();
  }
/******************************************************/

  public FontTest3()
  {
  
    gl = new CoreGL(); 
    glu = new CoreGLU();
    
    // Prepare drawing environment
    setLayout( new BorderLayout() );
    glc = GLComponentFactory.createGLComponent( getWidth(), getHeight() );
    add( "Center", glc );
    pack();
    show();
    
    fc = new FlowController();
    fc.addGlobalVariable( 0, "glc", glc, glc.getClass() );
    fc.addThis( 1, this, this.getClass() );
    fc.addGlobalVariable( 1, "gl", gl, gl.getClass() );
    fc.addGlobalVariable( 1, "glu", glu, glu.getClass() );
    
    
    // Setup the context capabilities */
    GLCapabilities cap = glc.getContext().getCapabilities();
    cap.setDepthBits( 12 );
    cap.setPixelType( GLCapabilities.RGBA );
    cap.setColourBits( 24 );
    cap.setRedBits( 1 );
    cap.setDoubleBuffered( GLCapabilities.DOUBLEBUFFER );
    
    // Register the GLComponentListener with the GLComponent
    glc.addGLEventListener( this );

    mouseHandler = new MouseHandler();
    glc.addMouseListener(mouseHandler);
    glc.addMouseMotionListener(mouseHandler);
    
    glc.addKeyListener( new KeyHandler() );
    
    //vc = new ViewController( this, glc );
    addWindowListener( new CloseWindowAndExit() );
    
    //make box obj.
    //db = new DrawBox( gl, glu );
    
    // Initialize the component
    glc.initialize();
    
    //glc.setSleepDuration( 450 );
    //glc.start();
    
  }
  
  // Returns the width and height of the demo */
  public int getWidth()
  {
    return width;
  }
  public int getHeight()
  {
    return height;
  }
  
  
  public void initialize( GLDrawable component )
  {
  
    // Here we set the attributes that will be constant
    // during the rendering contexts lifetime
    gl.glEnable(GL.GL_DEPTH_TEST);
    //gl.glDepthFunc(GL.GL_LESS);
    
    // prepare ligthsource
    float ambient[] = {0.2f,0.2f,0.3f,1.0f };
    float diffuse[] = {1.0f,1.0f,1.0f,1.0f };
    float position[] = {1.0f,1.0f,1.0f,0.0f };
    float lmodel_ambient[] = {0.4f,0.4f,0.4f,1.0f };
    
    gl.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, ambient);
    //gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, diffuse);
    gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, position);
    
    gl.glEnable(GL.GL_LIGHT0);
    gl.glEnable(GL.GL_LIGHTING);
    
    //gl.glEnable(GL.GL_COLOR_MATERIAL);
    
    // smooth the drawing
    gl.glShadeModel(GL.GL_SMOOTH);
    gl.glEnable(GL.GL_NORMALIZE);
    
    // set background to white
    gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    
  }

  // Handles viewport resizing
  public void reshape( GLDrawable component, int x, int y, int width, int height )
  {
    // Setup the viewport
    
    gl.glViewport( component, 0, 0, width, height );
    gl.glMatrixMode( GL.GL_PROJECTION );
    gl.glLoadIdentity();
    glu.gluPerspective( 45.0, (float) width/height, 0.015, 50.0 );
    gl.glMatrixMode( GL.GL_MODELVIEW );
  
  }

  // produce the image
  public void display( GLDrawable component )
  {
    System.out.println("FontTest3.display - start");
  
    // Clear the color and depth buffers.
     gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
      
    // Set up for scene
    gl.glMatrixMode(GL.GL_MODELVIEW);
    gl.glLoadIdentity();
    
    gl.glRotatef( rotY, 0.0f, 1.0f, 0.0f );
    
    // position the eye relative to scene
    glu.gluLookAt(	eyeX, eyeY, eyeZ,  // eye
              centerX, centerY, centerZ,   // looking at
              0.0f, 1.0f, 0.0f);   // is up

    //rotate for mouse move
    gl.glRotatef( rotateX, 1.0f, 0.0f, 0.0f );
    gl.glRotatef( rotateY, 0.0f, 1.0f, 0.0f );

    //transelate for key moves
    gl.glTranslatef( transX, transY, transZ );
    
    //drawChar( new Font( "Serif", 0, 14 ), 'S' );
    fillChar( new Font( "Serif", 0, 14 ), 'S' );
    
    gl.glTranslatef( -15.0f, 0.0f, 0.0f );
    
    fillChar( new Font( "Serif", 0, 14 ), 'a' );
    
    //drawChar( new Font( "Serif", 0, 14 ), 'A' );

    //fc.launchPoint( 1 );
  }
  
  
  private void drawChar( Font font, char c )
  {
    PathIterator pathItr[];
    float coord[];
    int type;
    float cp[][];
    float step = 0.5f;
    
    float colorCount = 0;
    
    pathItr = getGlyph( font, c );
    cp = new float[4][2];
    
    for(int i = 0; i < pathItr.length; i++)
    {
      do
      {
        coord = new float[6];
        type = pathItr[i].currentSegment( coord );
        
        switch( type )
        {
          case PathIterator.SEG_CLOSE:
            //cp = new float[4][2];
            break;
          case PathIterator.SEG_CUBICTO:
            cp[1][0] = coord[0];
            cp[1][1] = coord[1];
            cp[2][0] = coord[2];
            cp[2][1] = coord[3];
            cp[3][0] = coord[4];
            cp[3][1] = coord[5];
            drawBezier( cp, step );
            cp[0][0] = coord[4];
            cp[0][1] = coord[5];
            break;
          case PathIterator.SEG_LINETO:
            cp[1][0] = coord[0];
            cp[1][1] = coord[1];
            drawLine( cp );
            cp[0][0] = coord[0];
            cp[0][1] = coord[1];
            break;
          case PathIterator.SEG_MOVETO:
            //cp = new float[4][2];
            colorCount = 0;
            cp[0][0] = coord[0];
            cp[0][1] = coord[1];
            break;
          case PathIterator.SEG_QUADTO:
            cp[1][0] = coord[0];
            cp[1][1] = coord[1];
            cp[2][0] = coord[2];
            cp[2][1] = coord[3];
            drawCurve( cp, step );
            cp[0][0] = coord[2];
            cp[0][1] = coord[3];
            break;
          
        }
        /*
        gl.glColor3f( colorCount, colorCount, colorCount );
        colorCount += 0.08f;
        
        System.out.println("FontTest3.drawChar i="+i+", colorCount="+colorCount+", pathItr[i].getWindingRule="+pathItr[i].getWindingRule());
        */
        pathItr[i].next();
        
      }while( !pathItr[i].isDone() );
    }
    
    //fillChar( font, c );
  }
  
  private void fillChar( Font font, char c )
  {
    Shape charShapes[];
    
    charShapes = getShape( font, c );
    
    for(int i = 0; i < charShapes.length; i++)
    {
      fill3dShape( charShapes[i] );
    }
  }

  private void fill3dShape( Shape s )
  {
    Area a;
    Rectangle bounds;
    double step = 0.3;
    double depth = 0.8;
    boolean wireFrame = false;
    
    a = new Area( s );
    bounds = s.getBounds();
    //System.out.println("FontTest3.fill3dShape - isSingular: "+a.isSingular());

    for(double y = bounds.getY(); y < bounds.getY()+bounds.getHeight(); y += step)
    {
      for(double x = bounds.getX(); x < bounds.getX()+bounds.getWidth(); x += step)
      {
        if( a.contains( x, y ) )
        {
          //System.out.println("FontTest3.fill3dShape - x="+x+", y="+y);
        
          gl.glPushMatrix();
    
            gl.glTranslatef( (float)(x+step/2.0f), (float)(y+step/2.0f), -(float)(depth/2.0) );
            gl.glScalef( (float)(step*1.05), (float)(step*1.05), (float)depth );
      
            if(wireFrame)
            {
              shapes.wireCube(1.0);
            }
            else
            {
              shapes.solidCube(1.0);
            }
          
          gl.glPopMatrix();
        }
      }

    }
    
  }

  private void fillShape( Shape s )
  {
    Area a;
    Rectangle bounds;
    double step = 0.25;
    boolean drawing;
    
    a = new Area( s );
    bounds = s.getBounds();
    drawing = false;
    System.out.println("FontTest3.fillShape - isSingular: "+a.isSingular());

    for(double y = bounds.getY(); y < bounds.getY()+bounds.getHeight(); y += step)
    {
      for(double x = bounds.getX(); x < bounds.getX()+bounds.getWidth(); x += step)
      {
        if( a.contains( x, y ) )
        {
          if( !drawing )
          {
            gl.glBegin( GL.GL_QUAD_STRIP );
            drawing = true;
          }
        
          gl.glVertex3f( (float)x, (float)y, 0.0f );
          gl.glVertex3f( (float)x, (float)(y+step), 0.0f );
        }
        else if( drawing )
        {
          gl.glEnd();
          drawing = false;
        }
      }

    }
    
  }

  
/*  
  private void fillShape( Shape s )
  {
    Area a;
    Rectangle bounds;
    double step = 0.1;
    
    a = new Area( s );
    bounds = s.getBounds();
    System.out.println("FontTest3.fillShape - isSingular: "+a.isSingular());

    gl.glBegin( GL.GL_POINTS );
    for(double y = bounds.getY(); y < bounds.getY()+bounds.getHeight(); y += step)
    {
      for(double x = bounds.getX(); x < bounds.getX()+bounds.getWidth(); x += step)
      {
        if( a.contains( x, y ) )
        {
          gl.glVertex3f( (float)x, (float)y, 0.0f );
        }
      }

    }
    gl.glEnd();

    
  }
  */
  /**
  * Returns an array of PathIterator(s) representing a the glyph(s) of
  * a single character.
  * @param font the font to render
  * @param c the char to render
  * @return array of PathIterator(s)
  */
  private PathIterator[] getGlyph( Font font, char c )
  {
    Shape shapes[];
    PathIterator ans[];

    shapes = getShape( font, c );
    ans = new PathIterator[shapes.length];
    
    for(int i = 0; i < ans.length; i++)
    {
      ans[i] = shapes[i].getPathIterator( null );
      //System.out.println("FontT shapes[i].getBounds() = "+shape.getBounds());
    }

    return ans;
  }
  
  private Shape[] getShape( Font font, char c )
  {
    AffineTransform at;
    FontRenderContext renderCon;
    StringCharacterIterator itr;
    GlyphVector singleVec;
    Shape ans[];

    at = new AffineTransform();
    renderCon = new FontRenderContext( at, false, true );
    itr = new StringCharacterIterator( ""+c );
    
    singleVec = font.createGlyphVector( renderCon, itr );
    ans = new Shape[singleVec.getNumGlyphs()];
    
    for(int i = 0; i < ans.length; i++)
    {
      ans[i] = singleVec.getGlyphOutline( i );
    }

    return ans;
  }
  
  private void drawLine( float cp[][] )
  {
    gl.glBegin( GL.GL_LINE_STRIP );
      gl.glVertex3fv( cp[0] );
      gl.glVertex3fv( cp[1] );    
    gl.glEnd();
  }
  
  private void drawCurve( float cp[][], float step )
  {
  /*
   *    P(t) = B(2,0)*CP + B(2,1)*P1 + B(2,2)*P2
   *      0 <= t <= 1
   *
   *    B(n,m) = mth coefficient of nth degree Bernstein polynomial
   *           = C(n,m) * t^(m) * (1 - t)^(n-m)
   *    C(n,m) = Combinations of n things, taken m at a time
   *           = n! / (m! * (n-m)!)
   */

    float point[];
    
    point = new float[3];
  
    gl.glBegin( GL.GL_LINE_STRIP );
      for(float t = 0; t <= 1.0f; t += step)
      {
        point[0] = B(t, 2, 0)*cp[0][0] + B(t, 2, 1)*cp[1][0] + B(t, 2, 2)*cp[2][0];
        point[1] = B(t, 2, 0)*cp[0][1] + B(t, 2, 1)*cp[1][1] + B(t, 2, 2)*cp[2][1];
        point[2] = 0;
      
        gl.glVertex3fv( point );
      }
    gl.glEnd();
  }
  
  private void drawBezier( float cp[][], float step )
  {
  /*
   *    P(t) = B(3,0)*CP + B(3,1)*P1 + B(3,2)*P2 + B(3,3)*P3
   *      0 <= t <= 1
   *
   *    B(n,m) = mth coefficient of nth degree Bernstein polynomial
   *           = C(n,m) * t^(m) * (1 - t)^(n-m)
   *    C(n,m) = Combinations of n things, taken m at a time
   *           = n! / (m! * (n-m)!)
   */
    float point[];
    
    point = new float[3];
  
    gl.glBegin( GL.GL_LINE_STRIP );
      for(float t = 0; t <= 1.0f; t += step)
      {
        point[0] = B(t, 3, 0)*cp[0][0] + B(t, 3, 1)*cp[1][0]
                 + B(t, 3, 2)*cp[2][0] + B(t, 3, 3)*cp[3][0];
        point[1] = B(t, 3, 0)*cp[0][1] + B(t, 3, 1)*cp[1][1]
                 + B(t, 3, 2)*cp[2][1] + B(t, 3, 3)*cp[3][1];
        point[2] = 0;
      
        gl.glVertex3fv( point );
      }
    gl.glEnd(); 
    
  }
  
  private float B( float t, int n, int m )
  {
    return (float)((float)C(n, m) * Math.pow(t, m) * Math.pow(1-t, n-m));
  }
  
  private int C( int n, int m )
  {
    return fac(n) / (fac(m) * fac(n-m) );
  }
  
  private int fac( int n )
  {
    int ans = 1;
  
    for(int i = 1; i <= n; i++)
    {
      ans *= i;
    }
    
    return ans;
  }
  

  // Returns a GL pipeline from the listener
  public GL getGL()
  {
    return gl;
  }
  
  //inner class to handle mouse events
  class MouseHandler implements MouseListener, MouseMotionListener
  {
    /** methods required for MouseListener and MouseMotionListener interfaces */
    public void mouseReleased(MouseEvent e)
    {
      /** object is not moving anymore */
      mouseMoving=false;
      /** repainting the complete tree */
      glc.repaint();
    }
    
    public void mouseEntered(MouseEvent e) {}
    public void mouseExited(MouseEvent e) {}
    public void mouseMoved(MouseEvent e) {}
    public void mouseClicked(MouseEvent e) {}
    
    public void mousePressed(MouseEvent e)
    {
      /** record mouse down coordinates */
      lastx = e.getX();
      lasty = e.getY();
      mouseMoving=true;
    }
    
  
    public void mouseDragged(MouseEvent e)
    {
      /** create a rotation quaternion that tracks the mouse movement */	
      int x = e.getX();
      int y = e.getY();
      //Dimension dim = e.getComponent().getSize();
      int width = getWidth();
      int height = getHeight();
  
      
      //find angles to rotate
      //when dragging mouse along the X-axis of the screen,
      //it makes sense to rotate around the Y-axis of the module (at least in this example)
      //rotateY += (180.0f*((float)(x-lastx)/(float)(width)));
      rotateX += (-180.0f*((float)(y-lasty)/(float)(height)));
      
      lastx = x;
      lasty = y;
      
      glc.repaint();
    }
  }

  //inner class to handle keyevents
  class KeyHandler extends KeyAdapter
  {
  
    private boolean shift;

    public void keyPressed(KeyEvent e)
    {
      int code = e.getKeyCode();
      String str = KeyEvent.getKeyText( code );
      String mod = KeyEvent.getKeyModifiersText( code );
      
      float delta = 0.2f;
      
      if( str.indexOf( "Shift" ) != -1 )
      {
        shift = true;
      }
      
      System.out.println("TestBody.keyPressed - code="+code+" - getKeyText:" + str+ "mod:"+mod);

      if( str.compareTo("Up") == 0  && shift)
      {
        eyeY += delta;
        centerY += delta;
      }
      else if( str.compareTo("Down") == 0  && shift)
      {
        eyeY -= delta;
        centerY -= delta;
      }
      else if( str.compareTo("Up") == 0 )
      {
        eyeZ -= delta;
        centerZ += delta;
      }
      else if( str.compareTo("Down") == 0 )
      {
        eyeZ += delta;
        centerZ -= delta;
      }
      else if( str.compareTo("Page Up") == 0 )
      {
        eyeZ -= delta;
        //centerZ += 0.01f;
      }
      else if( str.compareTo("Page Down") == 0 )
      {
        eyeZ += delta;
        //centerZ -= 0.01f;
      }
      else if( str.compareTo("Left") == 0 && shift)
      {
        eyeX -= delta;
        centerX -= delta;
      }
      else if( str.compareTo("Right") == 0  && shift)
      {
        eyeX += delta;
        centerX += delta;
      }
      else if( str.compareTo("Left") == 0 )
      {
        double angle = -2.0*Math.PI/180.0;
      
        //rotY -= 5;
        float x1 = centerX - eyeX;
        float y1 = centerZ - eyeZ;
        
        float x2 = x1 * (float)Math.cos( angle ) - y1 * (float)Math.sin( angle );
        float y2 = y1 * (float)Math.cos( angle ) - x1 * (float)Math.sin( angle );
        
        centerX = x2 + eyeX;
        centerZ = y2 + eyeZ;
      }
      else if( str.compareTo("Right") == 0 )
      {
        double angle = 2.0*Math.PI/180.0;
      
        System.out.println("Right: cX="+centerX+" cZ="+centerZ);
        
        //rotY -= 5;
        float x1 = centerX - eyeX;
        float y1 = centerZ - eyeZ;
        
        float x2 = x1 * (float)Math.cos( angle ) - y1 * (float)Math.sin( angle );
        float y2 = y1 * (float)Math.cos( angle ) - x1 * (float)Math.sin( angle );
        
        centerX = x2 + eyeX;
        centerZ = y2 + eyeZ;
        
        System.out.println("Right: cX="+centerX+" cZ="+centerZ);

      }
      
      glc.repaint();
    }
    
    public void keyReleased(KeyEvent e)
    {
      int code = e.getKeyCode();
      String str = KeyEvent.getKeyText( code );
      String mod = KeyEvent.getKeyModifiersText( code );
      
      if( str.indexOf( "Shift" ) != -1 )
      {
        shift = false;
      }
      
      
      System.out.println("FontTest3.keyReleased - code="+code+" - getKeyText:" + str+ "mod:"+mod);

    }


      

  }

  //inner class to handle closeing window
  
  class CloseWindowAndExit extends WindowAdapter
  {
    public void windowDeiconified( WindowEvent evt )
    {
      if ( glc.isRunning() )
      {
        glc.resume();
      }
    }
    
    public void windowIconified( WindowEvent evt )
    {
      if ( glc.isRunning() )
      {
        glc.suspend();
      }
    }
  
    public void windowClosing( WindowEvent e )
    {
      glc.destroy();
      dispose();
      System.exit( 0 );
    }
  }
}


