package net.mkonrad.synes.gui;

import net.mkonrad.synes.ctrl.CentralPickingCtrl;
import net.mkonrad.synes.ctrl.GlobalKeyListener;
import net.mkonrad.synes.ctrl.GuiCtrl;
import net.mkonrad.synes.data.AbstractGraph;
import net.mkonrad.synes.data.Config;
import net.mkonrad.synes.data.Instrument;
import net.mkonrad.synes.data.InstrumentCabinet;
import net.mkonrad.synes.data.Layer;
import net.mkonrad.synes.data.LayerSet;
import net.mkonrad.synes.gui.elements.*;
import net.mkonrad.synes.gui.style.Colors;
import net.mkonrad.synes.util.Common3DTools;
import net.mkonrad.synes.util.DbgTools;
import net.mkonrad.synes.util.ErrTools;
import net.mkonrad.synes.util.TextureTable;

import javax.swing.*;

import java.awt.*;
import java.awt.event.KeyAdapter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.sun.j3d.utils.universe.*;

import de.sciss.jcollider.Group;

import javax.media.j3d.*;
import javax.vecmath.*;


public class SceneCore extends JComponent
{
  public static final int BOUNDSIZE = 100;  // larger than world
  public static final float DEFAULT_BPM = 60.0f;
  public static final float LAYER_HEIGHT = 0.5f;
  public static final int DEFAULT_NUM_OCTAVES = 5;
  
  protected static boolean OSC_MODE = false; // SuperCollider Mode? 
  
  private boolean _orbitMode = false;

  private static BoundingSphere _bounds = null;
  private static InstrumentCabinet _instrCabinet = new InstrumentCabinet();
  private static float _bpm = DEFAULT_BPM;
  
  private SimpleUniverse _suniverse;
  
  private BranchGroup _sceneBG;
  private BranchGroup _pickableObjBG;
  private BranchGroup _layersBG;
  
  private Canvas3DExt _canvas3D;
  protected HUDOverlay _hud;
  
  private GuiCtrl _guiCtrl;
  private CentralPickingCtrl _pickingCtrl;
  private GroundShape _ground;
  
  private LayerSet _layerSet;
  
  private JFrame _parentWindow;
  private Config _conf;
  
  
  public SceneCore(JFrame parentWindow, Config conf)
  {
    setLayout(new BorderLayout());
    setOpaque(false);
    setDoubleBuffered(true);
    
    _conf = conf;
    _parentWindow = parentWindow;
    
    int panelWidth = new Integer(_conf.getCfgValue("width")).intValue();
    int panelHeight = new Integer(_conf.getCfgValue("height")).intValue();
    
    if (_conf.getCfgValue("fullscreen").equals("true"))
    {
    	_parentWindow.setUndecorated(true);
      setPreferredSize(Toolkit.getDefaultToolkit().getScreenSize());
      DbgTools.print("Entering fullscreen mode...");
    }
    else
    {
	    setPreferredSize(new Dimension(panelWidth, panelHeight));
    }
    
    _canvas3D =	new Canvas3DExt(SimpleUniverse.getPreferredConfiguration(), panelWidth, panelHeight);
    
    add("Center", _canvas3D);
    _canvas3D.setFocusable(true);     // give focus to the canvas 
    _canvas3D.requestFocus();
    
    
    _suniverse = new SimpleUniverse(_canvas3D);
    DbgTools.print("J3D version: " + VirtualUniverse.getProperties().get("j3d.version"));
    
    createSceneGraph(); //calls createControllers() within!
    createHUD();
    
    // depth-sort transparent objects on a per-geometry basis
    _suniverse.getViewer().getView().setTransparencySortingPolicy(View.TRANSPARENCY_SORT_GEOMETRY);
    
    _suniverse.addBranchGraph(_sceneBG);
  }
  
  protected void initContents()
  {
    if (!OSC_MODE)
    {
      showContents();
    }
    else
    {
      _hud.displayLoadingMsg();
    }
  }
  
  private void createControllers()
  {
    addKeyListener(new GlobalKeyListener(this));
    
    _guiCtrl = new GuiCtrl(this);
    _pickingCtrl = new CentralPickingCtrl(_pickableObjBG, this);
    _ground.getBG().addChild(_pickingCtrl);
  }
  
