import flash.display.Sprite;
import flash.display.Stage;
import flash.display.Stage3D;
import flash.display3D.Context3D;
import flash.display3D.Context3DBlendFactor;

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

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

import flash.errors.Error;
import flash.errors.SecurityError;

import flash.ui.Keyboard;

import flash.net.FileReference;

import flash.text.TextField;
import flash.text.TextFormat;

import flash.Vector;
import flash.utils.Timer;

import flash.xml.XML;
import flash.xml.XMLList;
import flash.xml.XMLParser;

import xpath.XPath;
import xpath.xml.XPathXml;
import xpath.xml.XPathHxXml;

class Watermelon {
  static public var version:String = "0.23";

  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;

  private var stage( null, null ):Stage;
  private var stage3d( null, null ):Stage3D;
  private var c3d( null, null ):Context3D;

  public var myString( __getMyString, __setMyString ):String;
  private var myXml( null, null ):Xml;
  private var frameIndex( null, null ):Int;
  private var __readingFrame( null, null ):Int;
  public var camera( __getCamera, null ):Camera;
  public var systems( __getSystems, null ):Array< WMSystem >;
  public var light( __getLight, __setLight ):Vector3D;
  public var buttonWidth( __getButtonWidth, __setButtonWidth ):Float;
  public var buttonHeight( __getButtonHeight, __setButtonHeight ):Float;

  private var mpos( null, null ):Matrix3D;
  private var cpos( null, null ):Vector3D;
  private var view_offset( null, null ):Vector3D;

  public var arDegree( __getARDegree, __setARDegree ):Float;
  public var arDegreeX( __getARDegreeX, __setARDegreeX ):Float;
  public var arDegreeY( __getARDegreeY, __setARDegreeY ):Float;

  public var scaleWheel( __getScaleWheel, __setScaleWheel ):Float;
  public var depthWheel( __getDepthWheel, __setDepthWheel ):Float;

  private var mouseModeL( null, null ):Int;
  private var mouseModeW( null, null ):Int;

  // work
  private var __mousex( null, null ):Float;
  private var __mousey( null, null ):Float;
  private var __mousepx( null, null ):Float;
  private var __mousepy( null, null ):Float;

  private var __keep_width( null, null ):Int;
  private var __keep_height( null, null ):Int;

  // temporary sprites
  private var __about( null, null ):Sprite;
  private var __edit( null, null ):Sprite;

  // permanent sprites; controller and buttons on it
  private var __controller( null, null ):Sprite;
  private var __buttonForward( null, null ):Sprite;
  private var __buttonLast( null, null ):Sprite;
  private var __buttonBack( null, null ):Sprite;
  private var __buttonInit( null, null ):Sprite;
  private var __buttonStop( null, null ):Sprite;
  private var __player( null, null ):Sprite;
  private var __buttonPlayForward( null, null ):Sprite;
  private var __buttonPlayBackward( null, null ):Sprite;
  private var __scaler( null, null ):Sprite;
  private var __scalerInitialize( null, null ):Sprite;
  private var __scalerEnlarge( null, null ):Sprite;
  private var __scalerShrink( null, null ):Sprite;

  // flags
  private var __arNow( null, null ):Bool;
  private var __updateCameraPos( null, null ):Bool;
  private var __updateMPos( null, null ):Bool;
  private var __updateViewOffset( null, null ):Bool;
  private var __updateScene( null, null ):Bool;

  private var __busyNow( null, null ):Bool;
  private var __protectData( null, null ):Bool;

  private var __readBackground( null, null ):Bool;

  // misc.
  private var playFrameRate( null, null ):Float; // frames/sec
  private var __numFramesPerScene( null, null ):Int;
  private var __frameCounterForPlay( null, null ):Int;
  private var __playReverseMode( null, null ):Bool;
  private var __playing( null, null ):Bool;

  private var __defInheritScale( null, null ):Bool;
  private var __defAutoScale( null, null ):Bool;
  private var __defScaleFactor( null, null ):Float;
  private var __defScaleFactorManual( null, null ):Float;

  private var myTimer( null, null ):Timer;
  private var __aliases( null, null ):Array< WMAlias >;

  public function new() {
    stage = flash.Lib.current.stage;
    stage.scaleMode = flash.display.StageScaleMode.NO_SCALE;
    stage3d = stage.stage3Ds[0];

    // register callback for webpage
    if ( flash.external.ExternalInterface.available ) {
      try {
        flash.external.ExternalInterface.addCallback( "Redraw", this.visualize );
        flash.external.ExternalInterface.addCallback( "GetXML", this.__getMyString );
        flash.external.ExternalInterface.addCallback( "GetView", this.getCurrentViewInXml );
      } catch( e:SecurityError ) {
        // do nothing, just ignore
        // ad hoc solution for powerpoint, where ExternalInterface.available
        // is true but ExternalInterface.addCallback is not available
      }
    }

    visualize();
  }

  // set default values
  private function __initializeVariables():Void {
    // read XML data from resource file attached in compilation phase.
    // -resource (filename):structure
    myString = haxe.Resource.getString( "structure" );

    arDegree = 1.5;
    arDegreeX = 0.0;
    arDegreeY = arDegree;
    mouseModeL = Watermelon.MOUSE_L_ROTAT_MODE; // rotation mode
    //mouseModeL = Watermelon.MOUSE_L_TRANS_MODE; // translation mode
    mouseModeW = Watermelon.MOUSE_W_SCALE_MODE; // scale mode
    //mouseModeW = Watermelon.MOUSE_W_DEPTH_MODE; // depth mode
    scaleWheel = 0.01;
    depthWheel = 0.04 * Math.max( stage.stageWidth, stage.stageHeight );
    WMBase.setScaleBase( stage );
    systems = new Array< WMSystem >();
    camera = new Camera();
    camera.pos.z = -Math.max( stage.stageWidth, stage.stageHeight ) * 2;
    camera.ratio = stage.stageWidth / stage.stageHeight;
    camera.determineFov( stage.stageHeight, Math.abs( camera.pos.z ) );
    __keep_width = stage.stageWidth;
    __keep_height = stage.stageHeight;
    light = new Vector3D( 1, -1, -1 );
    buttonWidth = 20;
    buttonHeight = 20;
    light.normalize();
    cpos = new Vector3D();
    mpos = new Matrix3D();
    mpos.identity();
    playFrameRate = 3.0; // 3 frames per sec (at most)
    view_offset = new Vector3D();
    __arNow = true;
    __busyNow = false;
    __protectData = false;
    __updateCameraPos = true;
    __updateMPos = true;
    __updateViewOffset = false;
    __updateScene = true;
    __playing = false;
    __playReverseMode = false;
    __readBackground = false;
    __numFramesPerScene = -1;
    frameIndex = 0;
    __readingFrame = 0;
    __generateController();
    __defInheritScale = true;
    __defAutoScale = true;
    __defScaleFactor = 0.3;
    __defScaleFactorManual = 10.0;
    __aliases = new Array< WMAlias >();
  }

