package net.mkonrad.synes.gui.elements.instruments;

import javax.media.j3d.Appearance;
import javax.media.j3d.BadTransformException;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Material;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.TextureAttributes;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.TransparencyAttributes;
import javax.vecmath.AxisAngle4f;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

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

import net.mkonrad.synes.ctrl.anim.BeatAnimBehavior;
import net.mkonrad.synes.data.Beat;
import net.mkonrad.synes.data.SynthObj;
import net.mkonrad.synes.gui.SceneCore;
import net.mkonrad.synes.gui.style.Colors;
import net.mkonrad.synes.util.Common3DTools;
import net.mkonrad.synes.util.DbgTools;
import net.mkonrad.synes.util.MusicTools;
import net.mkonrad.synes.util.TextureTable;

/**
 * Graphical representation of an instrument. Abstract class.
 * 
 * @author mako
 *
 */
public abstract class AbstractInstrumentShape implements InstrumentShape
{
  private static TextureTable _texTable = null;
  
  protected BranchGroup _bg;  /** Object's branch group */ 
  protected TransformGroup _tg; /** For basic transformations (position, standard orientation) */
  protected TransformGroup _tgAnim; /** For transformations made for the beat animation */
  protected TransformGroup _tgRotation; /** For rotations along the graph line */
  
  protected SynthObj _synthData;
  protected Primitive _primitive;
  protected Appearance _app;
  protected Material _mat;
  
  protected BranchGroup _beatAnimBG = null;
  protected BeatAnimBehavior _beatAnim = null;
  protected int _beatAnimCycle = 0;
  protected Transform3D _beatAnimTransform;
  
  protected static int _defaultPrimitiveFlags = Primitive.GENERATE_NORMALS + Primitive.GENERATE_TEXTURE_COORDS /*+ Primitive.ALLOW_PICKABLE_WRITE + Primitive.ENABLE_GEOMETRY_PICKING*/;
  
  public AbstractInstrumentShape(SynthObj synthData)
  {
    if (_texTable == null)
    {
      _texTable = new TextureTable();
    }
    
    _synthData = synthData;
  }
  
  public void setEnabled(boolean enabled)
  {
    getPrimitive().setPickable(enabled);
    
    if (enabled)
      setTransparency(0.0f);
    else
      setTransparency(0.75f);
  }
  
  protected void init()
  {
    _primitive.setPickable(true);
    _primitive.setCapability(Primitive.ALLOW_PICKABLE_WRITE);
    //_primitive.setPrimitiveFlags(Primitive.ENABLE_GEOMETRY_PICKING);  //for exact intersection points
    _primitive.setUserData(_synthData);
    
    _bg = new BranchGroup();
    _bg.setCapability(BranchGroup.ALLOW_DETACH);
    _bg.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
    _bg.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
    _bg.setCapability(BranchGroup.ENABLE_PICK_REPORTING);
    _bg.setCapability(BranchGroup.ALLOW_PICKABLE_WRITE);
    _bg.setPickable(true);

    _tg = makeTG();
    _tgAnim = makeTG();
    _tgRotation = makeTG();
  }
  
  protected void connectSceneGraphElements()
  {
    _tgAnim.addChild(_primitive);
    _tgRotation.addChild(_tgAnim);
    _tg.addChild(_tgRotation);
    _bg.addChild(_tg);
  }
  
  private TransformGroup makeTG()
  {
    TransformGroup tg = new TransformGroup();
    
    tg.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    tg.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
    tg.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
    tg.setCapability(BranchGroup.ALLOW_PICKABLE_WRITE);
    
    return tg;
  }
  
  public Primitive getPrimitive()
  {
    return _primitive;
  }
  
  protected void initTransform3D(Vector3f coord)
  {    
    //set on position
    Transform3D t3d = new Transform3D();
    //t3d.setTranslation(new Vector3f(coord.x, coord.y, _height));
    t3d.setTranslation(new Vector3f(coord.x, coord.y, 0.0f));
    
    //scale and turn object
    //rescale(t3d);
    //t3d.rotX((float)Math.PI / 2.0f);
    t3d.setRotation(new AxisAngle4f(1, 0, 0, (float)Math.PI / 2.0f));
    
    _tg.setTransform(t3d);
    
    rescale();
  }
  
