// --------------------------------------------------------------------
// wm3d - A Flash Molecular Viewer
//
// Copyright (c) 2011-2014, tamanegi (tamanegi@users.sourceforge.jp)
// All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// --------------------------------------------------------------------

import flash.display.Stage;

/**
  Base class for wm3d objects.
**/

class WMBase {
  // static variables and functions
  /**
    stage; for non-primordial worker
  **/
  static public var stageWidth = flash.Lib.current.stage.stageWidth;
  static public var stageHeight = flash.Lib.current.stage.stageHeight;
  /**
    use relative scaling in distance. default value is true.
  **/
  static public var useRelative = true;
  /**
    relative scale factor; default value is min(stageWidth,stageHeight)*`characteristicSize`
  **/
  static public var scaleBase:Float = 0.0;
  /**
    scale factor used for relative scaling; default is 0.0025.
    See also `useRelative` and `scaleBase`.
  **/
  static public var characteristicSize:Float = 0.0025;

  /**
    split string like split function is perl.
    Return value is array of splitted strings of `s`.
  **/
  static public function splitString( s:String ):Array< String > {
    var v0:EReg = ~/^\s*/;
    var v1:EReg = ~/\s*$/;
    var v2:EReg = ~/\s+/g;
    var mystr:String = v0.replace( s, "" );
    mystr = v1.replace( mystr, "" );
    mystr = v2.replace( mystr, " " );
    return( mystr.split( " " ) );
  }

  /**
    set scale factor for relative scaling.
    Stage size `st` is used as a reference length.
  **/
  static public function setScaleBase():Void {
    scaleBase = Math.min( stageWidth, stageHeight );
    scaleBase *= characteristicSize;
  }

  /**
    returns actual position of `r` (if relative scaling is false, `r` itself
    will be returned)
  **/
  static public function getRelative( r:Float ):Float {
    if ( !useRelative ) return( r );
    // if not initialized, use width and height of root stage
    if ( scaleBase == 0.0 ) setScaleBase();
    return( r * scaleBase );
  }

  /**
    convert color in string (such as red) into RGB integer (such as 0xFF0000).
    Input value `c` is color string and the return value is RGB color in
    integer.
  **/
  static public function parseColor( c:String ):Int {
    var v:EReg = ~/\s+/g;
    var s:String = v.replace( c, "" );
    if ( s.charAt(0) == '0' ) return( Std.parseInt( s ) );
    switch( s.toLowerCase() ) {
      case "white": return( 0xFFFFFF );
      case "silver": return( 0xC0C0C0 );
      case "gray": return( 0x808080 );
      case "black": return( 0x000000 );
      case "cyan": return( 0x00FFFF );
      case "navy": return( 0x000080 );
      case "blue": return( 0x0000FF );
      case "lime": return( 0x00FF00 );
      case "green": return( 0x008000 );
      case "springgreen": return( 0x00FF7F );
      case "limegreen": return( 0x32CD32 );
      case "darkgreen": return( 0x006400 );
      case "khaki": return( 0xF0E68C );
      case "orange": return( 0xFFA500 );
      case "yellow": return( 0xFFFF00 );
      case "brown": return( 0xA52A2A );
      case "chocolate": return( 0xD2691E );
      case "gold": return( 0xFFD700 );
      case "violet": return( 0xEE82EE );
      case "salmon": return( 0xFA8072 );
      case "purple": return( 0x800080 );
      case "red": return( 0xFF0000 );
      case "magenta": return( 0xFF00FF );
      case "pink": return( 0xFFC0CB );
      default:
        var r0:EReg = ~/[a-z]/;
        if ( r0.match( s ) ) {
          trace( "warning: unknown color? : " + s );
        }
        return( Std.parseInt( s ) );
    }
  }

  /**
    clone object (this copies only public property. Inheritted properties are
    also ignored.) Instance fields of `s` shall be given, since
    Type.getInstanceFields(Type.getClass(s)) returns empty Array.
  **/
  static public function clone( s:Dynamic,
                                fields:Array< String > ):Dynamic {
    var ct = Type.getClass(s);
    var ret = Type.createInstance( ct, new Array< Dynamic >() );
    for ( field in fields ) {
      //// need to avoid non-existing fields?
      //if ( !Reflect.hasField( ret, field ) ) continue;
      Reflect.setProperty( ret, field, Reflect.getProperty( s, field ) );
    }
    return( ret );
  }

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