  public function visualize( ?str:String = null ):Void {
    __initializeVariables();
    if ( str != null ) myString = str;
    //myXml = __parseMyStringAliases();
    myXml = Xml.parse( myString );
    flash.Boot.__clear_trace();
    haxe.Log.setColor( 0xFF0000 );
    stage3d.addEventListener( Event.CONTEXT3D_CREATE, onReady );
    stage3d.requestContext3D();
  }

  public function onReady( _ ) {
    __removeAllStageEvents();
    c3d = stage3d.context3D;
    //c3d.enableErrorChecking = true;
    c3d.enableErrorChecking = false;
    c3d.configureBackBuffer( stage.stageWidth, stage.stageHeight, 0, true );
    // parse global settings
    var xml_global:XPath = new XPath( "WMXML/GLOBAL" );
    __loadGlobalSettings( xml_global.selectNodes( XPathHxXml.wrapNode( myXml ) ) );
    var xml_alias:XPath = new XPath( "WMXML/ALIAS" );
    __loadAliases( xml_alias.selectNodes( XPathHxXml.wrapNode( myXml ) ) );
    var xml_scene:XPath = new XPath( "WMXML/SCENE" );
    var nodes:Iterable< XPathXml > = xml_scene.selectNodes( XPathHxXml.wrapNode( myXml ) );
    systems = [];
    for ( nd in nodes ) {
      var sys:WMSystem = new WMSystem();
      __setDefalutValues( sys );
      // only register xml data here
      sys.registerXml( cast( nd, XPathHxXml ).getWrappedXml() );
      systems.push( sys );
    }
    if ( systems.length == 0 ) {
      systems.push( new WMSystem() );
    } else {
      systems[0].gen( c3d, __aliases );
    }
    stage.addEventListener( Event.ENTER_FRAME, render );
    stage.addEventListener( Event.RESIZE, __resize );
    __beginStandardHandlers();
    myTimer = new Timer( 50, systems.length );
    myTimer.addEventListener( TimerEvent.TIMER, __loadSystems );
    myTimer.start();
  }

  // timer event
  private function __loadSystems( ?e:TimerEvent = null ):Void {
    if ( __readingFrame == ( systems.length - 1 ) ) {
      myTimer.stop();
      // finish reading
      if ( systems.length > 1 && __playing ) {
        if ( __playReverseMode ) {
          __playBackward();
        } else {
          __playForward();
        }
      } else {
        __pausePlay();
        __playReverseMode = false;
      }
    } else {
      __setDefalutValues( systems[++__readingFrame] );
      systems[__readingFrame].gen( c3d, __aliases );
      if ( !__readBackground ) frameIndex = __readingFrame;
      if ( e != null ) e.updateAfterEvent();
    }
  }

  // set default variables to new WMSystem
  private function __setDefalutValues( sys:WMSystem ):Void {
    sys.scaleFactor = __defScaleFactor;
    if ( __defInheritScale && systems.length != 0 ) {
      sys.autoScale = false;
      sys.scaleFactorManual = systems[0].scaleFactorManual;
    } else {
      sys.autoScale = __defAutoScale;
      sys.scaleFactorManual = __defScaleFactorManual;
    }
  }

  private function __removeAllStageEvents() {
    if ( stage.hasEventListener( Event.ENTER_FRAME ) ) {
      stage.removeEventListener( Event.ENTER_FRAME, render );
    }
    if ( stage.hasEventListener( Event.RESIZE ) ) {
      stage.removeEventListener( Event.RESIZE, __resize );
    }
    if ( stage.hasEventListener( KeyboardEvent.KEY_DOWN ) ) {
      stage.removeEventListener( KeyboardEvent.KEY_DOWN, __pressKey );
    }
    if ( stage.hasEventListener( MouseEvent.MOUSE_DOWN ) ) {
      stage.removeEventListener( MouseEvent.MOUSE_DOWN, __mouseDown );
    }
    if ( stage.hasEventListener( MouseEvent.MOUSE_UP ) ) {
      stage.removeEventListener( MouseEvent.MOUSE_UP, __mouseUp );
    }
    if ( stage.hasEventListener( MouseEvent.MOUSE_WHEEL ) ) {
      stage.removeEventListener( MouseEvent.MOUSE_WHEEL, __mouseWheel );
    }
    if ( stage.hasEventListener( MouseEvent.MOUSE_MOVE ) ) {
      stage.removeEventListener( MouseEvent.MOUSE_MOVE, __mouseMove );
    }
  }

  public function render( _ ) {
    __processCounter();
    if ( c3d == null || !__needToUpdate() ) return;
    if ( __arNow ) __applyAutoRotation();
    var bgcolor:Int = systems[frameIndex].bgcolor;
    c3d.clear( cast( ( bgcolor & 0xFF0000 ) >> 16, Float ) / 255.0,
               cast( ( bgcolor & 0x00FF00 ) >> 8, Float ) / 255.0,
               cast( ( bgcolor & 0x0000FF ), Float ) / 255.0, 1.0 );
    c3d.setDepthTest( true, flash.display3D.Context3DCompareMode.LESS );
    c3d.setBlendFactors( Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA );
    c3d.setCulling( flash.display3D.Context3DTriangleFace.BACK );

    camera.update();
    var proj:Matrix3D = camera.m.toMatrix3D();
    if ( __updateCameraPos ) __updateCPOS();
    // draw returns false if error, such as context3d disposed, occurs.
    if ( systems[frameIndex].draw( c3d, mpos, proj, view_offset, light, cpos ) ) {
      // standard situation
      try {
        c3d.present();
      } catch( e:Error ) {
        // c3d is disposed in very good timing?
        //trace( "failed to present" );
        __updateScene = true;
      }
      __resetFlags();
    } else {
      // if fails to draw triangles, do nothing
      // new CONTEXT3D_CREATE event would be invoked automatically
      //trace( "failed to draw" );
      __resetFlags();
      __updateScene = true;
    };
  }

  private function __processCounter():Void {
    if ( myTimer.running || __numFramesPerScene <= 0 ) return;
    // play
    if ( __playing ) {
      if ( ++__frameCounterForPlay >= __numFramesPerScene ) {
        __frameCounterForPlay = 0;
        if ( __playReverseMode ) {
          __backScene();
        } else {
          __forwardScene();
        }
      }
    }
  }