  private void createHUD()
  {
    _hud = new HUDOverlay(this);
    _hud.display();
  }
  
  public boolean isOSCMode()
  {
    return OSC_MODE;
  }
  
  public BranchGroup getRootBG()
  {
    return _sceneBG;
  }
  
  public GroundShape getGround()
  {
    return _ground;
  }
  
  public HUDOverlay getHUD()
  {
    return _hud;
  }
  
  public CentralPickingCtrl getPickingCtrl()
  {
    return _pickingCtrl;
  }
  
  public Layer getCurrLayer()
  {
    return _layerSet.getCurrLayer();
  }
  
  public static InstrumentCabinet getInstrCabinet()
  {
    return _instrCabinet;
  }
  
  public static float getBpm()
  {
    return _bpm;
  }

  public static void setBpm(float bpm)
  {
    _bpm = bpm;
  }

  public LayerSet getLayerSet()
  {
    return _layerSet;
  }
  
  public ViewingPlatform getViewingPlatform()
  {
    return _suniverse.getViewingPlatform();
  }
  
  public Canvas3DExt getCanvas3D()
  {
    return _canvas3D;
  }
  
  public static Bounds getSceneBounds()
  {
    return _bounds;
  }

  public JFrame getParentWindow()
  {
  	return _parentWindow;
  }
  
  public void addKeyListener(KeyAdapter k)
  {
    _canvas3D.addKeyListener(k);
  }


  private void createSceneGraph() 
  { 
    _sceneBG = new BranchGroup(); //root node
    _bounds = new BoundingSphere(new Point3d(0,0,0), BOUNDSIZE);   

    lightScene();         // add the lights
    addBackground();      // add the bg
    
    //show the axis
//    if (DbgTools.getDbgMode() == true)
//    {
//    	BranchGroup helperLines = new HelperLines(20).getBG();
//    	_sceneBG.addChild(helperLines);
//    }
    
    //create a BranchGroup for all pickable objects
    _pickableObjBG = new BranchGroup();
    _pickableObjBG.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
    _pickableObjBG.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
    
    //make the ground
    _ground = new GroundShape();
    
    //make the layers
    _layerSet = new LayerSet();
    _layersBG = new BranchGroup();
    _layersBG.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
    _layersBG.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
    //_layersBG.setCapability(BranchGroup.ALLOW_DETACH);
    
    createNewLayer(Layer.TYPE_SEQUENCER, false, false);
    
    createControllers();
    
    //attach pickable objects to BG
    //_pickableObjBG.addChild(_layersBG);
    //_pickableObjBG.addChild(_ground.getBG());
    
    //attach the whole thing to the scene root BG
    _sceneBG.addChild(_pickableObjBG);

    _sceneBG.compile();
  }
  
  protected void showContents()
  {
    _pickableObjBG.addChild(_layersBG);
    _pickableObjBG.addChild(_ground.getBG());
  }
  
  /**
   * Create a new layer and attach it to the scene graph
   * @param type  Layer-Type (Layer.TYPE_SEQUENCER or Layer.TYPE_SYNTHESIZER)
   * @param createSCGroup Create a group for this layer on the SC Server
   * @param createLayerAboveCurrent Create the layer above the current layer
   * @return  The newly created layer
   */
  public Layer createNewLayer(int type, boolean createSCGroup,
      boolean createLayerAboveCurrent)
  {
    Layer newLayer = new Layer(getLayerSet().getLayerNum(), type);
    newLayer.setHeight(getCurrLevelHeight(createLayerAboveCurrent));
    _layerSet.addLayer(newLayer);
    _layersBG.addChild(newLayer.getLayerBG());

    DbgTools.print("Created layer#" + newLayer.getId() + " at height"
        + getCurrLevelHeight(createLayerAboveCurrent));

    if (isOSCMode() && createSCGroup)
    {
      try
      {
        newLayer.setSCGroup(Group.tail(((SceneCoreOSC) this).getSCStdGrp()));
        DbgTools.print("Created group for layer#" + newLayer.getId());
      } catch (IOException e)
      {
        ErrTools.print(e);
      }
    }

    return newLayer;
  }

