// --------------------------------------------------------------------
// wm3d - A Flash Molecular Viewer
//
// Copyright (c) 2011-2013, 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.display3D.Context3D;

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

import tinylib.Point3D;
import tinylib.primitives.Cylinder;
import tinylib.primitives.RoundedCylinder;

/**
  Bond object; a cylinder.

  See `WMBase` and `WMObjBase` classes for inheritted parameters.
**/

class WMBond extends WMObjBase {
  /**
    whether put spheres on top and bottom faces; default is true.
  **/
  @:isVar public var rounded( get, set ):Bool;
    /**
      getter of `rounded`
    **/
    public function get_rounded():Bool { return( rounded ); }
    /**
      setter of `rounded`
    **/
    public function set_rounded( r:Bool ):Bool {
      rounded = r;
      return( rounded );
    }
  /**
    whether skip drawing bottom and top faces. default is false.
  **/
  @:isVar public var exclude( get, set ):Bool;
    /**
      getter of `exclude`
    **/
    public function get_exclude():Bool { return( exclude ); }
    /**
      setter of `exclude`
    **/
    public function set_exclude( e:Bool ):Bool {
      exclude = e;
      return( exclude );
    }

  /**
    position of a terminal
  **/
  @:isVar public var pos0( get, set ):Point3D;
    /**
      getter of `pos0`
    **/
    public function get_pos0():Point3D { return( pos0 ); }
    /**
      setter of `pos0`
    **/
    public function set_pos0( p:Point3D ):Point3D {
      if ( p != null ) pos0 = p.clone();
      return( pos0 );
    }
  /**
    position of another terminal
  **/
  @:isVar public var pos1( get, set ):Point3D;
    /**
      getter of `pos1`
    **/
    public function get_pos1():Point3D { return( pos1 ); }
    /**
      setter of `pos1`
    **/
    public function set_pos1( p:Point3D ):Point3D {
      if ( p != null ) pos1 = p.clone();
      return( pos1 );
    }
  /**
    original position of a terminal; currently not intensively used
  **/
  @:isVar public var opos0( get, set ):Point3D;
    /**
      getter of `opos0`
    **/
    public function get_opos0():Point3D { return( opos0 ); }
    /**
      setter of `opos0`
    **/
    public function set_opos0( p:Point3D ):Point3D {
      if ( p != null ) opos0 = p.clone();
      return( opos0 );
    }
  /**
    original position of another terminal; currently not intensively used
  **/
  @:isVar public var opos1( get, set ):Point3D;
    /**
      getter of `opos1`
    **/
    public function get_opos1():Point3D { return( opos1 ); }
    /**
      setter of `opos1`
    **/
    public function set_opos1( p:Point3D ):Point3D {
      if ( p != null ) opos1 = p.clone();
      return( opos1 );
    }
  /**
    whether the bond is dashed line. default is false, solid line (cylinder)
    is used.
  **/
  @:isVar public var dashed( get, set ):Int;
    /**
      getter of `dashed`
    **/
    public function get_dashed():Int { return( dashed ); }
    /**
      setter of `dashed`
    **/
    public function set_dashed( d:Int ):Int {
      dashed = d;
      return( dashed );
    }

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

  /**
    Constructor. Parameters are listed below, where values in parenthesis
    are default values.

    - r: radius (1.0)
    - c0: first color (0x00FF00; lime) (pos0 side is filled with this color)
    - c1: second color (0x00FF00; lime) (pos1 side is filled with this color)
    - a: alpha (1.0, opaque)
    - o: offset (0.0)
    - q: quality (0)
    - ag: ambient intensity (0.4)
    - d: diffuse intensity (0.5)
    - sp: specular intensity (0.2)
    - gl: gloss (30.0)
    - sh: shader type ("Phong")
  **/
  public function new( ?r:Float = 1.0,
                       ?c0:Int = 0x00FF00,
                       ?c1:Int = 0x00FF00,
                       ?a:Float = 1.0,
                       ?o:Float = 0.0,
                       ?q:Int = 0,
                       ?ag:Float = 0.4,
                       ?d:Float = 0.5,
                       ?sp:Float = 0.2,
                       ?gl:Float = 30.0,
                       ?sh:String = "Phong" ) {
    super( r, c0, c1, a, o, q, ag, d, sp, gl, sh );
    rounded = true;
    exclude = false;
    pos0 = null;
    pos1 = null;
    opos0 = null;
    opos1 = null;
    dashed = 0;
  }

  /**
    returns a copy of this instance
  **/
  public function clone():WMBond {
    var ret:WMBond = new WMBond( radius, color0, color1, alpha, offset, quality, ambient, diffuse, specular, gloss, shader );
    ret.rounded = rounded;
    ret.exclude = exclude;
    ret.dashed = dashed;
    return( ret );
  }

  /**
    initialize variables, if `def` is given, values of `def` is used as default.
  **/
  public override function clear( ?def:WMDefaults = null ):Void {
    if ( def != null ) {
      copyFrom( def.Bond );
      rounded = def.BondRound;
      exclude = def.BondExclude;
      dashed = def.BondDashed;
    } else {
      radius = 1.0;
      color0 = 0x00FF00;
      color1 = 0x00FF00;
      alpha = 1.0;
      offset = 0.0;
      quality = 0;
      ambient = 0.4;
      diffuse = 0.5;
      specular = 0.2;
      gloss = 30.0;
      shader = "Gouraud";
      rounded = true;
      exclude = false;
    }
    pos0 = null;
    pos1 = null;
    opos0 = null;
    opos1 = null;
    _polygon = null;
  }

