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

import flash.geom.Matrix3D;

import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.TimerEvent;
import flash.events.IOErrorEvent;

import flash.errors.Error;

import flash.ui.Keyboard;

import flash.net.FileReference;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.net.URLVariables;

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

import flash.Vector;

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

class Watermelon {
  public var stage( __getStage, null ):Stage;
  public function __getStage():Stage { return( stage ); }

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

  public var myString( __getMyString, __setMyString ):String;
  public function __getMyString():String { return( myString ); }
  public function __setMyString( s:String ):String {
    myString = s;
    return( myString );
  }

  public var myPdb( __getMyPdb, null ):Pdb;
    public function __getMyPdb():Pdb { return( myPdb ); }

  // i do not know how to deal with Bytes
  private var myPicture( null, null ):EmbedBitmap;
  private var tfLoading( null, null ):TextField;

  private var myXml( null, null ):Xml;
  private var __readingFrame( null, null ):Int;

  public var systems( __getSystems, null ):Array< WMSystem >;
  public function __getSystems():Array< WMSystem > { return( systems ); }

  public var params( default, default ):WMParams;
  public var states( default, default ):WMStates;
  public var mevents( default, default ):WMMouseEvents;

  // remember initial width and height
  private var __keep_width( null, null ):Int;
  private var __keep_height( null, null ):Int;

  // temporary sprites
  private var __about( null, null ):WMAbout;
  private var __edit( null, null ):WMEdit;
  private var __pdb( null, null ):WMPdb;

  // permanent sprites; controller
  private var __controller( null, null ):WMController;

