package programaprincipal;

import javax.media.j3d.Canvas3D;
import javax.media.j3d.Switch;
import javax.media.j3d.GraphicsContext3D;
import javax.media.j3d.Texture;
import javax.media.j3d.Texture2D;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.ImageComponent;
import javax.media.j3d.Appearance;
import javax.media.j3d.TransparencyAttributes;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TextureAttributes;

import javax.vecmath.Vector3d;

import javax.imageio.ImageIO;

import java.io.File;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.image.PixelGrabber;
import java.awt.image.BufferedImage;

import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.geometry.Primitive;

import java.net.URL;

import com.sun.image.codec.jpeg.*;
import java.io.FileOutputStream;
import java.io.BufferedOutputStream;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;


/**
 * Classe responsavel pelo integração do video avatar dentro do ambiente
 * virtual 3D.
 */
public class MyCanvas3D extends Canvas3D implements KeyListener
{
  /** Cria os planos onde serão mapeadas as imagens do apresentador */
  private Box MC3D_plano_esq, MC3D_plano_dir;
  /** GraphicsContext3D object is used for immediate mode rendering
   * into a 3D canvas. */
  GraphicsContext3D MC3D_gc;
  /** Objeto da classe BufferedImage */
  BufferedImage MC3D_buf = null;
  /** Objeto da classe ImageComponent2D */
  ImageComponent2D MC3D_ImageComp2D_esq = null;
  /** Objeto da classe ImageComponent2D */
  ImageComponent2D MC3D_ImageComp2D_dir = null;
  /** Objeto da classe Texture2D */
  Texture2D MC3D_texture_esq = null;
  /** Objeto da classe Texture2D */
  Texture2D MC3D_texture_dir = null;
  /** Objeto da classe Image */
  Image MC3D_imagens[] = new Image[2];
  /** Objeto da Classe CaptureDeviceDialog*/
  CaptureDeviceDialog MC3D_CapDevDialog = null;
  /** Array de inteiros referente a cada pixels*/
  int[] MC3D_pixels = null;
  /** Retira um subconjunto de pixels da imagem. */
  PixelGrabber MC3D_pixelgrabber = null;
  /** Array de inteiros que armazena a imagem esquerda  */
  int MC3D_imageInt_esq[] = null;
  /** Array de inteiros que armazena a imagem direita  */
  int MC3D_imageInt_dir[] = null;
  /** variavel booleana que controla quando há mudança no tamanho da imagem*/
  boolean MC3D_novoTamImagem = true;

  //float MC3D_larguraPlano = 0.6f;
  //float MC3D_alturaPlano = 0.7f;

//  float MC3D_larguraPlano = 0.176f; //0.3f;
//  float MC3D_alturaPlano =  0.144f; // 0.4f;

//  float MC3D_larguraPlano = 0.493f; //0.3f;
//  float MC3D_alturaPlano =  0.373f; // 0.4f;

  float MC3D_larguraPlano = 0.453f; //0.3f;
  float MC3D_alturaPlano =  0.295f; // 0.4f;

  float MC3D_profundidadePlano = 0.0f;

  boolean MC3D_OqueRenderizar = true;
  boolean MC3D_posCameraConf = false;

  Switch MC3D_sw_box = null;

  MenuPrincipal MC3D_menuprincipal = null;

  int posImgEsq = 0;
  int posImgDir = 1;

  boolean ImgEstaticas = true;

  boolean naoTemImagens = true;

  boolean enviar = true;

  Transform3D t3d = null;

  TransformGroup tg =  null;

  TextureAttributes texturaAtributos = null;

  Transform3D paralaxeImgEsq = new Transform3D();
  Transform3D paralaxeImgDir = new Transform3D();