  // XML
  private function __loadGlobalSettings( ?nodes:Iterable< XPathXml > = null ):Void {
    for ( nd in nodes ) {
      __parseGlobalSettingAttributes( cast( nd, XPathHxXml ).getWrappedXml() );
      var ndio:Xml = cast( nd, XPathHxXml ).getWrappedXml();
      for ( ndc in ndio.elements() ) {
        var nn:String = ndc.nodeName.toUpperCase();
        switch( nn ) {
          case "ATOM":
            WMDefaults.gl_Atom.loadFromXml( ndc );
          case "BOND":
            WMDefaults.gl_Bond.loadFromXml( ndc );
            var strs = [ "rounded", "round" ];
            for ( s in strs ) {
              if ( ndc.exists( s ) ) {
                WMDefaults.gl_BondRound = ( Std.parseInt( ndc.get( s ) ) > 0 );
              }
            }
            strs = [ "exc", "exclude" ];
            for ( s in strs ) {
              if ( ndc.exists( s ) ) {
                WMDefaults.gl_BondExclude = ( Std.parseInt( ndc.get( s ) ) > 0 );
              }
            }
            strs = [ "dash", "dashed" ];
            for ( s in strs ) {
              if ( ndc.exists( s ) ) {
                WMDefaults.gl_BondDashed = Std.parseInt( ndc.get( s ) );
              }
            }
          case "RIBBON":
            WMDefaults.gl_Ribbon.loadFromXml( ndc );
            var strs = [ "thick", "thickness" ];
            for ( s in strs ) {
              if ( ndc.exists( s ) ) {
                WMDefaults.gl_RibbonThickness = Std.parseFloat( ndc.get( s ) );
              }
            }
          case "COIL":
            WMDefaults.gl_Coil.loadFromXml( ndc );
          case "SHAPE":
            WMDefaults.gl_Shape.loadFromXml( ndc );
          case "OBJ3D":
            WMDefaults.gl_Object3D.loadFromXml( ndc );
            if ( ndc.exists( "type" ) ) WMDefaults.gl_Object3DType = ndc.get( "type" );
          case "LABEL":
            WMDefaults.gl_Label.loadFromXml( ndc );
            if ( ndc.exists( "font" ) ) WMDefaults.gl_LabelFont = ndc.get( "font" );
            if ( ndc.exists( "fontsize" ) ) WMDefaults.gl_LabelSize = Std.parseFloat( ndc.get( "fontsize" ) );
          case "CAMERA":
            if ( ndc.exists( "z" ) ) {
              setCameraPosZ( Std.parseFloat( ndc.get( "z" ) ) );
            }
          case "PROTECT":
            setProtectData();
          case "WHEEL":
            if ( ndc.exists( "scale" ) ) {
              setScaleWheel( Std.parseFloat( ndc.get( "scale" ) ) );
            }
            if ( ndc.exists( "depth" ) ) {
              setScaleWheel( Std.parseFloat( ndc.get( "depth" ) ) );
            }
          case "MATRIX":
            if ( ndc.exists( "data" ) ) {
              var m:Matrix4 = Matrix4.fromString( ndc.get( "data" ) );
              setInitialView( m );
            }
          case "RADIUS":
            if ( ndc.exists( "method" ) ) {
              var met:String = ndc.get( "method" );
              if ( met.toLowerCase() == "absolute" ) {
                WMBase.useRelative = false;
              } else if ( met.toLowerCase() == "relative" ) {
                WMBase.useRelative = true;
              }
            }
            if ( ndc.exists( "scale" ) ) {
              WMBase.characteristicSize = Std.parseFloat( ndc.get( "scale" ) );
            }
          case "AUTOSCALE":
            if ( ndc.exists( "manual" ) ) {
              var n:Int = Std.parseInt( ndc.get( "manual" ) );
              if ( n > 0 ) __defAutoScale = false;
            }
            if ( ndc.exists( "autoscale" ) ) {
              __defScaleFactor = Std.parseFloat( ndc.get( "autoscale" ) );
            }
            if ( ndc.exists( "manualscale" ) ) {
              __defScaleFactorManual = Std.parseFloat( ndc.get( "manualscale" ) );
            }
            if ( ndc.exists( "inherit" ) ) {
              __defInheritScale = ( Std.parseInt( ndc.get( "inherit" ) ) > 0 );
            }
        }
      }
    }
  }

  private function __parseGlobalSettingAttributes( nd:Xml ):Void {
    if ( nd.exists( "light" ) ) {
      setLightDirection( Point3D.fromString( nd.get( "light" ) ) );
    }
    if ( nd.exists( "arrate" ) ) {
      setAutoRotationRate( Std.parseFloat( nd.get( "arrate" ) ) );
    }
    if ( nd.exists( "framerate" ) ) {
      setFrameRate( Std.parseInt( nd.get( "framerate" ) ) );
    }
    if ( nd.exists( "play" ) ) {
      var v:Int = Std.parseInt( nd.get( "play" ) );
      if ( v > 0 ) {
        __playing = true;
        __playReverseMode = false;
      } else if ( v < 0 ) {
        __playing = true;
        __playReverseMode = true;
      }
    }
    if ( nd.exists( "playrate" ) ) {
      playFrameRate = Std.parseFloat( nd.get( "playrate" ) );
    }
    var strs = [ "readbackground", "bgread", "readbg" ];
    for ( s in strs ) {
      // never mind the value
      if ( nd.exists( s ) ) __readBackground = true;
    }
  }

  private function __loadAliases( ?nodes:Iterable< XPathXml > = null ):Void {
    for ( ndc in nodes ) {
      var nd:Xml = cast( ndc, XPathHxXml ).getWrappedXml();
      // register an alias
      // this will still violate standards of XML, though
      for ( ndcc in nd.elements() ) {
        if ( ndcc.exists( "elem" ) ) {
          var wmal:WMAlias = new WMAlias();
          wmal.register( ndcc.nodeName.toUpperCase(), ndcc );
          __aliases.push( wmal );
          //trace( "wm3d: register alias " + wmal.name );
        } else {
          // alias without "elem" attribute is ignored
          trace( "wm3d::alias - elem attribute is required for an alias" );
        }
      }
    }
  }

  private function __applyAutoRotation() {
    mpos.appendRotation( arDegreeY, flash.geom.Vector3D.Y_AXIS );
    mpos.appendRotation( arDegreeX, flash.geom.Vector3D.X_AXIS );
  }

  private function __updateCPOS():Void {
    cpos.x = camera.pos.x;
    cpos.y = camera.pos.y;
    cpos.z = camera.pos.z;
    __updateCameraPos = false;
  }

  private function __needToUpdate():Bool {
    return( __updateCameraPos || __updateMPos || __arNow || __updateViewOffset || __updateScene );
  }

  private function __resetFlags():Void {
    __updateCameraPos = false;
    __updateMPos = false;
    __updateViewOffset = false;
    __updateScene = false;
  }

  private function __hasController():Bool {
    if ( __controller != null ||
         __buttonForward != null || __buttonBack != null ||
         __buttonLast != null || __buttonInit != null ||
         __player != null || __buttonStop != null ||
         __buttonPlayForward != null ||
         __buttonPlayBackward != null ||
         __scaler != null ||
         __scalerEnlarge != null || __scalerShrink != null ) return( true );
    return( false );
  }