  // misc.
  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:flash.errors.SecurityError ) {
        // do nothing, just ignore
        // this is work around for PowerPoint, where ExternalInterface.available
        // is true but ExternalInterface.addCallback is not available
      }
    }

    visualize();
  }

  // set default values
  private function __initializeVariables():Void {
    myPicture = null;
    myString = "";
    myPdb = null;

    var ls:Array< String > = haxe.Resource.listNames();
    for ( name in ls ) {
      switch( name ) {
        case "structure":
          // read XML data from resource file attached in compilation phase.
          // -resource (filename):structure
          myString = haxe.Resource.getString( "structure" );
          // load picture if exists
        case "image":
          myPicture = new EmbedBitmap( "image", stage );
          myPicture.addEventListener( EmbedBitmap.COMPLETE, __addNowLoading );
        case "pdb":
          if ( myPdb == null ) {
            myPdb = new Pdb( haxe.Resource.getString( "pdb" ) );
          }
      }
    }

    // if pdb is available, generate xml string from its data
    if ( myPdb != null ) {
      myString = myPdb.genXml();
    }

    WMBase.setScaleBase( stage );
    systems = new Array< WMSystem >();
    __keep_width = stage.stageWidth;
    __keep_height = stage.stageHeight;
    __readingFrame = 0;
    params = new WMParams( this );
    states = new WMStates( this );
    mevents = new WMMouseEvents( this );
    __aliases = new Array< WMAlias >();
  }

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

  public function onReady( _ ) {
    c3d = stage3d.context3D;
    //c3d.enableErrorChecking = true;
    c3d.enableErrorChecking = false;
    // antialiasing does not work? I cannot recognize any changes.
    c3d.configureBackBuffer( stage.stageWidth, stage.stageHeight, 0, true );
    // parse xml
    var myscenes:Array< Xml > = new Array< Xml >();
    for ( wmxml in myXml.elements() ) {
      // top element which shall be "WMXML"
      if ( wmxml.nodeName.toUpperCase() == "WMXML" ) {
        // 2nd level elements; GLOBAL, ALIAS, and SCENE
        for ( node in wmxml.elements() ) {
          switch( node.nodeName.toUpperCase() ) {
            case "GLOBAL":
              // parse global settings
              __loadGlobalSettings( node );
            case "ALIAS":
              // aliases
              __loadAliases( node );
            case "SCENE":
              // store scenes, not be read here, since scene depends on aliases
              myscenes.push( node );
          }
        }
      }
    }
    systems = [];
    for ( mydata in myscenes ) {
      var sys:WMSystem = new WMSystem();
      __setDefaultValues( sys );
      // only register xml data here
      sys.registerXml( mydata );
      systems.push( sys );
    }
    // show now loading text
    if ( myPicture == null ) __addNowLoading();
    states.beginLoadSysTimer();
  }

  private function __removePicture():Void {
    myPicture.removeFromStage();
    myPicture = null;
  }

  private function __addNowLoading( ?e:Event = null ):Void {
    if ( tfLoading != null ) return;
    tfLoading = new TextField();
    tfLoading.text = "Now loading ...";
    tfLoading.selectable = false;
    tfLoading.autoSize = flash.text.TextFieldAutoSize.CENTER;
    tfLoading.setTextFormat( new TextFormat( "Arial", 14, 0xaaaaaa, true, null, null, null, flash.text.TextFormatAlign.CENTER ) );
    stage.addChild( tfLoading );
    tfLoading.y = stage.height / 2 - tfLoading.height;
    tfLoading.x = stage.width / 2 - tfLoading.width / 2;
  }

  private function __removeNowLoading():Void {
    stage.removeChild( tfLoading );
    tfLoading = null;
  }

  // timer event
  public function loadSystems( ?e:TimerEvent = null ):Void {
    if ( __readingFrame == systems.length ) {
      states.stopLoadSysTimer();
      __removeNowLoading();
      // if no SCENE found, add an empty frame
      if ( systems.length == 0 ) {
        systems.push( new WMSystem() );
        __beginStandardHandlers();
      }
      // finish reading
      if ( systems.length > 1 && states.playingNow ) {
        if ( states.playReverse ) {
          states.playBackward();
        } else {
          states.playForward();
        }
      } else {
        states.pausePlay();
        states.playReverse = false;
      }
      return;
    } else {
      if ( __readingFrame != 0 && !systems[__readingFrame].reading ) {
        __setDefaultValues( systems[__readingFrame] );
      }
      systems[__readingFrame].gen( c3d, params.dcActive, __aliases );
      if ( e != null ) e.updateAfterEvent();
      if ( !params.readBackground ) states.frameIndex = __readingFrame;
      if ( systems[__readingFrame].completed ) ++__readingFrame;
      states.updateScene = true;
    }
    // activate standard events when first frame is prepared
    if ( !stage.hasEventListener( Event.ENTER_FRAME ) ) {
      stage.addEventListener( Event.ENTER_FRAME, render );
      stage.addEventListener( Event.RESIZE, __resize );
      __beginStandardHandlers();
    }
  }

  // set default variables to new WMSystem
  private function __setDefaultValues( sys:WMSystem ):Void {
    sys.scaleFactor = params.scaleFactorAuto;
    if ( params.inheritScale && systems.length != 0 ) {
      sys.autoScale = false;
      sys.scaleFactorManual = systems[0].scaleFactorManual;
    } else {
      sys.autoScale = params.doAutoScale;
      sys.scaleFactorManual = params.scaleFactorManual;
    }
  }

  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 ( mevents != null ) mevents.removeStandardEvents();
  }

  public function render( _ ) {
    states.processCounter();
    if ( c3d == null || !states.needToUpdate() ) return;
    if ( myPicture != null ) __removePicture();
    if ( states.arNow ) states.applyAutoRotation();
    var bgcolor:Int = systems[states.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 );

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

  // XML
  private function __loadGlobalSettings( ?node:Xml = null ):Void {
    __parseGlobalSettingAttributes( node );
    for ( nd in node.elements() ) {
      var nn:String = nd.nodeName.toUpperCase();
      switch( nn ) {
        case "ATOM":
          WMDefaults.gl_Atom.loadFromXml( nd );
        case "BOND":
          WMDefaults.gl_Bond.loadFromXml( nd );
          var strs = [ "rounded", "round" ];
          for ( s in strs ) {
            if ( nd.exists( s ) ) {
              WMDefaults.gl_BondRound = ( Std.parseInt( nd.get( s ) ) > 0 );
            }
          }
          strs = [ "exc", "exclude" ];
          for ( s in strs ) {
            if ( nd.exists( s ) ) {
              WMDefaults.gl_BondExclude = ( Std.parseInt( nd.get( s ) ) > 0 );
            }
          }
          strs = [ "dash", "dashed" ];
          for ( s in strs ) {
            if ( nd.exists( s ) ) {
              WMDefaults.gl_BondDashed = Std.parseInt( nd.get( s ) );
            }
          }
        case "RIBBON":
          WMDefaults.gl_Ribbon.loadFromXml( nd );
          var strs = [ "thick", "thickness" ];
          for ( s in strs ) {
            if ( nd.exists( s ) ) {
              WMDefaults.gl_RibbonThickness = Std.parseFloat( nd.get( s ) );
            }
          }
        case "COIL":
          WMDefaults.gl_Coil.loadFromXml( nd );
        case "SHAPE":
          WMDefaults.gl_Shape.loadFromXml( nd );
        case "OBJ3D":
          WMDefaults.gl_Object3D.loadFromXml( nd );
          if ( nd.exists( "type" ) ) WMDefaults.gl_Object3DType = nd.get( "type" );
        case "LABEL":
          WMDefaults.gl_Label.loadFromXml( nd );
          if ( nd.exists( "font" ) ) WMDefaults.gl_LabelFont = nd.get( "font" );
          if ( nd.exists( "fontsize" ) ) WMDefaults.gl_LabelSize = Std.parseFloat( nd.get( "fontsize" ) );
        case "CAMERA":
          if ( nd.exists( "z" ) ) {
            states.setCameraPosZ( Std.parseFloat( nd.get( "z" ) ) );
          }
        case "PROTECT":
          params.protectData = true;
        case "WHEEL":
          if ( nd.exists( "scale" ) ) {
            params.scaleWheel = Std.parseFloat( nd.get( "scale" ) );
          }
          if ( nd.exists( "depth" ) ) {
            params.depthWheel = Std.parseFloat( nd.get( "depth" ) );
          }
        case "MATRIX":
          if ( nd.exists( "data" ) ) {
            states.mpos = Matrix4.fromString( nd.get( "data" ) ).toMatrix3D();
          }
        case "RADIUS":
          if ( nd.exists( "method" ) ) {
            var met:String = nd.get( "method" );
            if ( met.toLowerCase() == "absolute" ) {
              WMBase.useRelative = false;
            } else if ( met.toLowerCase() == "relative" ) {
              WMBase.useRelative = true;
            }
          }
          if ( nd.exists( "scale" ) ) {
            WMBase.characteristicSize = Std.parseFloat( nd.get( "scale" ) );
          }
        case "AUTOSCALE":
          if ( nd.exists( "manual" ) ) {
            var n:Int = Std.parseInt( nd.get( "manual" ) );
            if ( n > 0 ) params.doAutoScale = false;
          }
          if ( nd.exists( "autoscale" ) ) {
            params.scaleFactorAuto = Std.parseFloat( nd.get( "autoscale" ) );
          }
          if ( nd.exists( "manualscale" ) ) {
            params.scaleFactorManual = Std.parseFloat( nd.get( "manualscale" ) );
          }
          if ( nd.exists( "inherit" ) ) {
            params.inheritScale = ( Std.parseInt( nd.get( "inherit" ) ) > 0 );
          }
        case "DC":
          if ( nd.exists( "active" ) ) {
            params.dcActive = ( Std.parseInt( nd.get( "active" ) ) > 0 );
          }
          if ( nd.exists( "coeff" ) ) {
            params.dcCoeff = Std.parseFloat( nd.get( "coeff" ) );
            params.dcCoeff /= Math.min( stage.stageWidth, stage.stageHeight );
          }
          if ( nd.exists( "length" ) ) {
            params.dcLength = Std.parseFloat( nd.get( "length" ) );
            params.dcLength *= Math.min( stage.stageWidth, stage.stageHeight );
          }
      }
    }
  }

  private function __parseGlobalSettingAttributes( nd:Xml ):Void {
    if ( nd.exists( "light" ) ) {
      states.setLightDirection( Point3D.fromString( nd.get( "light" ) ) );
    }
    if ( nd.exists( "arrate" ) ) {
      params.arDegree = Std.parseFloat( nd.get( "arrate" ) );
    }
    if ( nd.exists( "framerate" ) ) {
      stage.frameRate = Std.parseFloat( nd.get( "framerate" ) );
    }
    if ( nd.exists( "play" ) ) {
      var v:Int = Std.parseInt( nd.get( "play" ) );
      if ( v > 0 ) {
        states.playingNow = true;
        states.playReverse = false;
      } else if ( v < 0 ) {
        states.playingNow = true;
        states.playReverse = true;
      }
    }
    if ( nd.exists( "readatonce" ) ) {
      WMSystem.readAtOnce = ( Std.parseInt( nd.get( "readatonce" ) ) > 0 );
    }
    if ( nd.exists( "readchainatonce" ) ) {
      WMSystem.readChainAtOnce = ( Std.parseInt( nd.get( "readchainatonce" ) ) > 0 );
    }
    if ( nd.exists( "playrate" ) ) {
      params.playFrameRate = Std.parseFloat( nd.get( "playrate" ) );
    }
    var strs = [ "readbackground", "bgread", "readbg" ];
    for ( s in strs ) {
      // never mind the value
      if ( nd.exists( s ) ) params.readBackground = true;
    }
  }

  private function __loadAliases( ?nd:Xml = null ):Void {
    // register an alias
    // this will still violate standards of XML, though
    for ( ndc in nd.elements() ) {
      if ( ndc.exists( "elem" ) ) {
        var wmal:WMAlias = new WMAlias();
        wmal.register( ndc.nodeName.toUpperCase(), ndc );
        __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 __hasController():Bool {
    if ( __controller == null ) return( false );
    return( __controller.enabled() );
  }

  private function __generateController( ?rebuild = false ):Void {
    if ( __hasController() && !rebuild ) return;
    __controller = new WMController( this );
    stage.addChild( __controller.draw() );
  }

  //// Event-related functions
  private function __beginStandardHandlers():Void {
    mevents.beginStandardEvents();
    stage.addEventListener( KeyboardEvent.KEY_DOWN, __pressKey );
  }

  public function onController( mx:Float,
                                my:Float ):Bool {
    if ( my >= ( stage.stageHeight - __controller.height ) ) return( true );
    return( false );
  }

  public function handleZoom( d:Int ):Void {
    if ( states.mouseModeW == WMStates.MOUSE_W_SCALE_MODE ) {
      states.changeScale( d );
    } else if ( states.mouseModeW == WMStates.MOUSE_W_DEPTH_MODE ) {
      states.changeDepth( d );
    }
  }

  private function __pressKey( event:KeyboardEvent ):Void {
    if ( states.busyNow ) return;
    switch( event.keyCode ) {
      case Keyboard.LEFT: // left arrow
        if ( states.mouseModeL == WMStates.MOUSE_L_TRANS_MODE ) {
          states.mpos.appendTranslation( 1, 0, 0 );
          states.updateMPos = true;
        }
      case Keyboard.UP, 187: // up arrow, +
        if ( states.mouseModeL == WMStates.MOUSE_L_TRANS_MODE ) {
          states.mpos.appendTranslation( 0, -1, 0 );
          states.updateMPos = true;
        } else {
          handleZoom( 1 );
        }
      case Keyboard.RIGHT: // right arrow
        if ( states.mouseModeL == WMStates.MOUSE_L_TRANS_MODE ) {
          states.mpos.appendTranslation( -1, 0, 0 );
          states.updateMPos = true;
        }
      case Keyboard.DOWN, 189: // down arrow, -
        if ( states.mouseModeL == WMStates.MOUSE_L_TRANS_MODE ) {
          states.mpos.appendTranslation( 0, 1, 0 );
          states.updateMPos = true;
        } else {
          handleZoom( -1 );
        }
      case 13: // "return"
        states.forwardScene();
      case 65: // "a" - about
        __showAbout();
      case 66: // "b" - back
        if ( event.shiftKey ) { // "B" - back to 1st frame
          states.gotoInitScene();
        } else {
          states.backScene();
        }
      case 67: // "c" - clear, reset view, "C" - clear trace message
        if ( event.shiftKey ) {
          flash.Boot.__clear_trace();
        } else {
          states.mpos.identity();
          visualize();
        }
      case 68: // "d" - depth mode
        if ( event.shiftKey ) {
          var xmldata:String = "<WMXML>\n";
          xmldata += "  <GLOBAL>\n";
          xmldata += getCurrentViewInXml();
          xmldata += "  </GLOBAL>\n";
          for ( sys in systems ) {
            xmldata += "  <SCENE>\n";
            xmldata += sys.dump();
            xmldata += "  </SCENE>\n";
          }
          xmldata += "</WMXML>";
          if ( !params.protectData ) {
            var fref:FileReference = new FileReference();
            try {
              fref.save( xmldata, "wmdump.xml" );
            } catch( e:Error ) {
              trace( "Error: failed to save XML data into local file" );
              trace( "ErrorID: " + e.errorID + " Message: " + e.message );
            }
          }
        } else {
          states.mouseModeW = WMStates.MOUSE_W_DEPTH_MODE;
        }
      case 69: // "e" - edit mode
        if ( !params.protectData ) __editXML();
      case 70: // "f" - forward
        if ( event.shiftKey ) { // "F" - move to the last frame
          states.gotoLastScene();
        } else {
          states.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, "P" - load pdb
        if ( event.shiftKey ) {
          states.busyNow = true;
          if ( __pdb == null ) {
            __pdb = new WMPdb( this );
          }
          stage.addChild( __pdb );
        } else {
          if ( states.playingNow ) {
            states.pausePlay();
          } else {
            if ( event.shiftKey ) {
              states.playBackward();
            } else {
              states.playForward();
            }
          }
        }
      case 82: // "r" - rotation mode
        states.mouseModeL = WMStates.MOUSE_L_ROTAT_MODE;
      case 83: // "s" - scale mode
        if ( event.shiftKey ) {
          if ( !params.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 {
          states.mouseModeW = WMStates.MOUSE_W_SCALE_MODE;
        }
      case 84: // "t" - translation mode
        states.mouseModeL = WMStates.MOUSE_L_TRANS_MODE;
    }
  }

  // 2012/7/19: should be fixed
  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.
    states.view_offset.x = 3 * ( stage.stageWidth - __keep_width );
    states.view_offset.y = - 3 * ( stage.stageHeight - __keep_height );
    states.updateViewOffset = true;
    states.camera.determineFov( stage.stageHeight, Math.abs( states.camera.pos.z ) );
  }

  //// misc functions
  ////// ABOUT (see WMAbout.hx)
  private function __showAbout():Void {
    if ( states.busyNow ) return;
    if ( __about == null ) {
      // need to create
      __about = new WMAbout();
      // modify color/font/font size here if neccessary
    }
    if ( stage.contains( __about ) ) {
      // need to remove
      stage.removeChild( __about );
    } else {
      stage.addChild( __about.drawAbout() );
    }
  }

  ////// EDIT
  private function __editXML():Void {
    if ( states.busyNow ) return;
    states.busyNow = true; // this flag is resetted when removign window
    __edit = new WMEdit( this );
    stage.addChild( __edit.draw() );
  }

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

  private function __loadFileComplete( e:Event ):Void {
    if ( e.target.type != ".xml" && e.target.type != ".pdb" ) {
      trace( "Watermelon::__loadFileComplete: file must be .xml or .pdb. Stop loading file." );
      return;
    }
    states.mpos.identity();
    if ( e.target.type == ".xml" ) {
      visualize( e.target.data.toString() );
    } else if ( e.target.type == ".pdb" ) {
      myPdb = new Pdb( e.target.data.toString() );
      visualize( myPdb.genXml() );
    }
  }

  public function getCurrentViewInXml():String {
    var camera_xml:String = "    <CAMERA z=\"" + states.camera.pos.z + "\" />\n";
    var rd:flash.Vector< Float > = states.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) + "\n";
    }
    // 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 );
  }

  //// Load Pdb
  ////// This function NEVER works due to the crossdomain problem
  ////// of flash player (at least now).
  ////// RCSB and PDBj does not have crossdomain.xml,
  ////// PDBe has crossdomain.xml but access from outside is forbidden.
  public function retrievePdbFromRCSB( id:String ) {
    // URL: http://www.rcsb.org/pdb/files/(pdbid).pdb.gz
    // may not neccessary to convert
    var pdbid:String = StringTools.trim( id.toUpperCase() );
    // verify id
    if ( pdbid.length != 4 ) {
      trace( "retrievePdb - PDB id must be 4-character identifier." );
      return;
    }
    var url:String = "http://www.rcsb.org/pdb/download/downloadFile.do";
    var urlLoader:URLLoader = new URLLoader();
    urlLoader.addEventListener( Event.COMPLETE, __loadPdbComplete );
    var urlReq:URLRequest = new URLRequest( url );
    // setup cgi params
    var urlVars:URLVariables = new URLVariables();
    urlVars.structureId = pdbid;
    urlVars.compression = "NO";
    urlVars.fileFormat = "text";
    urlReq.data = urlVars;
    try {
      urlLoader.load( urlReq );
    } catch ( e:flash.errors.ArgumentError ) {
      trace( "retrievePdb - met ArgumentError");
    } catch ( e:flash.errors.SecurityError ) {
      trace( "retrievePdb - met SecurityError");
    }
  }

  private function __loadPdbComplete( e:Event ) {
    myPdb = new Pdb( e.target.data.toString() );
    visualize( myPdb.genXml() );
  }
}