  Appearance appear_esq = null;
  Appearance appear_dir = null;
  /**
   * Contrutor da classe MyCanvas3D
   * <p> - Funções: </p>
   * <p> - Recebe o GraphicsConfiguration da classe Ambiente Virtual </p>
   * <p> - Pega o modo Immediate GraphicsContext 3D  associado com o Canvas3D. </p>
   * <p> - Cria plano esquerdo e direito</p>
   * @param g
   */
  public MyCanvas3D (GraphicsConfiguration g,
                     CaptureDeviceDialog cdDialog,
                     MenuPrincipal mp)
  {
    super(g);
    MC3D_gc = getGraphicsContext3D();

    MC3D_menuprincipal = mp;

    MC3D_gc.getCanvas3D().addKeyListener(this);

    MC3D_CapDevDialog = cdDialog;
    MC3D_plano_esq = createBox(1); // plano esquerda
    MC3D_plano_dir = createBox(2); // plano direita

    tg =  new TransformGroup();
    tg.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
    tg.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

    t3d = new Transform3D();
    tg.setTransform(t3d);
    tg.addChild(MC3D_plano_esq);
    tg.addChild(MC3D_plano_dir);

    MC3D_gc.setModelTransform(t3d);
  }

  /**
   * Metodo responsavel por realizar a apresentaçao das imagens do video avatar
   * durante a renderização do ambiente virtual, utilizando o GraphicsContext3D.
   * Este metodo é chamado pelo loop de renderizaçao do Java 3D durante
   * a execução do loop de renderização.
   * @param fieldDesc: int
   */
  public void renderField(int fieldDesc)
  {
    super.renderField(fieldDesc);

   // MC3D_gc = getGraphicsContext3D();

    if (MC3D_OqueRenderizar)
    {
      switch (fieldDesc) {
        case Canvas3D.FIELD_LEFT: // 0
          MC3D_gc.draw(MC3D_plano_esq.getShape(0));
          break;
        case Canvas3D.FIELD_RIGHT: // 1
          MC3D_gc.draw(MC3D_plano_dir.getShape(0));
          AtualizaTextura();
          break;
        case Canvas3D.FIELD_ALL: // modo Mono (funciona OK): 2
          MC3D_gc.draw(MC3D_plano_esq.getShape(0));
          AtualizaTextura();
          break;
      }
    }
//         repaint();
    }

    /**
   * Este metodo é chamado pelo loop renderizador do Java 3D rendering loop depois
   * de completar toda renderizaçao do canvas para o frame e antes do buffer swap.
   */
  public void postRender()
  {
    getView().repaint();
//    MC3D_gc.flush(true);
  }


  /**
   * Metodo cria os Planos esquerdo e direito
   * @param app: appearence
   * @return: Box
   */
  private Box createBox(int qual)
  {
    Appearance ap = null;
    if (qual==1)
      ap = buildAppearance_Esq();
    else
      ap = buildAppearance_Esq();

    // largura, altura
    Box plano = new Box (MC3D_larguraPlano,
                         MC3D_alturaPlano,
                         MC3D_profundidadePlano,
                         Primitive.GENERATE_NORMALS  |
                         Primitive.GENERATE_TEXTURE_COORDS,
                         ap );
    return plano;
  }