  private void lightScene()
  /* One ambient light, 2 directional lights */
  {
    //Color3f white = new Color3f(1.0f, 1.0f, 1.0f);

    // Set up the ambient light
    AmbientLight ambientLightNode = new AmbientLight(Colors.ambientLight);
    ambientLightNode.setInfluencingBounds(_bounds);
    _sceneBG.addChild(ambientLightNode);

    // Set up the directional lights
    Vector3f light1Direction  = new Vector3f(-1.0f, -1.0f, -1.0f);
       // left, down, backwards 
    Vector3f light2Direction  = new Vector3f(1.0f, -1.0f, 1.0f);
       // right, down, forwards

    DirectionalLight light1 = 
            new DirectionalLight(Colors.directionalLight, light1Direction);
    light1.setInfluencingBounds(_bounds);
    _sceneBG.addChild(light1);

//    DirectionalLight light2 = 
//        new DirectionalLight(Colors.directionalLight, light2Direction);
//    light2.setInfluencingBounds(_bounds);
//    _sceneBG.addChild(light2);
  }  // end of lightScene()


  private void addBackground()
  {
  	Background back = new Background();
    back.setApplicationBounds( _bounds );
    back.setColor(Colors.background);
    _sceneBG.addChild( back );
  }

  public GuiCtrl getGuiCtrl()
  {
    return _guiCtrl;
  }

  public static String getInstrTextureDir()
  {
    return "./textures/instruments";
  }

  public float getCurrLevelHeight()
  {
    return getCurrLevelHeight(false);
  }
  
  public float getCurrLevelHeight(boolean newLayer)
  {
    return LAYER_HEIGHT * (_layerSet.getCurrLayerLevel() + (newLayer ? 1 : 0));
  }
  
  public void toggleCurrLayerType()
  {
    if (_layerSet.getCurrLayer().getGraphs().size() > 0)  //only allow that, if there is no "content" in the current layer
      return;
    
    int newType;
    
    if (getCurrLayer().getType() == Layer.TYPE_SEQUENCER)
    {
      newType = Layer.TYPE_SYNTH;
    }
    else
    {
      newType = Layer.TYPE_SEQUENCER;
    }
    
    getCurrLayer().setType(newType);
    _pickingCtrl.changeToLayerType(newType, null);
    
    _hud.updateLayerInfo();
    _hud.updateAvailableInstr();
  }

  public void changeLayer(int newLayerLevel, Instrument instr)
  {
    //disable picking for old layer elements and hide them a bit
    if (!_orbitMode)
      _layerSet.setEnabled(-1, false);
    
    //change layer
    _layerSet.setCurrLayerLevel(newLayerLevel);
    _pickingCtrl.changeToLayerType(getCurrLayer().getType(), instr);
    
    //enable picking for new layer elements and fully show them again
    if (!_orbitMode)
      _layerSet.setEnabled(-1, true);
    
    //move ground to new level
    _ground.setAtHeight(getCurrLevelHeight());
  }
  
  private boolean isOrbitEnabled()
  {
    return _guiCtrl.getOrbitControl().getEnable();
  }
  
  private void setOrbitCtrl(boolean status)
  {
    _guiCtrl.getOrbitControl().setEnable(status);
  }

  public void changeOrbitMode()
  { 
    boolean enable = !(isOrbitEnabled());
    
    Layer[] layers = _layerSet.getLayers();
    
    for (int i = 0; i < layers.length; i++)
    {
      _layerSet.setEnabled(i, enable);
    }
    
    if (!enable)
    {
      _layerSet.setEnabled(-1, true);
      
      if (_pickingCtrl.getCurrInstrument().needsKeyboard())
        _ground.showKeyboard();
    }
    else
    {
      _ground.hideKeyboard();
    }
    
    _ground.setPickable(!enable);
    
    setOrbitCtrl(enable);
    _hud.updateOrbitStatus(enable);
    
    _orbitMode = enable;
  }
  
  public boolean isOrbitMode()
  {
    return _orbitMode;
  }
  

  public void disableOrbitCtrl()
  {
    if (_orbitMode)
    {
      setOrbitCtrl(false);
    }
  }
  
  public void enableOrbitCtrl()
  {
    if (_orbitMode)
    {
      setOrbitCtrl(true);
    }
  }
}
