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.ui.Keyboard;

import flash.net.FileReference;

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

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

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

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

  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;
  public var camera( __getCamera, null ):Camera;
  public var systems( __getSystems, null ):Array< WMSystem >;
  public var light( __getLight, __setLight ):Vector3D;
  public var buttonSize( __getButtonSize, __setButtonSize ):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; buttons
  private var __buttonForward( null, null ):Sprite;
  private var __buttonBack( 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;

  // misc.

  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:flash.errors.SecurityError ) {
        // do nothing, just ignore
        // ad hoc solution for powerpoint
      }
    }

    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 = 5.0;
    WMBase.setScaleBase( stage );
    systems = new Array< WMSystem >();
    camera = new Camera();
    camera.pos.z = -Math.max( stage.stageWidth, stage.stageHeight ) * 3;
    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 );
    buttonSize = 16;
    light.normalize();
    cpos = new Vector3D();
    mpos = new Matrix3D();
    mpos.identity();
    view_offset = new Vector3D();
    __arNow = true;
    __busyNow = false;
    __protectData = false;
    __updateCameraPos = true;
    __updateMPos = true;
    __updateViewOffset = false;
    __updateScene = true;
    frameIndex = 0;
    __generateForwardBackButtons();
  }

  public function visualize( ?str:String = null ):Void {
    __initializeVariables();
    if ( str != null ) myString = str;
    myXml = __parseMyStringAliases();
    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.configureBackBuffer( stage.stageWidth, stage.stageHeight, 0, true );
    stage.addEventListener( Event.ENTER_FRAME, render );
    stage.addEventListener( Event.RESIZE, __resize );
    // parse global settings
    var xml_global:XPath = new XPath( "WMXML/GLOBAL" );
    __loadGlobalSettings( xml_global.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();
      sys.gen( c3d, cast( nd, XPathHxXml ).getWrappedXml() );
      systems.push( sys );
    }
    if ( systems.length == 0 ) {
      systems.push( new WMSystem() );
    }
    __beginStandardHandlers();
  }

  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( _ ) {
    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();
    systems[frameIndex].draw( c3d, mpos, proj, view_offset, light, cpos );
    c3d.present();
    __resetFlags();
  }

  // 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;
        switch( nn ) {
          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" ) );
            }
        }
      }
    }
  }

  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" ) ) );
    }
  }

  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 __generateForwardBackButtons():Void {
    if ( __buttonForward != null || __buttonBack != null ) return;
    __genForwardButton();
    __genBackButton();
    __addCommonEventListenersToButton( __buttonForward );
    __addCommonEventListenersToButton( __buttonBack );
    __buttonForward.addEventListener( MouseEvent.CLICK, __forwardScene );
    __buttonBack.addEventListener( MouseEvent.CLICK, __backScene );
    stage.addChild( __buttonForward );
    stage.addChild( __buttonBack );
    __buttonForward.alpha = __buttonBack.alpha = 0.0;
    __buttonForward.x = stage.stageWidth - buttonSize;
    __buttonBack.x = 0;
    __buttonForward.y = __buttonBack.y = stage.stageHeight - buttonSize;
  }

  private function __addCommonEventListenersToButton( s:Sprite ) {
    var ref = this;
    var mybut = s; // why this line is necessary?
    s.addEventListener( MouseEvent.ROLL_OVER,
                        function( e:Event ) {
                          if ( ref.systems.length > 1 ) 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.lineStyle( 0, 0x000000, 1.0 );
    __buttonForward.graphics.beginFill( 0x000000, 1.0 );
    __buttonForward.graphics.moveTo( 1, 1 );
    __buttonForward.graphics.lineTo( 1, buttonSize - 1 );
    __buttonForward.graphics.lineTo( buttonSize - 1, buttonSize / 2 );
    __buttonForward.graphics.lineTo( 1, 1 );
  }

  private function __genBackButton():Void {
    __buttonBack = new Sprite();
    __buttonBack.graphics.lineStyle( 0, 0x000000, 1.0 );
    __buttonBack.graphics.beginFill( 0x000000, 1.0 );
    __buttonBack.graphics.moveTo( buttonSize - 1, 1 );
    __buttonBack.graphics.lineTo( buttonSize - 1, buttonSize - 1 );
    __buttonBack.graphics.lineTo( 1, buttonSize / 2 );
    __buttonBack.graphics.lineTo( buttonSize - 1, 1 );
  }

  //// 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 ( __onButtons( event.stageX, event.stageY ) ) return;
    __stopAutoRotation();
    __mousex = __mousepx = event.stageX;
    __mousey = __mousepy = event.stageY;
    __beginInteractiveTransform();
  }

  private function __onButtons( mx:Float,
                                my:Float ):Bool {
    if ( systems.length > 1 &&
         ( __onForwardButton( mx, my ) || __onBackButton( mx, my ) ) ) {
      return( true );
    }
    return( false );
  }

  private function __onForwardButton( mx:Float,
                                      my:Float ):Bool {
    if ( mx >= stage.stageWidth - buttonSize &&
         my >= stage.stageHeight - buttonSize ) return( true );
    return( false );
  }

  private function __onBackButton( mx:Float,
                                   my:Float ):Bool {
    if ( mx <= buttonSize &&
         my >= stage.stageHeight - buttonSize ) 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
          frameIndex = 0;
          __updateScene = true;
        } 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
          frameIndex = systems.length - 1;
          __updateScene = true;
        } else {
          __forwardScene();
        }
      case 76: // "l" - load xml from file
        var fref:FileReference = new FileReference();
        fref.addEventListener( Event.SELECT, __loadFile );
        fref.addEventListener( Event.COMPLETE, __loadFileComplete );
        fref.browse();
      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();
            fref.save( __addCurrentSetting( myString ), "watermelon.xml" );
          }
        } 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 ) );
  }

  //// 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 );
  }

  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" />

    // 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 {
    frameIndex = ( frameIndex + 1 ) % systems.length;
    __updateScene = true;
  }

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

  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 __getButtonSize():Float { return( buttonSize ); }
  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 __setButtonSize( s:Float ):Float {
    buttonSize = s;
    return( s );
  }
  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 );
  }
}