  /**
    load from XML; if `def` is given, that is used as initial value. See also
    `clear` function of this class.
  **/
  public override function loadFromXml( x:Xml,
                                        ?def:WMDefaults = null ):Void {
    clear( def );
    loadFromXmlWOClear( x );
  }

  /**
    load XML data; usually this function is called by loadFromXml function
  **/
  public function loadFromXmlWOClear( x:Xml ):Void {
    loadFromXmlOverwrite( x );
    if ( !x.exists( "pos0" ) || !x.exists( "pos1" ) ) {
      trace( "pos0 and pos1 attributes are required for a BOND element." );
      return;
    }
    pos0 = Point3D.fromStringInverted( x.get( "pos0" ) );
    pos1 = Point3D.fromStringInverted( x.get( "pos1" ) );
    opos0 = pos0;
    opos1 = pos1;
  }

  /**
    read XML while overriding default values; this function does not read
    WMAtom specific fields. Use `loadFromXmlWOClear` when loading WMAtom
    specific fields.
  **/
  public function loadFromXmlOverwrite( x:Xml ):Void {
    super.loadFromXml( x );
    var strs = [ "rounded", "round" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) {
        rounded = ( Std.parseInt( x.get( str ) ) > 0 );
      }
    }
    strs = [ "exclude", "exc" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) {
        exclude = ( Std.parseInt( x.get( str ) ) > 0 );
      }
    }
    var strs = [ "dash", "dashed" ];
    for ( str in strs ) {
      if ( x.exists( str ) ) {
        dashed = Std.parseInt( x.get( str ) );
      }
    }
  }

  /**
    generate polygon and set corresponding shaders;
    this function calls `pregen` and `gen2`.
  **/
  public function gen( c:Context3D,
                       ?is_dc_active:Bool = false ):Void {
    pregen();
    gen2( c, is_dc_active );
  }

  /**
    preparation for generating polygon. This function is explicitly used in
    non-primordial Worker, where Context3D of the primordial Worker is not
    available. If Worker is not used, this function should not be called
    explicitly.
  **/
  public function pregen():Void {
    var vec:Point3D = Point3D.getSub( pos1, pos0 );
    var norm:Float = vec.norm() - WMBase.getRelative( offset );
    if ( rounded && dashed == 0 && !exclude ) {
      _polygon = new RoundedCylinder( WMBase.getRelative( radius ),
                                      norm, quality );
    } else {
      _polygon = new Cylinder( WMBase.getRelative( radius ),
                               norm, quality, exclude, exclude );
    }
    if ( dashed != 0 ) {
      shader = "SimpleUV";
    } else if ( color0 != color1 ) {
      if ( shader == "Gouraud" ) shader = "GouraudUV";
      if ( shader == "Phong" ) shader = "PhongUV";
    }
    _polygon.lookAt( vec );
    _polygon.translateByVec( Point3D.getMiddle( pos0, pos1 ) );
    setShaderParams();
  }

  /**
    generate polygon and shader. Call `pregen` function before.
    This function should not be called explicitly unless you are using
    multiple Workers.
  **/
  public function gen2( c:Context3D,
                        ?is_dc_active:Bool = false ):Void {
    _polygon.allocate( c, is_dc_active, shader, color0, alpha );
    if ( dashed != 0 ) {
      _polygon.createDashedDoubleColoredTexture( c, dashed, color0, color1 );
    } else if ( color0 != color1 ) {
      _polygon.createDoubleColoredTexture( c, color0, color1 );
    }
  }

  /**
    dump data of this instance in xml format
  **/
  public override function dump():String {
    var d:Dynamic = Type.getClass( this );
    var type:String = d.id;

    var str:String = " c=" + "\"" + color0 + "\"" +
                     " c0=" + "\"" + color0 + "\"" +
                     " c1=" + "\"" + color1 + "\"" +
                     " a=" + "\"" + alpha + "\"" +
                     " o=" + "\"" + offset + "\"" +
                     " am=" + "\"" + ambient + "\"" +
                     " s=" + "\"" + specular + "\"" +
                     " g=" + "\"" + gloss + "\"" +
                     " dashed=" + "\"" + dashed + "\"" +
                     " type=" + "\"" + type + "\"" +
                     " shader="+ "\"" + shader + "\"";
    return( _polygon.dump( str ) );
  }

  /**
    number of elements inside; return always 2.
  **/
  public function num():Int { return( 2 ); }
  /**
    sum of position
  **/
  public function sumPos():Point3D {
    return( Point3D.getAdd( pos0, pos1 ) );
  }
  /**
    translate coordinate by `p`
  **/
  public function translate( p:Point3D ):Void {
    pos0.add( p );
    pos1.add( p );
  }
  /**
    absolute max of position: max(abs(x),abs(y),abs(z))
  **/
  public function absmax():Point3D {
    var ret:Point3D = new Point3D( 0, 0, 0 );
    ret.x = Math.max( Math.abs( pos0.x ), Math.abs( pos1.x ) );
    ret.y = Math.max( Math.abs( pos0.y ), Math.abs( pos1.y ) );
    ret.z = Math.max( Math.abs( pos0.z ), Math.abs( pos1.z ) );
    return( ret );
  }
  /**
    scale position by `scale`
  **/
  public function scaleCoord( scale:Float ):Void {
    pos0.multiply( scale );
    pos1.multiply( scale );
  }
  /**
    predefined cost for drawing; return always 15.
  **/
  public override function getDataSize():Int { return( 15 ); }
}