  private function __generateController():Void {
    if ( __hasController() ) return;
    // controller's backgroundcolor is transparent
    __controller = new Sprite();
    __controller.graphics.beginFill( 0xFFFFFF, 0.0 );
    __controller.graphics.drawRect( 0, 0, stage.stageWidth, buttonHeight );
    __controller.graphics.endFill();
    __controller.alpha = 0.0;
    // generate individual buttons
    __genForwardButton();
    __genLastFrameButton();
    __genBackButton();
    __genInitFrameButton();
    __genPlayer();
    __genScaler();

    // set events
    __addControllerEventListeners( __controller );
    __buttonForward.addEventListener( MouseEvent.CLICK, __forwardScene );
    __buttonBack.addEventListener( MouseEvent.CLICK, __backScene );
    __buttonLast.addEventListener( MouseEvent.CLICK, __gotoLastScene );
    __buttonInit.addEventListener( MouseEvent.CLICK, __gotoInitScene );
    // set position
    __buttonForward.x = stage.stageWidth - 2 * buttonWidth;
    __buttonLast.x = stage.stageWidth - buttonWidth;
    __buttonBack.x = buttonWidth;
    __buttonInit.x = 0;
    stage.addChild( __controller );
    __controller.y = stage.stageHeight - buttonHeight;
  }

  private function __genPlayer():Void {
    __player = new Sprite();
    __buttonStop = new Sprite();
    __buttonStop.graphics.beginFill( 0xFFFFFF, 0.0 );
    __buttonStop.graphics.drawRect( 0, 0, buttonWidth, buttonHeight );
    __buttonStop.graphics.endFill();
    __buttonStop.graphics.beginFill( 0x888888, 1.0 );
    __buttonStop.graphics.drawRect( 2, 2, buttonWidth - 4, buttonHeight - 4 );
    __buttonStop.graphics.endFill();
    __player.graphics.beginFill( 0xFFFFFF, 0.0 );
    __player.graphics.drawRect( 0, 0, __buttonStop.width + 2 * buttonWidth, buttonHeight );
    __player.graphics.endFill();
    __player.addChild( __buttonStop );
    __buttonStop.x = buttonWidth;
    __buttonStop.addEventListener( MouseEvent.CLICK, __pausePlay );

    __genPlayForwardButton();
    __genPlayBackwardButton();
    __player.addChild( __buttonPlayForward );
    __player.addChild( __buttonPlayBackward );
    __buttonPlayForward.x = __player.width - buttonWidth;
    __buttonPlayBackward.x = 0;
    __buttonPlayForward.addEventListener( MouseEvent.CLICK, __playForward );
    __buttonPlayBackward.addEventListener( MouseEvent.CLICK, __playBackward );

    __controller.addChild( __player );
    __player.x = stage.stageWidth / 2 - __player.width - buttonWidth;
  }

  private function __genPlayForwardButton():Void {
    __buttonPlayForward = new Sprite();
    __buttonPlayForward.graphics.beginFill( 0xFFFFFF, 0.0 );
    __buttonPlayForward.graphics.drawRect( 0, 0, buttonWidth, buttonHeight );
    __buttonPlayForward.graphics.endFill();
    __buttonPlayForward.graphics.lineStyle( 3, 0x888888, 1.0 );
    __buttonPlayForward.graphics.moveTo( 2, buttonHeight / 2 );
    __buttonPlayForward.graphics.lineTo( buttonWidth - 2, buttonHeight / 2 );
    __buttonPlayForward.graphics.lineTo( buttonWidth / 2, 2 );
    __buttonPlayForward.graphics.moveTo( buttonWidth - 2, buttonHeight / 2 );
    __buttonPlayForward.graphics.lineTo( buttonWidth / 2, buttonHeight - 2 );
  }

  private function __genPlayBackwardButton():Void {
    __buttonPlayBackward = new Sprite();
    __buttonPlayBackward.graphics.beginFill( 0xFFFFFF, 0.0 );
    __buttonPlayBackward.graphics.drawRect( 0, 0, buttonWidth, buttonHeight );
    __buttonPlayBackward.graphics.endFill();
    __buttonPlayBackward.graphics.lineStyle( 3, 0x888888, 1.0 );
    __buttonPlayBackward.graphics.moveTo( buttonWidth - 2, buttonHeight / 2 );
    __buttonPlayBackward.graphics.lineTo( 2, buttonHeight / 2 );
    __buttonPlayBackward.graphics.lineTo( buttonWidth / 2, 2 );
    __buttonPlayBackward.graphics.moveTo( 2, buttonHeight / 2 );
    __buttonPlayBackward.graphics.lineTo( buttonWidth / 2, buttonHeight - 2 );
  }

  private function __genScaler():Void {
    __scaler = new Sprite();
    __scaler.graphics.beginFill( 0xFFFFFF, 0.0 );
    __scaler.graphics.drawRect( 0, 0, 3 * buttonWidth + 2, buttonHeight );
    __scaler.graphics.endFill();
    __scalerInitialize = new Sprite();
    __scalerInitialize.graphics.beginFill( 0xFFFFFF, 0.0 );
    __scalerInitialize.graphics.drawRect( 0, 0, buttonWidth, buttonHeight );
    __scalerInitialize.graphics.endFill();
    __scalerInitialize.graphics.lineStyle( 3.0, 0x888888, 1.0 );
    var rad:Float = 0.35 * Math.min( buttonWidth, buttonHeight );
    __scalerInitialize.graphics.drawCircle( 0.65 * buttonWidth,
                                            0.35 * buttonHeight, rad );
    var vec:Float = 1.0 / Math.sqrt( 2 );
    var vm:Float = 0.65 * Math.min( buttonWidth, buttonHeight );
    __scalerInitialize.graphics.moveTo( 0.65 * buttonWidth - rad * vec,
                                        0.35 * buttonHeight + rad * vec );
    __scalerInitialize.graphics.lineTo( 0.65 * buttonWidth - vm,
                                        0.35 * buttonHeight + vm );
    __scalerInitialize.graphics.endFill();
    __scaler.addChild( __scalerInitialize );

    __scalerInitialize.x = buttonWidth;
    var ref = this;
    __scalerInitialize.addEventListener( MouseEvent.CLICK,
                                         function( e:MouseEvent ) {
                                           var v3:Vector<Vector3D> = new Vector<Vector3D>(3);
                                           v3 = this.mpos.decompose();
                                           v3[2].setTo( 1.0, 1.0, 1.0 );
                                           this.mpos.recompose( v3 );
                                           this.__updateMPos = true;
                                         }
                                       );
    __genEnlargeButton();
    __genShrinkButton();
    __scalerEnlarge.x = __scaler.width - buttonWidth;
    __scalerShrink.x = 0;
    __scalerEnlarge.addEventListener( MouseEvent.CLICK,
                                      function( e:MouseEvent ) {
                                        changeScale( 5 );
                                      }
                                    );
    __scalerShrink.addEventListener( MouseEvent.CLICK,
                                      function( e:MouseEvent ) {
                                        changeScale( -5 );
                                      }
                                    );

    __controller.addChild( __scaler );
    __scaler.x = stage.stageWidth / 2 + buttonWidth;
  }