  // local variables
  /**
    radius in angstrom
  **/
  @:isVar public var radius( get, set ):Float;
    /**
      getter of `radius`
    **/
    public function get_radius():Float { return( radius ); }
    /**
      setter of `radius`
    **/
    public function set_radius( r:Float ):Float { radius = r; return( radius ); }
  /**
    color
  **/
  @:isVar public var color0( get, set ):Int;
    /**
      getter of `color0`
    **/
    public function get_color0():Int { return( color0 ); }
    /**
      setter of `color0`
    **/
    public function set_color0( c:Int ):Int { color0 = c; return( color0 ); }
  /**
    optional color
  **/
  @:isVar public var color1( get, set ):Int;
    /**
      getter of `color1`
    **/
    public function get_color1():Int { return( color1 ); }
    /**
      setter of `color1`
    **/
    public function set_color1( c:Int ):Int { color1 = c; return( color1 ); }
  /**
    alpha value (range 0.0(transparent)-1.0(opaque))
  **/
  @:isVar public var alpha( get, set ):Float;
    /**
      getter of `alpha`
    **/
    public function get_alpha():Float { return( alpha ); }
    /**
      setter of `alpha`
    **/
    public function set_alpha( a:Float ):Float { alpha = a; return( alpha ); }
  /**
    offset value; a few objects use this value
  **/
  @:isVar public var offset( get, set ):Float;
    /**
      getter of `offset`
    **/
    public function get_offset():Float { return( offset ); }
    /**
      setter of `offset`
    **/
    public function set_offset( o:Float ):Float { offset = o; return( offset ); }
  /**
    quality (accuracy) of the object; important for spherical moiety
  **/
  @:isVar public var quality( get, set ):Int;
    /**
      getter of `quality`
    **/
    public function get_quality():Int { return( quality ); }
    /**
      setter of `quality`
    **/
    public function set_quality( q:Int ):Int { quality = q; return( quality ); }

  /**
    intensity of ambient; note that ambient in wm3d is colored
  **/
  @:isVar public var ambient( get, set ):Float;
    /**
      getter of `ambient`
    **/
    public function get_ambient():Float { return( ambient ); }
    /**
      setter of `ambient`
    **/
    public function set_ambient( a:Float ):Float { ambient = a; return( ambient ); }
  /**
    intensity of diffuse; used in Gouraud and Phong shaders.
  **/
  @:isVar public var diffuse( get, set ):Float;
    /**
      getter of `diffuse`
    **/
    public function get_diffuse():Float { return( diffuse ); }
    /**
      setter of `diffuse`
    **/
    public function set_diffuse( d:Float ):Float { diffuse = d; return( diffuse ); }
  /**
    intensity of specular; used in Phong-shaders.
  **/
  @:isVar public var specular( get, set ):Float;
    /**
      getter of `specular`
    **/
    public function get_specular():Float { return( specular ); }
    /**
      setter of `specular`
    **/
    public function set_specular( s:Float ):Float { specular = s; return( specular ); }
  /**
    gloss used in Phong-shaders
  **/
  @:isVar public var gloss( get, set ):Float;
    /**
      getter of `gloss`
    **/
    public function get_gloss():Float { return( gloss ); }
    /**
      setter of `gloss`
    **/
    public function set_gloss( g:Float ):Float { gloss = g; return( gloss ); }
  /**
    Shader used for this object; Simple, Gouraud, and Phong are the only valid
    values.
  **/
  @:isVar public var shader( get, set ):String;
    /**
      getter of `shader`
    **/
    public function get_shader():String { return( shader ); }
    /**
      setter of `shader`
    **/
    public function set_shader( sn:String ):String { shader = sn; return( shader ); }

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

  /**
    Constructor.
  **/
  public function new( ?r:Float = 0.0,
                       ?c0:Int = 0x000000,
                       ?c1:Int = 0x000000,
                       ?a:Float = 1.0,
                       ?o:Float = 0.0,
                       ?q:Int = 0,
                       ?ag:Float = 0.3,
                       ?d:Float = 0.5,
                       ?sp:Float = 0.2,
                       ?gl:Float = 20.0,
                       ?sh:String = "Phong" ):Void {
    set( r, c0, c1, a, o, q, ag, d, sp, gl, sh );
  }