  protected void createAppearance()
  {
    //create appearance
    _app = new Appearance();
    _app.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE);
    
    //set texture
    TextureAttributes texAttr = new TextureAttributes();
    texAttr.setTextureMode(TextureAttributes.MODULATE);
    texAttr.setPerspectiveCorrectionMode(TextureAttributes.NICEST);
    _app.setTextureAttributes(texAttr);
    
    _app.setTexture(_texTable.getTexture(_synthData.getInstrument().getTexture()));
    
    //set material (color)
    _mat = new Material();
    _mat.setAmbientColor(_synthData.getInstrument().getColor());
    //_mat.setCapability(Material.ALLOW_COMPONENT_WRITE);
    _app.setMaterial(_mat);
  }
  
  private TransparencyAttributes createTransparencyAttrib(float t)
  {
    TransparencyAttributes trans = new TransparencyAttributes();
    trans.setTransparency(t);
    trans.setTransparencyMode(TransparencyAttributes.BLENDED);
    trans.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE);
    
    return trans;
  }
  
  public void setTransparency(float transValue)
  {
    TransparencyAttributes transAttrib = (transValue <= 0 ?
        new TransparencyAttributes() : createTransparencyAttrib(transValue));
    
    _app.setTransparencyAttributes(transAttrib);
  }
  
  protected abstract void createPrimitive();
  
  public void turn(float rad)
  {
    if (_synthData.getAmp() <= 0f) //else can cause a BadTransformException (dont know why, though...)
      return;
    
    //DbgTools.print("turning " + Math.toDegrees(rad) + "° or " + rad + " radians");
    
    Transform3D t3d = new Transform3D();
    _tgRotation.getTransform(t3d);
    t3d.setIdentity();
    t3d.rotY(rad);
    
    try
    {
      _tgRotation.setTransform(t3d);
    }
    catch (BadTransformException e)
    {
      //doin' nuffin'!
    }
  }
  
  /**
   * Rescale the primitive according to the amp of the beat
   */
  public void rescale()
  {
    rescale(null);
  }

  /**
   * Rescale the primitive according to the amp of the beat
   * @param t3d A Transform3D object or null (then the Transform3D of the
   *            current TransformGroup will be used)
   */
  public void rescale(Transform3D t3d)
  {
    boolean setAgain = false;
    
    if (t3d == null)
    {
      t3d = new Transform3D();
      _tgAnim.getTransform(t3d);
      setAgain = true;
    }
    
    //float scale = getCurrScale();
    //t3d.setScale(new Vector3d(scale, 1.0f, scale));
    t3d.setScale(getCurrScale());
    
    if (setAgain)
      _tgAnim.setTransform(t3d);
  }
  
  protected float getCurrScale()
  {
    return _synthData.getAmp() / 1f + 0.05f;
  }
  
  public BranchGroup getBG()
  {
    return _bg;
  }

  public SynthObj getSynthData()
  {
    return _synthData;
  }

  /**
   * Returns the <b>relative</b> position related to the GraphLine position.
   * Remember to use this when dealing with graphical data (e.g. changing the
   * position of this lines connected to this shape)
   * 
   * @return Vector3f The <b>relative</b> position of this InstrumentShape
   */
  public Vector3f getPos()
  {
    Vector3f posVec = new Vector3f();
    Transform3D t3d = new Transform3D();
    _tg.getTransform(t3d);
    t3d.get(posVec);
    
    return posVec;
  }
  
  /**
   * Returns the <b>absolute</b> position (<b>not</b> related to the GraphLine
   * position).
   * Remember to use this when dealing with music-data (e.g. calculating the
   * panning based on the absolute X-coordinate)
   * 
   * @return Vector3f The <b>absolute</b> position of this InstrumentShape
   */
  public Vector3f getAbsPos()
  {
    Vector3f pos = getPos();
    
    if (_synthData.getGraph() != null)
      pos.add(_synthData.getGraph().getGraphLine().getPos());
    
    return pos;
  }

  public TransformGroup getTG()
  {
    return _tg;
  }

  public void setSynthData(SynthObj synthData)
  {
    _synthData = synthData;
  }

  /**
   * Sets the <b>absolute</b> position of this InstrumentShape using a Point3f.
   * The relative position related to the GraphLine position is calculated
   * @param posVec The new absolute position
   */
  public void setPos(Point3f posVec)
  {
    setPos(new Vector3f(posVec.x, posVec.y, 0f));
  }
  
  /**
   * Sets the <b>absolute</b> position of this InstrumentShape using a Vector3f.
   * The relative position related to the GraphLine position is calculated
   * @param posVec The new absolute position
   */
  public void setPos(Vector3f posVec)
  {
    posVec.z = 0f;
    Transform3D t3d = new Transform3D();
    _tg.getTransform(t3d);
    
    if (_synthData.getGraph() != null)
      posVec.sub(_synthData.getGraph().getGraphLine().getPos());  //calculate the relative position related to the GraphLine position
    
    t3d.setTranslation(posVec);
    _tg.setTransform(t3d);
  }
  
  
  /**
   * Make a visual flash that indicates, that this note has been struck 
   */
  public void beat()
  {
    if (_beatAnim != null)
      resetAnim();
        
    _beatAnim = new BeatAnimBehavior(this);
    _beatAnim.setSchedulingBounds(SceneCore.getSceneBounds());
    _beatAnimBG = Common3DTools.wrapInBG(_beatAnim);
    _beatAnimBG.setCapability(BranchGroup.ALLOW_DETACH);
    _bg.addChild(_beatAnimBG);
    _beatAnimCycle = 0;
    
    _beatAnimTransform = new Transform3D();
    _tgAnim.getTransform(_beatAnimTransform);
  }
  
  /**
   * Beat animation update.
   * Gets called by {@link BeatAnimBehavior}. Makes a color transition based
   * on the sustain value
   */
  public void beatUpdate()
  {
    if (_synthData.getAmp() <= 0.0f)
      return; //we have a rest here!
    //DbgTools.print("anim!");
    float sustain = MusicTools.beats2ms(((Beat)_synthData).getSustain());
    //float perc = _beatAnimCycle / sustain;
    float growBorder = sustain / 4f;
    float growMaxScale = 0.5f;
    float scaleValue = 1.0f;
    
    //Slowly rescale
    if (_beatAnimCycle < growBorder)
    {
      scaleValue = getCurrScale() * (1.0f + (_beatAnimCycle / growBorder) * growMaxScale);
    }
    else
    {
      scaleValue = getCurrScale() * (1.0f + growMaxScale - (_beatAnimCycle / sustain) * growMaxScale);
    }

    _beatAnimTransform.setScale(scaleValue);
    _tgAnim.setTransform(_beatAnimTransform);
    
    /*
    //Fade from col1 to col2
    Color3f col1 = Colors.yellow;
    Color3f col2 = _noteData.getInstrument().getColor();
    _mat.setAmbientColor(
        col1.x * (1.0f - perc) + col2.x * perc,
        col1.y * (1.0f - perc) + col2.y * perc,
        col1.z * (1.0f - perc) + col2.z * perc
     );
    */
    
    _beatAnimCycle += BeatAnimBehavior.UPDATE_TIME;
    
    if (_beatAnimCycle >= sustain)
    {
      //stop animation
      resetAnim();
    }
  }
  
  private void resetAnim()
  {
    _beatAnimBG.detach();
    _beatAnimBG = null;
    _beatAnim = null;
    _beatAnimCycle = 0;
    _beatAnimTransform.setScale(getCurrScale());
    _tgAnim.setTransform(_beatAnimTransform);
  }
}