  private function __genEnlargeButton():Void {
    __scalerEnlarge = new Sprite();
    __scalerEnlarge.graphics.beginFill( 0xFFFFFF, 0.0 );
    __scalerEnlarge.graphics.drawRect( 0, 0, buttonWidth, buttonHeight );
    __scalerEnlarge.graphics.endFill();
    __scalerEnlarge.graphics.lineStyle( 3, 0x888888, 1.0 );
    __scalerEnlarge.graphics.moveTo( 2, buttonHeight / 2 );
    __scalerEnlarge.graphics.lineTo( buttonWidth - 2, buttonHeight / 2 );
    __scalerEnlarge.graphics.moveTo( buttonWidth / 2, 2 );
    __scalerEnlarge.graphics.lineTo( buttonWidth / 2, buttonHeight - 2 );
    __scaler.addChild( __scalerEnlarge );
  }

  private function __genShrinkButton():Void {
    __scalerShrink = new Sprite();
    __scalerShrink.graphics.beginFill( 0xFFFFFF, 0.0 );
    __scalerShrink.graphics.drawRect( 0, 0, buttonWidth, buttonHeight );
    __scalerShrink.graphics.endFill();
    __scalerShrink.graphics.lineStyle( 3, 0x888888, 1.0 );
    __scalerShrink.graphics.moveTo( 2, buttonHeight / 2 );
    __scalerShrink.graphics.lineTo( buttonWidth - 2, buttonHeight / 2 );
    __scaler.addChild( __scalerShrink );
  }

  private function __addControllerEventListeners( s:Sprite ):Void {
    var ref = this;
    var mybut = s; // why this line is necessary?
    s.addEventListener( MouseEvent.ROLL_OVER,
                        function( e:Event ) {
                          mybut.alpha = 1.0;
                        } );
    s.addEventListener( MouseEvent.ROLL_OUT,
                        function( e:Event ) {
                          mybut.alpha = 0.0;
                        } );
  }

  private function __genForwardButton():Void {
    __buttonForward = new Sprite();
    __buttonForward.graphics.beginFill( 0xFFFFFF, 0.0 );
    __buttonForward.graphics.drawRect( 0, 0, buttonWidth, buttonHeight );
    __buttonForward.graphics.endFill();
    __buttonForward.graphics.lineStyle( 0, 0x888888, 1.0 );
    __buttonForward.graphics.beginFill( 0x888888, 1.0 );
    __buttonForward.graphics.moveTo( 2, 2 );
    __buttonForward.graphics.lineTo( 2, buttonHeight - 2 );
    __buttonForward.graphics.lineTo( buttonWidth - 2, buttonHeight / 2 );
    __buttonForward.graphics.lineTo( 2, 2 );
    __buttonForward.graphics.endFill();
    __controller.addChild( __buttonForward );
  }

  private function __genLastFrameButton():Void {
    __buttonLast = new Sprite();
    __buttonLast.graphics.beginFill( 0xFFFFFF, 0.0 );
    __buttonLast.graphics.drawRect( 0, 0, buttonWidth, buttonHeight );
    __buttonLast.graphics.endFill();
    __buttonLast.graphics.lineStyle( 0, 0x888888, 1.0 );
    __buttonLast.graphics.beginFill( 0x888888, 1.0 );
    __buttonLast.graphics.moveTo( 2, 2 );
    __buttonLast.graphics.lineTo( 2, buttonHeight - 2 );
    __buttonLast.graphics.lineTo( buttonWidth / 2, buttonHeight / 2 );
    __buttonLast.graphics.lineTo( 2, 2 );
    __buttonLast.graphics.moveTo( buttonWidth / 2, 2 );
    __buttonLast.graphics.lineTo( buttonWidth / 2, buttonHeight - 2 );
    __buttonLast.graphics.lineTo( buttonWidth - 2, buttonHeight / 2 );
    __buttonLast.graphics.lineTo( buttonWidth / 2, 2 );
    __buttonLast.graphics.endFill();
    __controller.addChild( __buttonLast );
  }

  private function __genBackButton():Void {
    __buttonBack = new Sprite();
    __buttonBack.graphics.beginFill( 0xFFFFFF, 0.0 );
    __buttonBack.graphics.drawRect( 0, 0, buttonWidth, buttonHeight );
    __buttonBack.graphics.endFill();
    __buttonBack.graphics.lineStyle( 0, 0x888888, 1.0 );
    __buttonBack.graphics.beginFill( 0x888888, 1.0 );
    __buttonBack.graphics.moveTo( buttonWidth - 2, 2 );
    __buttonBack.graphics.lineTo( buttonWidth - 2, buttonHeight - 1 );
    __buttonBack.graphics.lineTo( 2, buttonHeight / 2 );
    __buttonBack.graphics.lineTo( buttonWidth - 2, 2 );
    __buttonBack.graphics.endFill();
    __controller.addChild( __buttonBack );
  }

  private function __genInitFrameButton():Void {
    __buttonInit = new Sprite();
    __buttonInit.graphics.beginFill( 0xFFFFFF, 0.0 );
    __buttonInit.graphics.drawRect( 0, 0, buttonWidth, buttonHeight );
    __buttonInit.graphics.endFill();
    __buttonInit.graphics.lineStyle( 0, 0x888888, 1.0 );
    __buttonInit.graphics.beginFill( 0x888888, 1.0 );
    __buttonInit.graphics.moveTo( buttonWidth - 2, 2 );
    __buttonInit.graphics.lineTo( buttonWidth - 2, buttonHeight - 2 );
    __buttonInit.graphics.lineTo( buttonWidth / 2, buttonHeight / 2 );
    __buttonInit.graphics.lineTo( buttonWidth - 2, 2 );
    __buttonInit.graphics.moveTo( buttonWidth / 2, 2 );
    __buttonInit.graphics.lineTo( buttonWidth / 2, buttonHeight - 2 );
    __buttonInit.graphics.lineTo( 2, buttonHeight / 2 );
    __buttonInit.graphics.lineTo( buttonWidth / 2, 2 );
    __buttonInit.graphics.endFill();
    __controller.addChild( __buttonInit );
  }

  //// Event-related functions

  private function __beginAutoRotation():Void {
    __arNow = true;
  }

  private function __stopAutoRotation():Void {
    __arNow = false;
  }

  private function __beginStandardHandlers():Void {
    __beginStandardMouseHandlers();
    stage.addEventListener( KeyboardEvent.KEY_DOWN, __pressKey );
  }

  private function __beginStandardMouseHandlers():Void {
    stage.addEventListener( MouseEvent.MOUSE_DOWN, __mouseDown );
    stage.addEventListener( MouseEvent.MOUSE_UP, __mouseUp );
    stage.addEventListener( MouseEvent.MOUSE_WHEEL, __mouseWheel );
  }

  private function __beginInteractiveTransform():Void {
    stage.addEventListener( MouseEvent.MOUSE_MOVE, __mouseMove );
  }

  private function __stopInteractiveTransform():Void {
    if ( stage.hasEventListener( MouseEvent.MOUSE_MOVE ) ) {
      stage.removeEventListener( MouseEvent.MOUSE_MOVE, __mouseMove );
    }
  }