  /**
   * metodo utilizado para atualizar a textura quando as imagens são atualizadas
   */
  private void AtualizaTextura()
  {

    if (!MC3D_CapDevDialog.CDD_menuPrincipal.getjCheckBoxMenuEntradaParCameras().isSelected())
    {
      // refaz os planos para retirar a imagem que ficou, tornado-os transparente
      if (ImgEstaticas)
      {
//        Appearance appear_esq = MC3D_plano_esq.getAppearance();
//        appear_esq.setTexture(null);

        if (MC3D_imagens == null)
          MC3D_imagens = new Image[2];

        try {
//          MC3D_imagens[posImgEsq] = ImageIO.read(new File("D:/robson/Doutorado/Tese/Implementação/Projeto/Fotos-Testes/Maquina-Braga/img1-centro-limpa.jpg"));
          MC3D_imagens[posImgEsq] = ImageIO.read(new File("D:/robson/Doutorado/Tese/Implementação/Projeto/img-esq-limpa.jpg"));
//          MC3D_imagens[posImgEsq] = ImageIO.read(new File("D:/usuários/robson/Doutorado/Tese/Implementação/Projeto/img-esq-limpa.jpg"));

//          MC3D_imagens[posImgDir] = ImageIO.read(new File("D:/robson/Doutorado/Tese/Implementação/Projeto/Fotos-Testes/Maquina-Braga/img1-centro-limpa.jpg"));
          MC3D_imagens[posImgDir] = ImageIO.read(new File("D:/robson/Doutorado/Tese/Implementação/Projeto/img-dir-limpa.jpg"));
//          MC3D_imagens[posImgDir] = ImageIO.read(new File("D:/usuários/robson/Doutorado/Tese/Implementação/Projeto/img-dir-limpa.jpg"));
        }
        catch (Exception e)
        {
          e.printStackTrace();

        }

        // Textura Direita para null
//        Appearance appear_dir = MC3D_plano_dir.getAppearance();
//       appear_dir.setTexture(null);
        ImgEstaticas = false;
        enviar = true;
      }
    }
    else
    {

        MC3D_imagens = MC3D_CapDevDialog.getImages();
        if (MC3D_imagens == null)
        {
          ImgEstaticas = true;
           return;
        }
        enviar = true;
    }
    // esta invertido devido a restrição no processo de escolha das cameras
    // tornando a vizualizaçao invertida
    // deveria ser: img_esq = CapDevDialog.getImage_Esq();
    // deveria ser: img_dir = CapDevDialog.getImage_Dir();

    // img_esq = CapDevDialog.getImage_Dir(); // imagem esquerda
    // img_dir = CapDevDialog.getImage_Esq(); // imagem direita
    // if (img_esq !== null)
    if (MC3D_imagens != null)
    {
      if (MC3D_imagens[posImgEsq] != null) // devices não registrados ou menuparcameras desabilitado
      {
        // Aplica chromakey na Imagem
        MC3D_buf = MC3D_CapDevDialog.chromaKey(MC3D_imagens[posImgEsq]);


        // Textura esquerda
        appear_esq = MC3D_plano_esq.getAppearance();
        appear_esq.setTexture(geraTextura(posImgEsq));

      // obtem array de inteiros do buf para ser enviado ao cliente
      MC3D_imageInt_esq = new int[MC3D_CapDevDialog.CDD_arrayPixel.length];
      System.arraycopy(MC3D_CapDevDialog.CDD_arrayPixel, 0, MC3D_imageInt_esq, 0, MC3D_CapDevDialog.CDD_arrayPixel.length);

      //obtem array de inteiros do buf para ser enviado ao cliente
//          MC3D_imageInt_esq = imgGrabber(MC3D_buf, MC3D_buf.getWidth(), MC3D_buf.getHeight());

//          MC3D_imagens[posImgDir].flush();
//          MC3D_buf.flush();

    }

    // esta invertido devido a restrição no processo de escolha das cameras
    // a vizualizaçao fica invertida
    // deveria ser: img_dir = CapDevDialog.getImage_Dir();
      // if (img_dir != null)
      if (MC3D_imagens[posImgDir] != null) // devices não registrados ou menuparcameras desabilitado
      {
        // Aplica chromakey na Imagem
        MC3D_buf = MC3D_CapDevDialog.chromaKey(MC3D_imagens[posImgDir]);

        // Textura Direita
        appear_dir = MC3D_plano_dir.getAppearance();
        appear_dir.setTexture(geraTextura(posImgDir));

        // obtem array de inteiros do buf para ser enviado ao cliente
        //MC3D_imageInt_dir = imgGrabber(MC3D_buf, MC3D_buf.getWidth(), MC3D_buf.getHeight());

        // obtem array de inteiros do buf para ser enviado ao cliente
        MC3D_imageInt_dir = new int[MC3D_CapDevDialog.CDD_arrayPixel.length];
        System.arraycopy(MC3D_CapDevDialog.CDD_arrayPixel, 0, MC3D_imageInt_dir, 0, MC3D_CapDevDialog.CDD_arrayPixel.length);
//            ImgEstaticas = false;

        if (enviar)
        {
          // seta variavel na classe Socketserver para serem enviados ao cliente
          MC3D_CapDevDialog.CDD_menuPrincipal.SServer.EnviarImagens(
              MC3D_imageInt_esq, MC3D_imageInt_dir, MC3D_buf.getWidth(),
              MC3D_buf.getHeight());
        }

        //          MC3D_imagens[posImgDir].flush();
//          MC3D_buf.flush();
      }
    }
    else ImgEstaticas = true;
//    getView().repaint();
    }


