// --------------------------------------------------------------------
// 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.Triangles;

/**
  A general shape composed by triangles

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

class WMShape extends WMObjBase {
  /**
    (only for debugging purpose) Use flat shading or not. default is false.
  **/
  @:isVar public var flat( get, set ):Bool;
    /**
      getter of `flat`
    **/
    public function get_flat():Bool { return( flat ); }
    /**
      setter of `flat`
    **/
    public function set_flat( f:Bool ):Bool { flat = f; return( flat ); }

  /**
    positions
  **/
  public var posstr0:Array< String >;
  /**
    positions
  **/
  public var posstr1:Array< String >;
  /**
    positions
  **/
  public var posstr2:Array< String >;

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

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

    - r: radius (0.0), not used
    - c0: color (0x00FF00; lime)
    - c1: color, currently not used (0x00FF00, lime)
    - a: alpha (1.0, opaque)
    - o: offset, not used (0.0), not used.
    - q: quality, higher is better (0), not used.
    - 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 = 0.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 );
    posstr0 = new Array< String >();
    posstr1 = new Array< String >();
    posstr2 = new Array< String >();
    flat = false;
  }

  /**
    returns clone of `this` instance
  **/
  public function clone():WMShape {
    var ret:WMShape = new WMShape( radius, color0, color1, alpha, offset, quality, ambient, diffuse, specular, gloss, shader );
    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.Shape );
    } else {
      radius = 0.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 = "Phong";
    }
    _polygon = new Triangles();
    posstr0 = [];
    posstr1 = [];
    posstr2 = [];
  }

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

  /**
    load XML data; usually this function is called by loadFromXml function
  **/
  public function loadFromXmlWOClear( x:Xml ):Void {
    loadFromXmlOverwrite( x );
    if ( x.exists( "flat" ) ) flat = true;
    for ( elem in x.elementsNamed( "TRI" ) ) {
      if ( !elem.exists( "p0" ) ||
           !elem.exists( "p1" ) ||
           !elem.exists( "p2" ) ) {
        trace( "p0, p1, and p2 are required for TRI" );
        return;
      }
      if ( elem.get("p0") == elem.get("p1") ) {
        trace( "p0-p1:" + elem.get("p0") + elem.get("p1") );
      }
      if ( elem.get("p0") == elem.get("p2") ) {
        trace( "p0-p2:" + elem.get("p0") + elem.get("p2") );
      }
      if ( elem.get("p1") == elem.get("p2") ) {
        trace( "p1-p2:" + elem.get("p1") + elem.get("p2") );
      }
      posstr0.push( elem.get( "p0" ) );
      posstr1.push( elem.get( "p1" ) );
      posstr2.push( elem.get( "p2" ) );
    }
  }

  /**
    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() {
    _polygon = new Triangles();
    if ( flat ) _polygon.makeFlat = true;
    var len:Int = posstr0.length;
    for ( i in 0 ... len ) {
      _polygon.registerFace( posstr0[i], posstr1[i], posstr2[i] );
    }
    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 ) {
    _polygon.allocate( c, is_dc_active, shader, color0, alpha );
  }

  public function getPositions0():Array< Point3D > {
    var ret = new Array< Point3D >();
    for ( str in posstr0 ) {
      ret.push( Point3D.fromStringInverted( str ) );
    }
    return( ret );
  }

  public function getPositions1():Array< Point3D > {
    var ret = new Array< Point3D >();
    for ( str in posstr1 ) {
      ret.push( Point3D.fromStringInverted( str ) );
    }
    return( ret );
  }

  public function getPositions2():Array< Point3D > {
    var ret = new Array< Point3D >();
    for ( str in posstr2 ) {
      ret.push( Point3D.fromStringInverted( str ) );
    }
    return( ret );
  }

  public function setPositions0( ar:Array< Point3D > ):Void {
    posstr0 = [];
    for ( pos in ar ) posstr0.push( pos.toString() );
  }

  public function setPositions1( ar:Array< Point3D > ):Void {
    posstr1 = [];
    for ( pos in ar ) posstr1.push( pos.toString() );
  }

  public function setPositions2( ar:Array< Point3D > ):Void {
    posstr2 = [];
    for ( pos in ar ) posstr2.push( pos.toString() );
  }

  /**
    number of elements inside; return always 1.
  **/
  public function num():Int { return( posstr0.length * 3 ); }
  /**
    sum of position
  **/
  public function sumPos():Point3D {
    var ret:Point3D = new Point3D( 0, 0, 0 );
    for ( p in posstr0 ) ret.add( Point3D.fromStringInverted( p ) );
    for ( p in posstr1 ) ret.add( Point3D.fromStringInverted( p ) );
    for ( p in posstr2 ) ret.add( Point3D.fromStringInverted( p ) );
    return( ret );
  }

  /**
    translate coordinate by `p`
  **/
  public function translate( p:Point3D ):Void {
    var pos:Array< Point3D > = getPositions0();
    for ( cp in pos ) cp.add( p );
    setPositions0( pos );
    pos = getPositions1();
    for ( cp in pos ) cp.add( p );
    setPositions1( pos );
    pos = getPositions2();
    for ( cp in pos ) cp.add( p );
    setPositions2( pos );
  }

  /**
    absolute max of position: max(abs(x),abs(y),abs(z))
  **/
  public function absmax():Point3D {
    var ret:Point3D = new Point3D( 0, 0, 0 );
    for ( p in posstr0 ) {
      var pos:Point3D = Point3D.fromStringInverted( p );
      ret.x = Math.max( ret.x, Math.abs( pos.x ) );
      ret.y = Math.max( ret.y, Math.abs( pos.y ) );
      ret.z = Math.max( ret.z, Math.abs( pos.z ) );
    }
    for ( p in posstr1 ) {
      var pos:Point3D = Point3D.fromStringInverted( p );
      ret.x = Math.max( ret.x, Math.abs( pos.x ) );
      ret.y = Math.max( ret.y, Math.abs( pos.y ) );
      ret.z = Math.max( ret.z, Math.abs( pos.z ) );
    }
    for ( p in posstr2 ) {
      var pos:Point3D = Point3D.fromStringInverted( p );
      ret.x = Math.max( ret.x, Math.abs( pos.x ) );
      ret.y = Math.max( ret.y, Math.abs( pos.y ) );
      ret.z = Math.max( ret.z, Math.abs( pos.z ) );
    }
    return( ret );
  }
  /**
    scale position by `scale`
  **/
  public function scaleCoord( scale:Float ):Void {
    var pos:Array< Point3D > = getPositions0();
    for ( p in pos ) p.multiply( scale );
    setPositions0( pos );
    pos = getPositions1();
    for ( p in pos ) p.multiply( scale );
    setPositions1( pos );
    pos = getPositions2();
    for ( p in pos ) p.multiply( scale );
    setPositions2( pos );
  }
  /**
    predefined cost for drawing; returns number of triangles.
  **/
  public override function getDataSize():Int { return( posstr0.length ); }
}