  private function __mouseDown( event:MouseEvent ):Void {
    // do nothing when editing or doing something
    if ( __busyNow ) return;
    // do nothing when button, such as forward or back, is clicked
    if ( __onController( event.stageX, event.stageY ) ) return;
    __stopAutoRotation();
    __mousex = __mousepx = event.stageX;
    __mousey = __mousepy = event.stageY;
    __beginInteractiveTransform();
  }

  private function __onController( mx:Float,
                                   my:Float ):Bool {
    if ( my >= ( stage.stageHeight - buttonHeight ) ) return( true );
    return( false );
  }

  private function __mouseUp( event:MouseEvent ):Void {
    if ( __busyNow ) return;
    if ( !stage.hasEventListener( MouseEvent.MOUSE_MOVE ) ) return;
    __stopInteractiveTransform();
    var movex:Float = event.stageX - __mousepx;
    var movey:Float = event.stageY - __mousepy;
    if ( mouseModeL == MOUSE_L_ROTAT_MODE ) {
      var threshold = Math.min( stage.stageWidth, stage.stageHeight ) / 2;
      if ( Math.max( Math.abs( movex ), Math.abs( movey ) ) > threshold ) {
        var dist:Float = Math.sqrt( movex * movex + movey * movey );
        arDegreeX = arDegree * movey / dist;
        arDegreeY = arDegree * movex / dist;
        __beginAutoRotation();
      }
    }
  }

  private function __mouseMove( event:MouseEvent ):Void {
    if ( __busyNow ) return;
    var movex:Float = event.stageX - __mousex;
    var movey:Float = event.stageY - __mousey;
    if ( mouseModeL == MOUSE_L_ROTAT_MODE ) {
      movex *= 30.0 * Math.PI / stage.stageWidth;
      movey *= 30.0 * Math.PI / stage.stageHeight;
      mpos.appendRotation( movey, flash.geom.Vector3D.X_AXIS );
      mpos.appendRotation( movex, flash.geom.Vector3D.Y_AXIS );
    } else if ( mouseModeL == MOUSE_L_TRANS_MODE ) {
      mpos.appendTranslation( -movex, movey, 0.0 );
    }
    __mousex = event.stageX;
    __mousey = event.stageY;
    __updateMPos = true;
  }

  private function __mouseWheel( event:MouseEvent ):Void {
    if ( __busyNow ) return;
    __handleZoom( event.delta );
  }

  private function __handleZoom( d:Int ):Void {
    if ( mouseModeW == MOUSE_W_SCALE_MODE ) {
      changeScale( d );
    } else if ( mouseModeW == MOUSE_W_DEPTH_MODE ) {
      changeDepth( d );
    }
  }

  private function __pressKey( event:KeyboardEvent ):Void {
    if ( __busyNow ) return;
    switch( event.keyCode ) {
      case Keyboard.LEFT: // left arrow
        if ( mouseModeL == MOUSE_L_TRANS_MODE ) {
          mpos.appendTranslation( 1, 0, 0 );
          __updateMPos = true;
        }
      case Keyboard.UP, 187: // up arrow, +
        if ( mouseModeL == MOUSE_L_TRANS_MODE ) {
          mpos.appendTranslation( 0, -1, 0 );
          __updateMPos = true;
        } else {
          __handleZoom( 1 );
        }
      case Keyboard.RIGHT: // right arrow
        if ( mouseModeL == MOUSE_L_TRANS_MODE ) {
          mpos.appendTranslation( -1, 0, 0 );
          __updateMPos = true;
        }
      case Keyboard.DOWN, 189: // down arrow, -
        if ( mouseModeL == MOUSE_L_TRANS_MODE ) {
          mpos.appendTranslation( 0, 1, 0 );
          __updateMPos = true;
        } else {
          __handleZoom( -1 );
        }
      case 13: // "return"
        __forwardScene();
      case 65: // "a" - about
        __showAbout();
      case 66: // "b" - back
        if ( event.shiftKey ) { // "B" - back to 1st frame
          __gotoInitScene();
        } else {
          __backScene();
        }
      case 67: // "c" - clear, reset view
        mpos.identity();
        visualize();
      case 68: // "d" - depth mode
        mouseModeW = MOUSE_W_DEPTH_MODE;
      case 69: // "e" - edit mode
        if ( !__protectData ) __editXML();
      case 70: // "f" - forward
        if ( event.shiftKey ) { // "F" - move to the last frame
          __gotoLastScene();
        } else {
          __forwardScene();
        }
      case 76: // "l" - load xml from file
        var fref:FileReference = new FileReference();
        fref.addEventListener( Event.SELECT, __loadFile );
        fref.addEventListener( Event.COMPLETE, __loadFileComplete );
        try {
          fref.browse();
        } catch( e:Error ) {
          trace( "Error: failed to load local file" );
          trace( "ErrorID: " + e.errorID + " Message: " + e.message );
        }
      case 80: // "p" - play
        if ( __playing ) {
          __pausePlay();
        } else {
          if ( event.shiftKey ) {
            __playBackward();
          } else {
            __playForward();
          }
        }
      case 82: // "r" - rotation mode
        mouseModeL = MOUSE_L_ROTAT_MODE;
      case 83: // "s" - scale mode
        if ( event.shiftKey ) {
          if ( !__protectData ) {
            var fref:FileReference = new FileReference();
            try {
              fref.save( __addCurrentSetting( myString ), "watermelon.xml" );
            } catch( e:Error ) {
              trace( "Error: failed to save XML data into local file" );
              trace( "ErrorID: " + e.errorID + " Message: " + e.message );
            }
          }
        } else {
          mouseModeW = MOUSE_W_SCALE_MODE;
        }
      case 84: // "t" - translation mode
        mouseModeL = MOUSE_L_TRANS_MODE;
    }
  }

  private function __resize( event:Event ):Void {
    WMBase.setScaleBase( stage );
    // this is not completely correct.
    // i do not know why i am using scale factor of 3.
    view_offset.x = 3 * ( stage.stageWidth - __keep_width );
    view_offset.y = - 3 * ( stage.stageHeight - __keep_height );
    __updateViewOffset = true;
    camera.determineFov( stage.stageHeight, Math.abs( camera.pos.z ) );
  }

  private function __pausePlay( ?e:MouseEvent = null ):Void {
    __playing = false;
  }

  private function __playForward( ?e:MouseEvent = null ):Void {
    if ( systems.length <= 1 || myTimer.running ) return;
    __initPlay();
  }

  private function __playBackward( ?e:MouseEvent = null ):Void {
    if ( systems.length <= 1 || myTimer.running ) return;
    __initPlay();
    __playReverseMode = true;
  }

  private function __initPlay():Void {
    if ( systems.length <= 1 ) return;
    __setNumFramesPerScene();
    __playReverseMode = false;
    __playing = true;
    __frameCounterForPlay = 0;
  }

  //// misc functions