    /**
     * Metodo respossanvel por gerar a nova textura a partir da imagem
     * @param img: Image
     * @return: Texture2D
     */
  public Texture2D geraTextura(int olho)
  {
    if (MC3D_novoTamImagem)
    {
      MC3D_ImageComp2D_esq = new ImageComponent2D(ImageComponent.FORMAT_RGBA, MC3D_buf.getWidth(), MC3D_buf.getHeight());
      MC3D_ImageComp2D_esq.setCapability(ImageComponent2D.ALLOW_IMAGE_WRITE);
      MC3D_ImageComp2D_dir = new ImageComponent2D(ImageComponent.FORMAT_RGBA, MC3D_buf.getWidth(), MC3D_buf.getHeight());
      MC3D_ImageComp2D_dir.setCapability(ImageComponent2D.ALLOW_IMAGE_WRITE);

/*      MC3D_texture_esq = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA, MC3D_buf.getWidth(), MC3D_buf.getHeight());
      MC3D_texture_esq.setCapability(Texture2D.ALLOW_IMAGE_WRITE);
      MC3D_texture_dir = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA, MC3D_buf.getWidth(), MC3D_buf.getHeight());
      MC3D_texture_dir.setCapability(Texture2D.ALLOW_IMAGE_WRITE);
*/
      MC3D_novoTamImagem = false;

    }
    if (olho == 0) // esq
    {
      MC3D_ImageComp2D_esq.set(MC3D_buf);

      MC3D_texture_esq = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA, MC3D_buf.getWidth(), MC3D_buf.getHeight());

      MC3D_texture_esq.setCapability(Texture2D.ALLOW_IMAGE_WRITE);

      MC3D_texture_esq.setImage(0, MC3D_ImageComp2D_esq);

      return MC3D_texture_esq;
    }
    else
    {
      MC3D_ImageComp2D_dir.set(MC3D_buf);

      MC3D_texture_dir = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA, MC3D_buf.getWidth(), MC3D_buf.getHeight());

      MC3D_texture_dir.setCapability(Texture2D.ALLOW_IMAGE_WRITE);

      MC3D_texture_dir.setImage(0, MC3D_ImageComp2D_dir);

