// Container for the status information used in wm3d.
// Almost all variables are referenced by parent Watermelon.
// So, most variables are defined as public.

import flash.geom.Vector3D;
import flash.geom.Matrix3D;

import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.TimerEvent;

import flash.utils.Timer;

class WMStates {
  // static information
  static public var MOUSE_L_ROTAT_MODE:Int = 0;
  static public var MOUSE_L_TRANS_MODE:Int = 1;
  static public var MOUSE_W_SCALE_MODE:Int = 0;
  static public var MOUSE_W_DEPTH_MODE:Int = 1;

  // parent Watermelon
  private var wm:Watermelon;

  // camera related
  //// camera itself
  public var camera( __getCamera, __setCamera ):Camera;
    public function __getCamera():Camera { return( camera ); }
    public function __setCamera( c:Camera ):Camera {
      camera = c.clone();
      return( camera );
    }

  //// current coordinate
  public var mpos( __getMPos, __setMPos ):Matrix3D;
    public function __getMPos():Matrix3D { return( mpos ); } // not clone()
    public function __setMPos( m:Matrix3D ):Matrix3D {
      mpos = m.clone();
      updateMPos = true;
      return( mpos );
    }

  //// direction of light
  public var light( __getLight, __setLight ):Vector3D;
    public function __getLight():Vector3D { return( light ); }
    public function __setLight( l:Vector3D ) {
      light = l.clone();
      return( light );
    }

  //// camera position
  public var cpos( __getCPos, __setCPos ):Vector3D;
    public function __getCPos():Vector3D { return( cpos ); }
    public function __setCPos( p:Vector3D ):Vector3D {
      cpos = p.clone();
      return( cpos );
    }

  //// offset of view; i forgot what this is
  public var view_offset( __getViewOffset, __setViewOffset ):Vector3D;
    public function __getViewOffset():Vector3D { return( view_offset ); }
    public function __setViewOffset( o:Vector3D ):Vector3D {
      view_offset = o.clone();
      return( view_offset );
    }

  // auto-rotation status
  //// x
  public var arDegreeX( __getARDegreeX, __setARDegreeX ):Float;
    public function __getARDegreeX():Float { return( arDegreeX ); }
    public function __setARDegreeX( d:Float ):Float {
      arDegreeX = d;
      return( arDegreeX );
    }

  //// y
  public var arDegreeY( __getARDegreeY, __setARDegreeY ):Float;
    public function __getARDegreeY():Float { return( arDegreeY ); }
    public function __setARDegreeY( d:Float ):Float {
      arDegreeY = d;
      return( arDegreeY );
    }

  // mouse mode
  //// left
  public var mouseModeL( __getMouseModeL, __setMouseModeL ):Int;
    public function __getMouseModeL():Int { return( mouseModeL ); }
    public function __setMouseModeL( m:Int ) {
      mouseModeL = m;
      return( mouseModeL );
    }

  //// wheel
  public var mouseModeW( __getMouseModeW, __setMouseModeW ):Int;
    public function __getMouseModeW():Int { return( mouseModeW ); }
    public function __setMouseModeW( m:Int ) {
      mouseModeW = m;
      return( mouseModeW );
    }

  // flags often referenced
  //// whether auto-rotating
  public var arNow( __isAutoRotating, __setAutoRotating ):Bool;
    public function __isAutoRotating():Bool { return( arNow ); }
    public function __setAutoRotating( f:Bool ):Bool {
      arNow = f;
      return( arNow );
    }

  //// whether update of camera position is required
  public var updateCameraPos( __doUpdateCameraPos, __setUpdateCameraPos ):Bool;
    public function __doUpdateCameraPos():Bool { return( updateCameraPos ); }
    public function __setUpdateCameraPos( f:Bool ) {
      updateCameraPos = f;
      return( updateCameraPos );
    }

  //// whether update coordinate
  public var updateMPos( __doUpdateMPos, __setUpdateMPos ):Bool;
    public function __doUpdateMPos():Bool { return( updateMPos ); }
    public function __setUpdateMPos( f:Bool ):Bool {
      updateMPos = f;
      return( updateMPos );
    }