  /**
    set vaiables
  **/
  public function set( ?r:Float = 0.0,
                       ?c0:Int = 0x000000,
                       ?c1:Int = 0x000000,
                       ?a:Float = 1.0,
                       ?o:Float = 0.0,
                       ?q:Int = 0,
                       ?ag:Float = 0.3,
                       ?d:Float = 0.5,
                       ?sp:Float = 0.2,
                       ?gl:Float = 20.0,
                       ?sh:String = "Phong" ):Void {
    radius = r;
    color0 = c0;
    color1 = c1;
    alpha = a;
    offset = o;
    quality = q;
    ambient = ag;
    diffuse = d;
    specular = sp;
    gloss = gl;
    shader = sh;
  }

  /**
    initialize variables
  **/
  public function clear( ?def:WMDefaults = null ):Void {
    if ( def != null ) {
      copyFrom( def.Atom );
    } else {
      radius = 0.0;
      color0 = 0x000000;
      color1 = 0x000000;
      alpha = 1.0;
      offset = 0.0;
      quality = 0;
      ambient = 0.3;
      diffuse = 0.5;
      specular = 0.2;
      gloss = 20.0;
      shader = "Phong";
    }
  }

  /**
    copy values from `d`
  **/
  public function copyFrom( d:Dynamic ):Void {
    radius = d.radius;
    color0 = d.color0;
    color1 = d.color1;
    alpha = d.alpha;
    offset = d.offset;
    quality = d.quality;
    ambient = d.ambient;
    diffuse = d.diffuse;
    specular = d.specular;
    gloss = d.gloss;
    shader = d.shader;
  }

  /**
    load varibles from Xml. If `def` is given, `def` is used as an initial
    values and then overwritten by Xml data `x`.
  **/
  public function loadFromXml( x:Xml,
                               ?def:WMDefaults = null ):Void {
    // radius: r, w, radius, width
    var strs = [ "width", "w", "radius", "r" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) radius = Std.parseFloat( x.get( str ) );
    }
    // color: c, color
    strs = [ "color", "c" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) color0 = color1 = WMBase.parseColor( x.get( str ) );
    }
    // color0: c0, col0, color0
    strs = [ "c0", "col0", "color0" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) color0 = WMBase.parseColor( x.get( str ) );
    }
    // color1: c1, col1, color1
    strs = [ "c1", "col1", "color1" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) color1 = WMBase.parseColor( x.get( str ) );
    }
    // alpha: a, alpha
    strs = [ "alpha", "a" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) alpha = Std.parseFloat( x.get( str ) );
    }
    // offset: o, offset
    strs = [ "o", "offset" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) offset = Std.parseFloat( x.get( str ) );
    }
    // quality: q, qua, quality
    strs = [ "q", "qua", "quality" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) quality = Std.parseInt( x.get( str ) );
    }
    // ambient: am, amb, ambient
    strs = [ "am", "amb", "ambient" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) ambient = Std.parseFloat( x.get( str ) );
    }
    // diffuse: d, dif, diff, diffuse
    strs = [ "d", "dif", "diff", "diffuse" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) diffuse = Std.parseFloat( x.get( str ) );
    }
    // specular: s, spe, specular
    strs = [ "s", "spe", "spec", "specular" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) specular = Std.parseFloat( x.get( str ) );
    }
    // gloss: g, gloss
    strs = [ "g", "gloss" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) gloss = Std.parseFloat( x.get( str ) );
    }
    // shader: shade, shader, shading
    strs = [ "shade", "shader", "shading" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) {
        var vstr:String = x.get( str ).toLowerCase();
        if ( vstr == "simple" ) {
          shader = "Simple";
        } else if ( vstr == "gouraud" ) {
          shader = "Gouraud";
        } else if ( vstr == "phong" ) {
          shader = "Phong";
        }
      }
    }
  }

  /**
    data size: this is not meaningful for this class
  **/
  public function getDataSize():Int { return(1); }
}
