import flash.display3D.Context3D;

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

import flash.events.TimerEvent;

import flash.utils.Timer;

class WMChain {
  public var chain( __getChain, null ):SmoothChain;
    public function __getChain():SmoothChain { return( chain ); }
  public var ribbons( __getRibbons, null ):Array< WMRibbon >;
    public function __getRibbons():Array< WMRibbon > { return( ribbons ); }
  public var coils( __getCoils, null ):Array< WMRibbon >;
    public function __getCoils():Array< WMRibbon > { return( coils ); }
  private var __indexes( null, null ):Array< Int >;
  public var genCompleted( __getCompleted, null ):Bool;
    public function __getCompleted():Bool { return( genCompleted ); }
  private var __keepDCActive( null, null ):Bool;
  private var __chainTimer( null, null ):Timer;
  private var __currentIndex( null, null ):Int;
  private var __objlist( null, null ):Array< Dynamic >;
  private var __c3d( null, null ):Context3D;

  // ######################################################################

  public function new() {
    ribbons = new Array< WMRibbon >();
    coils = new Array< WMRibbon >();
    genCompleted = false;
  }

  public function setPositions( pos:Array< Dynamic >,
                                ?ni:Int = 2 ):Void {
    var pa:Array< Dynamic > = new Array< Dynamic >();
    __indexes = new Array< Int >();
    var my_ni:Int = ni + 1;
    for ( p in pos ) {
      my_ni = ni + 1;
      if ( p.n != null && p.n >= 0 ) {
        my_ni = p.n + 1;
        pa.push( { p:Point3D.fromString( p.pos ), n:my_ni, dir:p.dir } );
      } else {
        pa.push( { p:Point3D.fromString( p.pos ), n:-1, dir:p.dir } );
      }
      for ( i in 0 ... my_ni ) __indexes.push( p.index );
    }
    // cut last my_ni - 1 elements ... in ad hoc way ... orz
    for ( i in 0 ... my_ni - 1 ) __indexes.pop();
    chain = new SmoothChain( pa, ni + 1 );
  }

  public function register( r:WMRibbon ):Void {
    var rc:WMRibbon = r.clone();
    var a:Array< Int > = __getIndex( rc.initXML, rc.lastXML );
    if ( a == null ) return; // invalid ribbon/coil. do nothing.
    rc.init = a[0];
    rc.last = a[1];
    // if init == last, that object should be ignored
    if ( rc.last > rc.init ) {
      if ( rc.isRibbon ) {
        ribbons.push( rc );
      } else {
        coils.push( rc );
      }
    }
  }

  public function gen( c:Context3D,
                       ?is_dc_active:Bool = false ):Void {
    if ( WMSystem.readChainAtOnce ) {
      for ( rb in ribbons ) rb.gen( c, chain, is_dc_active );
      for ( cl in coils ) cl.gen( c, chain, is_dc_active );
      genCompleted = true;
    } else {
      if ( __chainTimer == null ) {
        __keepDCActive = is_dc_active;
        __chainTimer = new Timer( 0.2 );
        __chainTimer.addEventListener( TimerEvent.TIMER, __genObjs );
        __chainTimer.start();
        __objlist = new Array< Dynamic >();
        for ( rb in ribbons ) __objlist.push( rb );
        for ( cl in coils ) __objlist.push( cl );
        __currentIndex = 0;
        __c3d = c;
      }
    }
  }

  private function __genObjs( ?e:TimerEvent ):Void {
    if ( __currentIndex < __objlist.length ) {
      __objlist[__currentIndex++].gen( __c3d, chain, __keepDCActive );
    }
    if ( __currentIndex == __objlist.length ) {
      genCompleted = true;
      __chainTimer.stop();
      __chainTimer.removeEventListener( TimerEvent.TIMER, __genObjs );
      __chainTimer = null;
    }
  }

  public function dump():String {
    var ret:String = "";
    for ( rb in ribbons ) ret += rb.dump();
    for ( cl in coils ) ret += cl.dump();
    return( ret );
  }

  public function draw( c:Context3D,
                        mpos:Matrix3D,
                        proj:Matrix3D,
                        voffset:Vector3D,
                        ?light:Vector3D = null,
                        ?cpos:Vector3D = null,
                        ?dcActive:Bool = false,
                        ?dcCoeff:Float = 0.0,
                        ?dcLength:Float = 0.0 ):Bool {
    for ( rb in ribbons ) {
      if ( !rb.draw( c, mpos, proj, voffset, light, cpos,
                     dcActive, dcCoeff, dcLength ) ) return( false );
    }
    for ( cl in coils ) {
      if ( !cl.draw( c, mpos, proj, voffset, light, cpos,
                     dcActive, dcCoeff, dcLength ) ) return( false );
    }
    return( true );
  }

  private function __getIndex( init:Int,
                               last:Int ):Array< Int > {
    var ret:Array< Int > = new Array< Int >();
    var keep:Int = -1;
    // dirty code ... orz
    var flag:Bool = false;
    for ( i in 0 ... __indexes.length ) {
      if ( init == __indexes[i] && !flag ) {
        ret.push( i );
        flag = true;
      }
      if ( last < __indexes[i] && flag ) {
        ret.push( i );
        break;
      }
      if ( last == __indexes[i] && flag ) keep = i + 1;
    }
    if ( ret.length == 1 && keep > 0 ) ret.push( keep );
    if ( ret.length != 2 ) {
      trace( "WMChain::getIndex: unexpected error. Invalid \"init\" and \"last\" are given?" );
      return( null );
    }
    return( ret );
  }

  public function getPositions():Array< Point3D > {
    return( chain.getPositions() );
  }

  public function num():Int {
    return( chain.controls.length );
  }
  public function sumPos():Point3D {
    var ret:Point3D = new Point3D( 0, 0, 0 );
    for ( cont in chain.controls ) ret.add( cont.pos );
    return( ret );
  }
  public function translate( p:Point3D ):Void {
    for ( cont in chain.controls ) cont.pos.add( p );
  }
  public function absmax():Point3D {
    var ret:Point3D = new Point3D( 0, 0, 0 );
    for ( cont in chain.controls ) {
      ret.x = Math.max( Math.abs( cont.pos.x ), ret.x );
      ret.y = Math.max( Math.abs( cont.pos.y ), ret.y );
      ret.z = Math.max( Math.abs( cont.pos.z ), ret.z );
    }
    return( ret );
  }
  public function scaleCoord( scale:Float ):Void {
    for ( cont in chain.controls ) {
      cont.pos.multiply( scale );
    }
  }
  public function getDataSize():Int { return( chain.controls.length ); }
}