  //// whether update offset of view
  public var updateViewOffset( __doUpdateViewOffset, __setUpdateViewOffset ):Bool;
    public function __doUpdateViewOffset():Bool { return( updateViewOffset ); }
    public function __setUpdateViewOffset( f:Bool ) {
      updateViewOffset = f;
      return( updateViewOffset );
    }

  //// whether update of scene is needed
  public var updateScene( __doUpdateScene, __setUpdateScene ):Bool;
    public function __doUpdateScene():Bool { return( updateScene ); }
    public function __setUpdateScene( f:Bool ):Bool {
      updateScene = f;
      return( updateScene );
    }

  // flags
  //// general flag; whether i am busy now?
  public var busyNow( __isBusy, __setBusy ):Bool;
    public function __isBusy():Bool { return( busyNow ); }
    public function __setBusy( f:Bool ):Bool {
      busyNow = f;
      return( busyNow );
    }

  //// whether playing now
  public var playingNow( __isPlaying, __setPlaying ):Bool;
    public function __isPlaying():Bool { return( playingNow ); }
    public function __setPlaying( f:Bool ):Bool {
      playingNow = f;
      return( playingNow );
    }

  //// whether playing is in remove mode
  public var playReverse( __isReversePlay, __setReversePlay ):Bool;
    public function __isReversePlay():Bool { return( playReverse ); }
    public function __setReversePlay( f:Bool ):Bool {
      playReverse = f;
      return( playReverse );
    }

  // timer
  public var myLoadSysTimer( __getLoadSysTimer, null ):Timer;
    public function __getLoadSysTimer():Timer { return( myLoadSysTimer ); }

  // counter
  //// frame counter for play mode
  public var frameCounter( __getFrameCounter, __setFrameCounter ):Int;
    public function __getFrameCounter():Int { return( frameCounter ); }
    public function __setFrameCounter( c:Int ):Int {
      frameCounter = c;
      return( frameCounter );
    }

  //// current frame
  public var frameIndex( __getFrameIndex, __setFrameIndex ):Int;
    public function __getFrameIndex():Int { return( frameIndex ); }
    public function __setFrameIndex( i:Int ):Int {
      frameIndex = i;
      return( frameIndex );
    }

  public function new( w:Watermelon ) {
    wm = w;

    // default values
    arDegreeX = 0.0;
    arDegreeY = wm.params.arDegree; // params must be initialized first
    mouseModeL = WMStates.MOUSE_L_ROTAT_MODE; // rotation mode
    //mouseModeL = WMStates.MOUSE_L_TRANS_MODE; // translation mode
    mouseModeW = WMStates.MOUSE_W_SCALE_MODE; // scale mode
    //mouseModeW = WMStates.MOUSE_W_DEPTH_MODE; // depth mode

    camera = new Camera();
    camera.pos.z = -Math.max( wm.stage.stageWidth, wm.stage.stageHeight ) * 2;
    camera.ratio = wm.stage.stageWidth / wm.stage.stageHeight;
    camera.determineFov( wm.stage.stageHeight, Math.abs( camera.pos.z ) );

    light = new Vector3D( 1, -1, -1 );
    light.normalize();

    cpos = new Vector3D();
    mpos = new Matrix3D();
    mpos.identity();

    view_offset = new Vector3D();

    arNow = true;
    busyNow = false;
    updateCameraPos = true;
    updateMPos = true;
    updateViewOffset = false;
    updateScene = true;

    playingNow = false;
    playReverse = false;

    frameCounter = 0;
    frameIndex = 0;
  }

  public function needToUpdate():Bool {
    return( updateCameraPos || updateMPos || arNow || updateViewOffset ||
            updateScene );
  }

  public function updateCPos():Void {
    cpos.x = camera.pos.x;
    cpos.y = camera.pos.y;
    cpos.z = camera.pos.z;
    updateCameraPos = false;
  }