  private function __showAbout():Void {
    if ( __busyNow ) return;
    if ( __about == null ) {
      var fs_large:Float = stage.stageWidth * 0.05;
      var fs_small:Float = stage.stageWidth * 0.03;
      var margin:Int = Std.int( stage.stageHeight * 0.1 );
      __about = new Sprite();
      __about.graphics.beginFill( 0x00FF00, 0.5 );
      __about.graphics.drawRect( 0, margin, stage.stageWidth, stage.stageHeight - 2 * margin );
      __about.graphics.endFill();
      __about.addChild( __showAboutProgramName( fs_large, margin ) );
      __about.addChild( __showAboutVersion( fs_small, margin ) );
      __about.addChild( __showAboutDescription( fs_small, margin ) );
      __about.addChild( __showAboutNotice( fs_small, margin ) );
      var ref = this;
      __about.addEventListener( MouseEvent.CLICK,
                                function( e:Event ) {
                                  ref.stage.removeChild( ref.__about );
                                  ref.__about = null;
                                } );
      stage.addChild( __about );
    } else {
      stage.removeChild( __about );
      __about = null;
    }
  }

  private function __showAboutProgramName( s:Float,
                                           m:Int ):TextField {
    var ret:TextField = new TextField();
    ret.y = m * 2;
    ret.width = stage.stageWidth;
    ret.text = "wm3d";
    ret.selectable = false;
    ret.autoSize = flash.text.TextFieldAutoSize.CENTER;
    ret.setTextFormat( new TextFormat( "Arial", s, 0x000000, true, null, null, null, flash.text.TextFormatAlign.CENTER ) );
    return( ret );
  }

  private function __showAboutVersion( s:Float,
                                       m:Int ):TextField {
    var ret:TextField = new TextField();
    ret.y = m * 2.5;
    ret.width = stage.stageWidth;
    ret.text = "version: " + Watermelon.version;
    ret.selectable = false;
    ret.autoSize = flash.text.TextFieldAutoSize.RIGHT;
    ret.setTextFormat( new TextFormat( "Arial", s, 0x000000, true, null, null, null, flash.text.TextFormatAlign.RIGHT ) );
    return( ret );
  }

  private function __showAboutDescription( s:Float,
                                           m:Int ):TextField {
    var ret:TextField = new TextField();
    ret.y = m * 4;
    ret.width = stage.stageWidth;
    ret.text = "simple flash molecular viewer\n" +
               "written in haXe\n" +
               "©2011-2012 tamanegi. All rights reserved.\n\n" +
               "wm3d is licensed under MPL1.1/GPL2.0+/LGPL2.1+ trilicense.";
    ret.selectable = false;
    ret.autoSize = flash.text.TextFieldAutoSize.CENTER;
    ret.setTextFormat( new TextFormat( "Arial", s, 0xFF0000, true, null, null, null, flash.text.TextFormatAlign.CENTER ) );
    return( ret );
  }

  private function __showAboutNotice( s:Float,
                                      m:Int ):TextField {
    var ret:TextField = new TextField();
    ret.y = stage.stageHeight - 2 * m;
    ret.width = stage.stageWidth;
    ret.text = "click this window to remove message.";
    ret.autoSize = flash.text.TextFieldAutoSize.RIGHT;
    ret.setTextFormat( new TextFormat( "Arial", s, 0x000000, true, null, null, null, flash.text.TextFormatAlign.RIGHT ) );
    return( ret );
  }

  private function __editXML():Void {
    __busyNow = true;
    __edit = new Sprite();
    __edit.graphics.beginFill( 0x00FF00, 0.5 );
    __edit.graphics.drawRect( 0, 0, stage.stageWidth, stage.stageHeight );
    __edit.graphics.endFill();
    var tf:TextFieldWithScrollBar = new TextFieldWithScrollBar( __edit, new TextFormat( "Arial", 12, 0x000000 ), 0.7 * stage.stageWidth, 0.7 * stage.stageHeight, 12, myString );
    tf.name = "EDIT_WINDOW";
    tf.x = 0.1 * stage.stageWidth;
    tf.y = 0.1 * stage.stageHeight;
    __edit.addChild( tf );
    stage.addChild( __edit );

    // OK and Cancel buttons
    var ok:TextField = new TextField();
    ok.border = true;
    ok.background = true;
    ok.text = "OK";
    ok.setTextFormat( new TextFormat( "Arial", 16 ) );
    ok.selectable = false;
    ok.autoSize = flash.text.TextFieldAutoSize.CENTER;
    var okbutton:Sprite = new Sprite();

    var cancel:TextField = new TextField();
    cancel.border = true;
    cancel.background = true;
    cancel.text = "Cancel";
    cancel.setTextFormat( new TextFormat( "Arial", 16 ) );
    cancel.selectable = false;
    cancel.autoSize = flash.text.TextFieldAutoSize.CENTER;
    var cancelbutton:Sprite = new Sprite();

    okbutton.addChild( ok );
    cancelbutton.addChild( cancel );
    __edit.addChild( okbutton );
    __edit.addChild( cancelbutton );

    okbutton.x = 0.6 * stage.stageWidth;
    okbutton.y = 0.85 * stage.stageHeight;
    cancelbutton.x = 0.7 * stage.stageWidth;
    cancelbutton.y = 0.85 * stage.stageHeight;

    okbutton.addEventListener( MouseEvent.CLICK, __editXMLOK );
    cancelbutton.addEventListener( MouseEvent.CLICK, __editXMLCancel );
  }

  private function __editXMLOK( event:MouseEvent ):Void {
    var d:Dynamic = __edit.getChildByName( "EDIT_WINDOW" );
    var s:String = d.getText();
    stage.removeChild( __edit );
    __edit = null;
    __busyNow = false;
    visualize( s );
  }

  private function __editXMLCancel( event:Event ):Void {
    __busyNow = false;
    stage.removeChild( __edit );
    __edit = null;
  }

  private function __loadFile( e:Event ):Void {
    e.target.load();
  }

  private function __loadFileComplete( e:Event ):Void {
    if ( e.target.type != ".xml" ) {
      trace( "Watermelon::__loadFileComplete: file must be .xml. Do not load file." );
      return;
    }
    mpos.identity();
    visualize( e.target.data.toString() );
  }

  public function getCurrentViewInXml():String {
    var camera_xml:String = "    <CAMERA z=\"" + camera.pos.z + "\" />\n";
    var rd:flash.Vector< Float > = mpos.rawData;
    var rmat:String = "    <MATRIX data=\"" + rd[0] + " " + rd[1] + " " +
                                rd[2] + " " + rd[3] + " " + rd[4] + " " +
                                rd[5] + " " + rd[6] + " " + rd[7] + " " +
                                rd[8] + " " + rd[9] + " " + rd[10] + " " +
                                rd[11] + " " + rd[12] + " " + rd[13] + " " +
                                rd[14] + " " + rd[15] + "\" />\n";
    return( camera_xml + rmat );
  }