      return MC3D_texture_dir;
    }
  }

  /**
   * retorna uma nova Appearance com a texture a partir da imagem esquerda
   * @return: Appearance
   **/
  public Appearance buildAppearance_Esq( )
  {
    Appearance appear = new Appearance( );
    appear.setCapability(Appearance.ALLOW_TEXTURE_WRITE);
    appear.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_READ);
    appear.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_WRITE);


    // basta colocar 1.0f para total transparencia do plano
    appear.setTransparencyAttributes(
        new TransparencyAttributes(TransparencyAttributes.FASTEST, 1.0f));

    texturaAtributos = new TextureAttributes();
    texturaAtributos.setCapability(TextureAttributes.ALLOW_TRANSFORM_WRITE);
    texturaAtributos.setCapability(TextureAttributes.ALLOW_TRANSFORM_READ);

    appear.setTextureAttributes(texturaAtributos);

    return appear;
  }

  /**
   * retorna uma nova Appearance com a texture a partir da imagem esquerda
   * @return: Appearance
   **/
  public Appearance buildAppearance_Dir( )
  {
    Appearance appear = new Appearance( );
    appear.setCapability(Appearance.ALLOW_TEXTURE_WRITE);
    appear.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_READ);
    appear.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_WRITE);

    // basta colocar 1.0f para total transparencia do plano
    appear.setTransparencyAttributes(
        new TransparencyAttributes(TransparencyAttributes.FASTEST, 1.0f));

    texturaAtributos = new TextureAttributes();
    texturaAtributos.setCapability(TextureAttributes.ALLOW_TRANSFORM_WRITE);
    texturaAtributos.setCapability(TextureAttributes.ALLOW_TRANSFORM_READ);
    appear.setTextureAttributes(texturaAtributos);

    return appear;
  }


  /**
   * Transforma a Image em um array de inteiros.
   * @param img: Imagem
   * @param width: largura da imagem
   * @param height: altura da imagem
   * @return: array de inteiros da imagem
   */
  public int[] imgGrabber(BufferedImage img, int width, int height)
  {
    int w = width = img.getWidth(null);
    int h = height = img.getHeight(null);

    MC3D_pixels = new int[w*h];

    MC3D_pixelgrabber = new PixelGrabber (img, 0, 0, w, h, MC3D_pixels, 0, w);

    try
    {
      MC3D_pixelgrabber.grabPixels();
    }
    catch (InterruptedException _ex) {}

    if ((MC3D_pixelgrabber.status() & 0x80) == 0);

    return MC3D_pixels;
  }

  public void keyPressed(KeyEvent e)
  {
    if (e.getKeyCode() == KeyEvent.VK_SPACE)
    {
      if (MC3D_sw_box != null)
      {
        if (MC3D_OqueRenderizar) {
          MC3D_OqueRenderizar = false;
          MC3D_sw_box.setWhichChild(0);
        }
        else
        {
          MC3D_OqueRenderizar = true;
          MC3D_sw_box.setWhichChild(1);
        }
      }
    }
    if (e.getKeyCode() == KeyEvent.VK_C) // muda posiçao da camera virtual
                                         // para lateral
    {
      if (!MC3D_posCameraConf)
      {
        MC3D_menuprincipal.MP_av3D.setTranslacaoCameraConfiguracao();
        MC3D_posCameraConf = true;
      }
      else
      {
        MC3D_menuprincipal.MP_av3D.setPontoVistaTerceiraPessoa();
        MC3D_posCameraConf = false;
      }
    }

    if (e.getKeyCode() == KeyEvent.VK_I) // inverte imagens
   {
     if (posImgEsq == 0)
       posImgEsq = 1;
     else
       posImgEsq =0;

     if (posImgDir == 0)
       posImgDir = 1;
     else
       posImgDir =0;

     enviar = true;
   }



   if (e.getKeyCode() == 98) // cima
   {
     System.out.println("cima");
//     translateY (+0.01);
     translateParalaxeY_VideoAvatar_Esq(+0.01);
   }

   if (e.getKeyCode() == 104) //baixo
   {
     System.out.println("baixo");
//     translateY (-0.01);
     translateParalaxeY_VideoAvatar_Esq(-0.01);
   }

   if (e.getKeyCode() == 102) // esquerda
   {
     System.out.println("esquerda");
//     translateX (-0.01);
     translateParalaxeX_VideoAvatar_Dir(-0.01);
   }

   if (e.getKeyCode() == 100) // direita
   {
     System.out.println("direita");
//     translateX (+0.01);
     translateParalaxeX_VideoAvatar_Dir(+0.01);
   }
  }

  public void keyReleased(KeyEvent e)
  {}

  public void keyTyped(KeyEvent e)
  {}

  public void setSwitch(Switch sw_box)
  {
    MC3D_sw_box = sw_box;
  }

  /**
   * Metodo para transladar os planos no eixo Y
   * @param: valor de translaçao
   */
  public void translateY (double val)
  {
    Vector3d translate = new Vector3d();
    MC3D_gc.getModelTransform(t3d);
    t3d.get(translate);
    translate.y += val;
    t3d.setTranslation(translate);
    MC3D_gc.setModelTransform(t3d);

  }

  /**
   * Metodo para transladar os planos no eixo X
   * @param: valor de translaçao
   */
  public void translateX (double val)
  {
    Vector3d translate = new Vector3d();
    MC3D_gc.getModelTransform(t3d);
    t3d.get(translate);
    translate.x += val;
    t3d.setTranslation(translate);
    MC3D_gc.setModelTransform(t3d);
  }

  /**
   * Metodo para transladar a imagem esq do video avatar no eixo X
   * @param: valor de translaçao
   */
  public void translateParalaxeX_VideoAvatar_Esq(double val)
  {

    Vector3d translate = new Vector3d();
    paralaxeImgEsq.get(translate);
    translate.x += val;
    paralaxeImgEsq.setTranslation(translate);
    appear_esq.getTextureAttributes().setTextureTransform(paralaxeImgEsq);
    MC3D_CapDevDialog.CDD_menuPrincipal.SServer.EnviarComandos (11,
        val,
        0.0,
        0.0,
        13,
        MC3D_CapDevDialog.CDD_menuPrincipal.MP_av3D.getMatrizTranslacao(),
        MC3D_CapDevDialog.CDD_menuPrincipal.MP_av3D.getMatrizRotacao());
  }

  /**
   * Metodo para transladar a imagem esq do video avatar no eixo Y
   * @param: valor de translaçao
   */
  public void translateParalaxeY_VideoAvatar_Esq(double val)
  {
    Vector3d translate = new Vector3d();
    paralaxeImgEsq.get(translate);
    translate.y += val;
    paralaxeImgEsq.setTranslation(translate);
    appear_esq.getTextureAttributes().setTextureTransform(paralaxeImgEsq);
    MC3D_CapDevDialog.CDD_menuPrincipal.SServer.EnviarComandos (12,
        val,
        0.0,
        0.0,
        13,
        MC3D_CapDevDialog.CDD_menuPrincipal.MP_av3D.getMatrizTranslacao(),
        MC3D_CapDevDialog.CDD_menuPrincipal.MP_av3D.getMatrizRotacao());

  }

  /**
   * Metodo para transladar a imagem dir do video avatar no eixo X
   * @param: valor de translaçao
   */
  public void translateParalaxeX_VideoAvatar_Dir(double val)
  {
    Vector3d translate = new Vector3d();
    paralaxeImgDir.get(translate);
    translate.x += val;
    paralaxeImgDir.setTranslation(translate);
    appear_dir.getTextureAttributes().setTextureTransform(paralaxeImgDir);
    MC3D_CapDevDialog.CDD_menuPrincipal.SServer.EnviarComandos (21,
        val,
        0.0,
        0.0,
        13,
        MC3D_CapDevDialog.CDD_menuPrincipal.MP_av3D.getMatrizTranslacao(),
        MC3D_CapDevDialog.CDD_menuPrincipal.MP_av3D.getMatrizRotacao());

  }

  /**
   * Metodo para transladar a imagem dir do video avatar no eixo Y
   * @param: valor de translaçao
   */
  public void translateParalaxeY_VideoAvatar_Dir(double val)
  {
    Vector3d translate = new Vector3d();
    paralaxeImgDir.get(translate);
    translate.y += val;
    paralaxeImgDir.setTranslation(translate);
    appear_dir.getTextureAttributes().setTextureTransform(paralaxeImgDir);
    MC3D_CapDevDialog.CDD_menuPrincipal.SServer.EnviarComandos (22,
        val,
        0.0,
        0.0,
        13,
        MC3D_CapDevDialog.CDD_menuPrincipal.MP_av3D.getMatrizTranslacao(),
        MC3D_CapDevDialog.CDD_menuPrincipal.MP_av3D.getMatrizRotacao());

  }


} // fim da classe MyCanvas3D


