// --------------------------------------------------------------------
// 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.Cone3D;
import tinylib.primitives.Cube3D;
import tinylib.primitives.Cylinder;
import tinylib.primitives.Square3D;

/**
  Basic 3D Objects: Cone, Cube, Cylinder, Square(2D)

  available objects and their params
  Cone: radius, height, quality
  Cube: x, y, z
  Cylinder: radius, height, quality
  Square: width, height
**/

class WMObject3D extends WMObjBase {
  /**
    object type. default is "cone"
  **/
  @:isVar public var type( get, set ):String;
    /**
      getter of `type`
    **/
    public function get_type():String { return( type ); }
    /**
      setter of `type`
    **/
    public function set_type( s:String ):String {
      type = s;
      return( s );
    }
  /**
    length for X direction; used for "Cube"
  **/
  @:isVar public var xsize( get, set ):Float;
    /**
      getter of `xsize`
    **/
    public function get_xsize():Float { return( xsize ); }
    /**
      setter of `xsize`
    **/
    public function set_xsize( x:Float ):Float {
      xsize = x;
      return( xsize );
    }
  /**
    length for Y direction; used for "Cube"
  **/
  @:isVar public var ysize( get, set ):Float;
    /**
      getter of `ysize`
    **/
    public function get_ysize():Float { return( ysize ); }
    /**
      setter of `ysize`
    **/
    public function set_ysize( y:Float ):Float {
      ysize = y;
      return( ysize );
    }
  /**
    length for Z direction; used for "Cube"
  **/
  @:isVar public var zsize( get, set ):Float;
    /**
      getter of `zsize`
    **/
    public function get_zsize():Float { return( zsize ); }
    /**
      setter of `zsize`
    **/
    public function set_zsize( z:Float ):Float {
      zsize = z;
      return( zsize );
    }
  /**
    position of the object
  **/
  @:isVar public var pos( get, set ):Point3D;
    /**
      getter of `pos`
    **/
    public function get_pos():Point3D { return( pos ); }
    /**
      setter of `pos`
    **/
    public function set_pos( p:Point3D ):Point3D {
      if ( p != null ) pos = p.clone();
      return( pos );
    }
  /**
    direction of the object
  **/
  @:isVar public var dir( get, set ):Point3D;
    /**
      getter for `dir`
    **/
    public function get_dir():Point3D { return( dir ); }
    /**
      setter for `dir`
    **/
    public function set_dir( d:Point3D ):Point3D {
      if ( d != null ) dir = d.clone();
      return( dir );
    }

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

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

    - r: radius (1.0)
    - c0: color (0x00FF00; lime)
    - c1: color (0x00FF00, lime)
    - a: alpha (1.0, opaque)
    - o: offset, not used (0.0)
    - q: quality, higher is better (3)
    - 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 = 3,
                       ?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 );
    type = "cone";
    xsize = ysize = zsize = 0.0;
    pos = null;
    dir = null;
    _polygon = null;
  }

  /**
    returns clone of `this` instance
  **/
  public function clone():WMObject3D {
    var ret:WMObject3D = new WMObject3D( radius, color0, color1, alpha, offset, quality, ambient, diffuse, specular, gloss, shader );
    ret.xsize = xsize;
    ret.ysize = ysize;
    ret.zsize = zsize;
    ret.type = type;
    return( ret );
  }

  /**
    initialize values; if `def` is given, that is used as initial value.
  **/
  public override function clear( ?def:WMDefaults = null ):Void {
    if ( def != null ) {
      copyFrom( def.Object3D );
      type = def.Object3DType;
    } else {
      radius = 1.0;
      color0 = 0x00FF00;
      color1 = 0x00FF00;
      alpha = 1.0;
      offset = 0.0;
      quality = 3;
      ambient = 0.4;
      diffuse = 0.5;
      specular = 0.2;
      gloss = 30.0;
      shader = "Phong";
      type = "cone";
    }
    pos = null;
    dir = 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 );
  }

  /**
    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 );
    if ( x.exists( "type" ) ) type = x.get( "type" );
  }

  /**
    load XML data; usually this function is called by loadFromXml function
  **/
  public function loadFromXmlWOClear( x:Xml ):Void {
    loadFromXmlOverwrite( x );
    if ( !x.exists( "pos" ) ) {
      trace( "pos attribute is required for an <OBJ3D>" );
      return;
    }
    pos = Point3D.fromStringInverted( x.get( "pos" ) );
    if ( x.exists( "dir" ) ) dir = Point3D.fromStringInverted( x.get( "dir" ) );
    if ( x.exists( "x" ) ) xsize = Std.parseFloat( x.get( "x" ) );
    if ( x.exists( "y" ) ) ysize = Std.parseFloat( x.get( "y" ) );
    if ( x.exists( "z" ) ) zsize = Std.parseFloat( x.get( "z" ) );
  }

  /**
    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() {
    switch( type ) {
      case "cone":
        var myrad:Float = ( xsize == 0.0 ) ? radius : xsize;
        _polygon = new Cone3D( myrad, ysize, quality );
      case "cube":
        _polygon = new Cube3D( xsize, ysize, zsize );
      case "cylinder":
        var myrad:Float = ( xsize == 0.0 ) ? radius : xsize;
        _polygon = new Cylinder( myrad, ysize, quality );
      case "square":
        _polygon = new Square3D( xsize, ysize, true );
    }
    if ( dir != null ) _polygon.lookAt( dir );
    _polygon.translate( pos.x, pos.y, pos.z );
  }

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

  /**
    number of elements inside; return always 1.
  **/
  public function num():Int { return(1); }
  /**
    sum of position
  **/
  public function sumPos():Point3D { return( pos ); }
  /**
    translate coordinate by `p`
  **/
  public function translate( p:Point3D ):Void { pos.add( p ); }
  /**
    absolute max of position: max(abs(x),abs(y),abs(z))
  **/
  public function absmax():Point3D {
    return( new Point3D( Math.abs( pos.x ), Math.abs( pos.y ), Math.abs( pos.z ) ) );
  }
  /**
    scale position by `scale`
  **/
  public function scaleCoord( scale:Float ):Void {
    pos.multiply( scale );
    xsize *= scale;
    ysize *= scale;
    zsize *= scale;
  }
  /**
    predefined cost for drawing; return always 10.
  **/
  public override function getDataSize():Int { return(5); }
}