  private function __addCurrentSetting( s:String ):String {
    var ret:String = s;
    var xml_begin:String = "  <GLOBAL>\n";
    var xml_end:String = "  </GLOBAL>\n";
    var base_xml:String = getCurrentViewInXml() + xml_end;
    // regular expression to find GLOBAL element
    var r0:EReg = ~/[ \t]*<GLOBAL[^>]*>.*<\/GLOBAL[\t ]*>/s;
    var r0p:EReg = ~/[ \t]*<GLOBAL[^>]*>/;
    if ( r0p.match( s ) ) {
      if ( r0p.matched(0).length > 8 ) xml_begin = r0p.matched(0);
    }
    // extract setting related lines
    ret = r0.replace( ret, "" ); // REMOVE all settings
    var myxml:String = "";
    if ( r0.match( s ) ) myxml = r0.matched(0);
    if ( myxml.length == 0 ) { // no settings in original XML
      myxml = xml_begin + base_xml;
    } else {
      var r:EReg = ~/[ \t]*<\/*(GLOBAL|CAMERA|MATRIX)[^>]*>[ \t]*$/mg;
      myxml = r.replace( myxml, "" );
      myxml = xml_begin + myxml + base_xml;
    }
    var r1:EReg = ~/<WMXML[ \t]*>/;
    ret = r1.replace( ret, "<WMXML>\n" + myxml );
    return( ret );
  }

// it is too ugly style. use ALIAS element instead
//  private function __parseMyStringAliases():Xml {
//    // alias will be processed here
//    // string is converted in cpp macro-like fashion
//    // ex.
//    // alias MYBOND BOND radius="x" ...
//    // <MYBOND pos0="yyy" pos1="zzz" />
//    // is converted to <BOND radius="x" pos0="yyy" pos1="zzz" />
//    // ***This alias function apparently violates XML standard.  ***
//    // ***If this something invalid XML cannot be parsed in haxe,***
//    // ***alias function will immediately be removed.            ***
//
//    // split string with LF
//    var lines:Array< String > = myString.split( String.fromCharCode( 10 ) );
//    var myLines:Array< String > = [];
//    for ( l in lines ) {
//      // split string with CR
//      var ls:Array< String > = l.split( String.fromCharCode( 13 ) );
//      for ( ll in ls ) {
//        if ( ll.length > 0 ) myLines.push( ll );
//      }
//    }
//    lines = [];
//    // search alias and replace strings
//    // alias replaces only element name (and its attribute)
//    var newString:String = myString;
//    for ( l in myLines ) {
//      var ls:Array< String > = l.split( " " );
//      if ( ls.length < 3 ) continue;
//      if ( ls[0].toLowerCase() == "alias" ) {
//        var newname:String = "<" + ls[1];
//        ls.shift();
//        ls.shift();
//        newString = StringTools.replace( newString, newname, "<" + ls.join( " " ) );
//      }
//    }
//    return( Xml.parse( newString ) );
//  }

  private function __forwardScene( ?e:MouseEvent = null ):Void {
    if ( myTimer.running ) return;
    frameIndex = ( frameIndex + 1 ) % systems.length;
    __updateScene = true;
  }

  private function __gotoLastScene( ?e:MouseEvent = null ):Void {
    if ( myTimer.running ) return;
    frameIndex = systems.length - 1;
    __updateScene = true;
  }

  private function __backScene( ?e:MouseEvent = null ):Void {
    if ( myTimer.running ) return;
    frameIndex--;
    if ( frameIndex < 0 ) frameIndex = systems.length + frameIndex;
    __updateScene = true;
  }

  private function __gotoInitScene( ?e:MouseEvent = null ):Void {
    if ( myTimer.running ) return;
    frameIndex = 0;
    __updateScene = true;
  }

  private function __setNumFramesPerScene():Void {
    __numFramesPerScene = Std.int( stage.frameRate / playFrameRate );
  }

  public function changeScale( d:Int ):Void {
    var sc:Float = 1.0 + scaleWheel * d;
    mpos.appendScale( sc, sc, sc );
    __updateMPos = true;
  }

  public function changeDepth( d:Int ):Void {
    camera.pos.z += depthWheel * d;
    if ( camera.pos.z > 0 ) camera.pos.z = 0;
    camera.determineFov( stage.stageHeight, Math.abs( camera.pos.z ) );
    __updateCameraPos = true;
  }

  // modify camera z position
  public function setCameraPosZ( z:Float ):Void {
    camera.pos.z = z;
    __updateCameraPos = true;
  }

  // modify auto-rotation rate
  // if zero is given, auto-rotation is completely deactivated
  public function setAutoRotationRate( r:Float ):Void {
    arDegree = Math.max( r, 0.0 );
    if ( r == 0.0 ) __stopAutoRotation();
  }

  // prevent EDIT & SAVE of data to hide out the structure data
  public function setProtectData():Void {
    __protectData = true;
  }

  // modify framerate of the current stage
  public function setFrameRate( r:Int ):Void {
    stage.frameRate = r;
  }

  // modify extent of scale change on mouse wheeling
  public function setScaleWheel( s:Float ):Void {
    scaleWheel = s;
  }

  // modify extent of depth change on mouse wheeling
  public function setDepthWheel( d:Float ):Void {
    depthWheel = d;
  }

  public function setInitialView( m:Matrix4 ):Void {
    mpos.append( m.toMatrix3D() );
    __updateMPos = true;
  }

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

  // getters and setters
  public function __getMyString():String { return( myString ); }
  public function __getCamera():Camera { return( camera ); }
  public function __getSystems():Array< WMSystem > { return( systems ); }
  public function __getLight():Vector3D { return( light ); }
  public function __getButtonWidth():Float { return( buttonWidth ); }
  public function __getButtonHeight():Float { return( buttonHeight ); }
  public function __getARDegree():Float { return( arDegree ); }
  public function __getARDegreeX():Float { return( arDegreeX ); }
  public function __getARDegreeY():Float { return( arDegreeY ); }
  public function __getScaleWheel():Float { return( scaleWheel ); }
  public function __getDepthWheel():Float { return( depthWheel ); }

  public function __setMyString( s:String ):String {
    myString = s;
    return( myString );
  }
  public function __setLight( l:Vector3D ):Vector3D {
    light = l.clone();
    return( light );
  }
  public function __setButtonWidth( s:Float ):Float {
    buttonWidth = s;
    return( s );
  }
  public function __setButtonHeight( h:Float ):Float {
    buttonHeight = h;
    return( h );
  }
  public function __setARDegree( d:Float ):Float {
    arDegree = d;
    return( arDegree );
  }
  public function __setARDegreeX( d:Float ):Float {
    arDegreeX = d;
    return( arDegreeX );
  }
  public function __setARDegreeY( d:Float ):Float {
    arDegreeY = d;
    return( arDegreeY );
  }
  public function __setScaleWheel( w:Float ):Float {
    scaleWheel = w;
    return( scaleWheel );
  }
  public function __setDepthWheel( w:Float ):Float {
    depthWheel = w;
    return( depthWheel );
  }
}
