package net.mkonrad.synes.ctrl;

import java.io.IOException;

import javax.media.j3d.Node;
import javax.vecmath.Point3f;

import net.mkonrad.synes.data.AbstractGraph;
import net.mkonrad.synes.data.Beat;
import net.mkonrad.synes.data.Layer;
import net.mkonrad.synes.data.Modulator;
import net.mkonrad.synes.data.SynthGraph;
import net.mkonrad.synes.data.SynthInstrument;
import net.mkonrad.synes.data.SynthObj;
import net.mkonrad.synes.gui.SceneCoreOSC;
import net.mkonrad.synes.gui.elements.AbstractGraphLine;
import net.mkonrad.synes.synth.SynthExt;
import net.mkonrad.synes.util.DbgTools;
import net.mkonrad.synes.util.ErrTools;

public abstract class AbstractPickableObj
{ 
  protected CentralPickingCtrl _pickCtrl;
  protected SceneCoreOSC _coreCtrl;

  protected SynthObj _selectedSynthObj = null;
  protected AbstractGraphLine _selectedGraphLine = null;

  protected SynthObjAttribCtrl _mouseWheelListener;
  
  public AbstractPickableObj(CentralPickingCtrl pickCtrl)
  {
    _pickCtrl = pickCtrl;
    _coreCtrl = _pickCtrl.getCoreCtrl();
    _mouseWheelListener = new SynthObjAttribCtrl(this);
  }
  
  public CentralPickingCtrl getPickCtrl()
  {
    return _pickCtrl;
  }
  
  public SynthObj getSelectedSynthObj()
  {
    return _selectedSynthObj;
  }
  
  public AbstractGraphLine getSelectedGraphLine()
  {
    return _selectedGraphLine;
  }
  
  protected void startEditMode()
  {
    _coreCtrl.getCanvas3D().addMouseWheelListener(_mouseWheelListener);
    _pickCtrl.setState(CentralPickingCtrl.STATE_GRAPH_EDIT);
  }
  
  protected void stopEditMode()
  {
    _coreCtrl.getCanvas3D().removeMouseWheelListener(_mouseWheelListener);
    _pickCtrl.setState(CentralPickingCtrl.STATE_READY);
  }
  
  /**
   * Change the amp value of a SynthObj
   * Gets called by {@link SynthObjAttribCtrl} when rotating the Mouse-Wheel
   * @param byValue
   */
  public void changeSynthObjAmp(int byValue)
  {
    int layerType = _coreCtrl.getCurrLayer().getType();
    
    //get the right SynthObj that will be changed
    SynthObj synthObj;
    AbstractGraphLine graphLine;
    if (_pickCtrl.getState() == CentralPickingCtrl.STATE_GRAPH_CREATE)
    {
      graphLine = _pickCtrl.getCurrGraphLine();
      
      //if (layerType == Layer.TYPE_SEQUENCER)
      //{
        synthObj = (SynthObj)_pickCtrl.getCurrGraph().get(_pickCtrl.getCurrGraph().size() - 1);
        graphLine.hidePointerShape(); //do not display the pointer shape!
      //}
      //else
      //{
      //  synthObj = graphLine.getPointerObj();
      //}
    }
    else
    {
      synthObj = _pickCtrl.getSynthObjPickingCtrl().getSelectedSynthObj();
      graphLine = _pickCtrl.getSynthObjPickingCtrl().getSelectedGraphLine();
    }
    
    //change the amp value
    float newAmp = synthObj.getAmp() + (float)byValue * 0.025f;
    
    if (newAmp <= 0.0f)
      newAmp = 0.00f;
    else if (newAmp > 1.0f)
      newAmp = 1.0f;
    
    DbgTools.print("changed to new amp: " + newAmp);
    synthObj.setAmp(newAmp);
    
    //send change to synth at SC server
    boolean isMod = false;
    if (layerType == Layer.TYPE_SYNTH)
    {
      isMod = sendSynthAmpChange(synthObj);
    }
    
    //make the shape smaller / bigger
    synthObj.getInstrumentShape().rescale();
    
    //make the line connected to this synth also smaller / bigger
    SynthObj connectedPrevObj = null;
    SynthObj connectedNextObj = null;
    if (layerType == Layer.TYPE_SEQUENCER)
    {
      connectedPrevObj = ((Beat)synthObj).getPrevBeat();
      connectedNextObj = ((Beat)synthObj).getNextBeat();
    }
    

    if (!isMod)
    {
      if (layerType == Layer.TYPE_SEQUENCER)
      {
        if (connectedPrevObj != null)
          graphLine.updateLinePoints(connectedPrevObj, synthObj);
        if (connectedNextObj != null)
          graphLine.updateLinePoints(synthObj, connectedNextObj);
      }
      else
      {
        graphLine.updateLinePoints(null, synthObj);
      }
    }
    else
    {
      AbstractGraph graph = graphLine.getGraph();

      for (int i = 0; i < graph.size(); i++)
      {
        SynthObj lineObj = ((SynthGraph) graph).getSynthObj(i);
        
        if (lineObj != synthObj)
        {
          graphLine.updateLinePoints(null, lineObj);
        }
      }
    }

    graphLine.updateGeometry();
  }
  