  public function resetFlags():Void {
    updateCameraPos = false;
    updateMPos = false;
    updateViewOffset = false;
    updateScene = false;
  }

  public function setCameraPosZ( z:Float ):Void {
    camera.pos.z = z;
    updateCameraPos = true;
  }

  public function setLightDirection( p:Point3D ):Void {
    light.x = p.x;
    light.y = p.y;
    light.z = p.z;
    light.normalize();
  }

  // mouse actions relatives
  //// scaling
  public function changeScale( d:Int ):Void {
    var sc:Float = 1.0 + wm.params.scaleWheel * d;
    mpos.appendScale( sc, sc, sc );
    updateMPos = true;
  }

  //// depth change
  public function changeDepth( d:Int ):Void {
    camera.pos.z += wm.params.depthWheel * d;
    camera.pos.z = Math.max( 0, camera.pos.z );
    camera.determineFov( wm.stage.stageHeight, Math.abs( camera.pos.z ) );
    updateCameraPos = true;
  }

  // auto-rotation related
  public function applyAutoRotation() {
    mpos.appendRotation( arDegreeY, flash.geom.Vector3D.Y_AXIS );
    mpos.appendRotation( arDegreeX, flash.geom.Vector3D.X_AXIS );
  }

  public function beginAutoRotation():Void { arNow = true; }
  public function stopAutoRotation():Void { arNow = false; }

  // play mode
  public function pausePlay( ?e:MouseEvent = null ):Void { playingNow = false; }

  public function initPlay():Void {
    if ( wm.systems.length <= 1 ) return;
    wm.params.assignNumFramesPerScene();
    playReverse = false;
    playingNow = true;
    frameCounter = 0;
  }

  public function playForward( ?e:MouseEvent = null ):Void {
    if ( wm.systems.length <= 1 || myLoadSysTimer.running ) return;
    initPlay();
  }

  public function playBackward( ?e:MouseEvent = null ):Void {
    if ( wm.systems.length <= 1 || myLoadSysTimer.running ) return;
    initPlay();
    playReverse = true;
  }

  // scene migration
  public function forwardScene( ?e:MouseEvent = null ):Void {
    if ( myLoadSysTimer.running ) return;
    frameIndex = ( frameIndex + 1 ) % wm.systems.length;
    updateScene = true;
  }

  public function gotoLastScene( ?e:MouseEvent ):Void {
    if ( myLoadSysTimer.running ) return;
    frameIndex = wm.systems.length - 1;
    updateScene = true;
  }

  public function backScene( ?e:MouseEvent = null ):Void {
    if ( myLoadSysTimer.running ) return;
    if ( --frameIndex < 0 ) frameIndex = wm.systems.length + frameIndex;
    updateScene = true;
  }

  public function gotoInitScene( ?e:MouseEvent = null ):Void {
    if ( myLoadSysTimer.running ) return;
    frameIndex = 0;
    updateScene = true;
  }

  // timer (load systems)
  public function beginLoadSysTimer( ?delay:Float = 2.0 ):Void {
    myLoadSysTimer = new Timer( delay );
    myLoadSysTimer.addEventListener( TimerEvent.TIMER, wm.loadSystems );
    myLoadSysTimer.start();
  }

  public function stopLoadSysTimer():Void { myLoadSysTimer.stop(); }
  public function isLoadSysTimerRunning():Bool {
    if ( myLoadSysTimer != null ) {
      if ( myLoadSysTimer.running ) return( true );
    }
    return( false );
  }

  // frame counter
  //// count
  public function processCounter():Void {
    // this function is meaningful only when in play mode
    if ( isLoadSysTimerRunning() || wm.params.numFramesPerScene <= 0 ) return;
    if ( playingNow ) {
      if ( ++frameCounter >= wm.params.numFramesPerScene ) {
        frameCounter = 0;
        if ( playReverse ) {
          backScene();
        } else {
          forwardScene();
        }
      }
    }
  }
}