  /**
   * Send the change of the amp value of this synthObj to the SC server
   * @param synthObj  SynthObj with changed amp value
   */
  private boolean sendSynthAmpChange(SynthObj synthObj)
  {
    boolean isMod = false;
    
    if (synthObj.getSynth() != null)
    {
      try
      {
        if (_coreCtrl.isOSCMode())
        {
          synthObj.getSynth().set("amp",
            synthObj.getInstrument().getValueForArg("amp", synthObj));
        }
      } catch (IOException e)
      {
        ErrTools.print(e);
      }
    }
    else  //seems we have a Modulator
    {
      Modulator mod = null;
      try
      {
        mod = (Modulator) synthObj; // do we really have a Modulator here?
      } catch (ClassCastException e)
      {
        // do nothing
      }

      if (mod != null) //yes, we have changed a Modulators amp value!
      {
        isMod = true;
        SynthObj synthObjToMod = null;
        AbstractGraph graph = (mod.getGraph() != null ? mod.getGraph() : _pickCtrl.getCurrGraph());

        if (_coreCtrl.isOSCMode())
        {
          try
          {
            for (int i = 0; i < graph.size(); i++)
            {
              if (graph.get(i) != mod)
              {
                synthObjToMod = ((SynthObj) graph.get(i));
                if (synthObjToMod.getSynth() != null)
                {
                  synthObjToMod.getSynth().set(
                      "modAmp",
                      ((SynthInstrument) synthObjToMod.getInstrument())
                          .getModValueForArg("modAmp", synthObjToMod, mod));
                }
              }
            }
          } catch (IOException e)
          {
            ErrTools.print(e);
          }
        }
      }
    }
    
    return isMod;
  }
  
  public void playSynthGraph(SynthGraph synthGraph)
  {
    SynthObj synthToPlay;
    Modulator mod = synthGraph.getModulator();
    
    for (int i = 0; i < synthGraph.size(); i++)
    {
      synthToPlay = synthGraph.getSynthObj(i);
      if (synthToPlay != mod)
      {
        playSynth(synthToPlay);
      }
    }
  }
  
  public void releaseSynthGraph(SynthGraph synthGraph)
  {
    SynthObj synthToStop;
    Modulator mod = synthGraph.getModulator();
    
    for (int i = 0; i < synthGraph.size(); i++)
    {
      synthToStop = synthGraph.getSynthObj(i);
      if (synthToStop != mod && synthToStop.getSynth() != null)
      {
        try
        {
          DbgTools.print("Releasing SynthObj#" + synthToStop.getId());
          synthToStop.getSynth().releaseWithin(10);
          synthToStop.setSynth(null);
        } catch (IOException e)
        {
          ErrTools.print(e);
        }
      }
    }
  }
  
  public void playSynth(SynthObj synthObj)
  {
    DbgTools.print("Playing SynthObj#" + synthObj.getId() + " in group#" + _coreCtrl.getCurrLayer().getSCGroup().getNodeID());
    try
    {
      SynthExt synth = SynthExt.basicNew(_coreCtrl.getSCServer(), _coreCtrl.getCurrLayer().getSCGroup(), synthObj);
      
      Modulator mod = null;
      if (synthObj.getGraph() != null)
        ((SynthGraph)(synthObj.getGraph())).getModulator();
      
      if (mod == null)
      {
        synth.play(synthObj.getInstrument().getSynthArgs(), synthObj.getInstrument().getAllSynthValues(synthObj));
      }
      else
      {
        synth.play(synthObj.getInstrument().getModArgs(), ((SynthInstrument)synthObj.getInstrument()).getAllModValues(synthObj, mod));
      }
      
      synthObj.setSynth(synth);
    }
    catch (IOException e)
    {
      ErrTools.print(e);
    }
  }
  
  public abstract boolean clicked(Node pickedObj, Point3f where, int mouseState);
  public abstract boolean moved(Node pickedObj, Point3f where, int mouseState);
  public abstract boolean dragged(Point3f where, int mouseState);
  public abstract boolean released(Point3f where, int mouseState);
}
