package jogamp.opengl.glu.gl2.nurbs;
import jogamp.opengl.glu.nurbs.*;

/*
 ** License Applicability. Except to the extent portions of this file are
 ** made subject to an alternative license as permitted in the SGI Free
 ** Software License B, Version 2.0 (the "License"), the contents of this
 ** file are subject only to the provisions of the License. You may not use
 ** this file except in compliance with the License. You may obtain a copy
 ** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600
 ** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at:
 **
 ** http://oss.sgi.com/projects/FreeB
 **
 ** Note that, as provided in the License, the Software is distributed on an
 ** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS
 ** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND
 ** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A
 ** PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
 **
 ** Original Code. The Original Code is: OpenGL Sample Implementation,
 ** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics,
 ** Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc.
 ** Copyright in any portions created by third parties is as indicated
 ** elsewhere herein. All Rights Reserved.
 **
 ** Additional Notice Provisions: The application programming interfaces
 ** established by SGI in conjunction with the Original Code are The
 ** OpenGL(R) Graphics System: A Specification (Version 1.2.1), released
 ** April 1, 1999; The OpenGL(R) Graphics System Utility Library (Version
 ** 1.3), released November 4, 1998; and OpenGL(R) Graphics with the X
 ** Window System(R) (Version 1.3), released October 19, 1998. This software
 ** was created using the OpenGL(R) version 1.2.1 Sample Implementation
 ** published by SGI, but has not been independently verified as being
 ** compliant with the OpenGL(R) version 1.2.1 Specification.
 */

import java.lang.reflect.Method;

import com.jogamp.opengl.GL2;
import com.jogamp.opengl.glu.GLUnurbs;

/**
 * Base object for working with NURBS curves and surfaces
 *
 * @author Tomas Hrasky
 *
 */
public class GLUgl2nurbsImpl implements GLUnurbs {

  /**
   * Curve type - no type
   */
  public static final int CT_NONE = 0;

  /**
   * Curve type - NURBS curve
   */
  public static final int CT_NURBSCURVE = 1;

  /**
   * Curve type - picewise linear curve
   */
  public static final int CT_NPWLCURVE = 2;

  /**
   * Matrixes autoloading
   */
  private final boolean autoloadmode;

  /**
   * Using callback
   * private final int callBackFlag;
   */

  /**
   * Object for error call backs
   * private final Object errorCallback;
   */

  /**
   * List of map definitions
   */
  Maplist maplist;

  /**
   * Indicates validity of data
   */
  private int isDataValid;

  /**
   * Are we in the middle of curve processing
   */
  private int inCurve;

  /**
   * Current curve
   */
  private O_curve currentCurve;

  /**
   * Are we in trim
   */
  private boolean inTrim;

  /**
   * Are we playbacking curve/surface rendering
   */
  private boolean playBack;

  /**
   * Next curve in linked list
   */
  private O_curve nextCurve;

  /**
   * Is curve modified
   * private int isCurveModified;
   */

  /**
   * Object holding rendering settings
   */
  private final Renderhints renderhints;

  /**
   * Display list
   * private DisplayList dl;
   */

  /**
   * Object for subdividing curves and surfaces
   */
  private final Subdivider subdivider;

  /**
   * Object responsible for rendering
   * private Backend backend;
   */

  /**
   * Next picewise linear curve in linked list
   * private O_pwlcurve nextPwlcurve;
   */

  /**
   * Next trimming NURBS curve in linked list
   */
  private O_nurbscurve nextNurbscurve;

  /**
   * Are we in the middle of surface processing
   */
  private int inSurface;

  /**
   * Are there any changes in trimming
   * private boolean isTrimModified;
   */

  /**
   * Are there any changes in surface data
   * private boolean isDataSurfaceModified;
   */

  /**
   * Nurber of trmims of processed surface
   */
  private int numTrims;

  /**
   * Current processed surface
   */
  private O_surface currentSurface;

  /**
   * Next trimming curve
   */
  private O_trim nextTrim;

  /**
   * Nextr surface in linked list
   * private O_nurbssurface nextNurbssurface;
   */

  /**
   * Are there any changes in surface
   * private boolean isSurfaceModified;
   */

  /**
   * Initializes default GLUgl2nurbs object
   */
  public GLUgl2nurbsImpl() {
    // DONE
    maplist = new Maplist();
    renderhints = new Renderhints();
    subdivider = new Subdivider();
    // original code

    redefineMaps();

    defineMap(GL2.GL_MAP2_NORMAL, 0, 3);
    defineMap(GL2.GL_MAP1_NORMAL, 0, 3);
    defineMap(GL2.GL_MAP2_TEXTURE_COORD_1, 0, 1);
    defineMap(GL2.GL_MAP1_TEXTURE_COORD_1, 0, 1);
    defineMap(GL2.GL_MAP2_TEXTURE_COORD_2, 0, 2);
    defineMap(GL2.GL_MAP1_TEXTURE_COORD_2, 0, 2);
    defineMap(GL2.GL_MAP2_TEXTURE_COORD_3, 0, 3);
    defineMap(GL2.GL_MAP1_TEXTURE_COORD_3, 0, 3);
    defineMap(GL2.GL_MAP2_TEXTURE_COORD_4, 1, 4);
    defineMap(GL2.GL_MAP1_TEXTURE_COORD_4, 1, 4);
    defineMap(GL2.GL_MAP2_VERTEX_4, 1, 4);
    defineMap(GL2.GL_MAP1_VERTEX_4, 1, 4);
    defineMap(GL2.GL_MAP2_VERTEX_3, 0, 3);
    defineMap(GL2.GL_MAP1_VERTEX_3, 0, 3);
    defineMap(GL2.GL_MAP2_COLOR_4, 0, 4);
    defineMap(GL2.GL_MAP1_COLOR_4, 0, 4);
    defineMap(GL2.GL_MAP2_INDEX, 0, 1);
    defineMap(GL2.GL_MAP1_INDEX, 0, 1);

    setnurbsproperty(GL2.GL_MAP1_VERTEX_3, NurbsConsts.N_SAMPLINGMETHOD,
                     NurbsConsts.N_PATHLENGTH);
    setnurbsproperty(GL2.GL_MAP1_VERTEX_4, NurbsConsts.N_SAMPLINGMETHOD,
                     NurbsConsts.N_PATHLENGTH);
    setnurbsproperty(GL2.GL_MAP2_VERTEX_3, NurbsConsts.N_SAMPLINGMETHOD,
                     NurbsConsts.N_PATHLENGTH);
    setnurbsproperty(GL2.GL_MAP2_VERTEX_4, NurbsConsts.N_SAMPLINGMETHOD,
                     NurbsConsts.N_PATHLENGTH);

    setnurbsproperty(GL2.GL_MAP1_VERTEX_3, NurbsConsts.N_PIXEL_TOLERANCE,
                     (float) 50.0);
    setnurbsproperty(GL2.GL_MAP1_VERTEX_4, NurbsConsts.N_PIXEL_TOLERANCE,
                     (float) 50.0);
    setnurbsproperty(GL2.GL_MAP2_VERTEX_3, NurbsConsts.N_PIXEL_TOLERANCE,
                     (float) 50.0);
    setnurbsproperty(GL2.GL_MAP2_VERTEX_4, NurbsConsts.N_PIXEL_TOLERANCE,
                     (float) 50.0);

    setnurbsproperty(GL2.GL_MAP1_VERTEX_3, NurbsConsts.N_ERROR_TOLERANCE,
                     (float) 0.50);
    setnurbsproperty(GL2.GL_MAP1_VERTEX_4, NurbsConsts.N_ERROR_TOLERANCE,
                     (float) 0.50);
    setnurbsproperty(GL2.GL_MAP2_VERTEX_3, NurbsConsts.N_ERROR_TOLERANCE,
                     (float) 0.50);
    setnurbsproperty(GL2.GL_MAP2_VERTEX_4, NurbsConsts.N_ERROR_TOLERANCE,
                     (float) 0.50);

    setnurbsproperty(GL2.GL_MAP1_VERTEX_3, NurbsConsts.N_S_STEPS,
                     (float) 100.0);
    setnurbsproperty(GL2.GL_MAP1_VERTEX_4, NurbsConsts.N_S_STEPS,
                     (float) 100.0);
    setnurbsproperty(GL2.GL_MAP2_VERTEX_3, NurbsConsts.N_S_STEPS,
                     (float) 100.0);
    setnurbsproperty(GL2.GL_MAP2_VERTEX_4, NurbsConsts.N_S_STEPS,
                     (float) 100.0);

    setnurbsproperty(GL2.GL_MAP1_VERTEX_3, NurbsConsts.N_SAMPLINGMETHOD,
                     NurbsConsts.N_PATHLENGTH);

    set_domain_distance_u_rate(100.0);
    set_domain_distance_v_rate(100.0);
    set_is_domain_distance_sampling(0);

    this.autoloadmode = true;

    // this.callBackFlag = 0;

    // this.errorCallback = null;
  }

  /**
   * Sets domain distance for dom.dist. sampling in u direction
   *
   * @param d
   *            distance
   */
  private void set_domain_distance_u_rate(final double d) {
    // DONE
    subdivider.set_domain_distance_u_rate(d);
  }

  /**
   * Sets domain distance for dom.dist. sampling in v direction
   *
   * @param d
   *            distance
   */
  private void set_domain_distance_v_rate(final double d) {
    // DONE
    subdivider.set_domain_distance_v_rate(d);
  }

  /**
   * Begins new NURBS curve
   */
  public void bgncurve() {
    // DONE
    final O_curve o_curve = new O_curve();
    thread("do_bgncurve", o_curve);
  }

  /**
   * Calls a method with given name and passes argumet
   *
   * @param name
   *            name of a method to be called
   * @param arg
   *            parameter to be passed to called method
   */
  private void thread(final String name, final Object arg) {
    // DONE
    final Class<?> partype[] = new Class[1];
    partype[0] = arg.getClass();
    Method m;
    try {
      m = this.getClass().getMethod(name, partype);
      /* if (dl != null) {
        dl.append(this, m, arg);
      } else */ {
        m.invoke(this, new Object[] { arg });
      }
    } catch (final Throwable e) {
      e.printStackTrace();
    }

  }

  /**
   * Calls a method with given name
   *
   * @param name
   *            name of a method to be called
   */
  private void thread2(final String name) {
    // DONE
    try {
      final Method m = this.getClass().getMethod(name, (Class[]) null);
      /* if (dl != null) {
        dl.append(this, m, null);
      } else */ {
        m.invoke(this, (Object[]) null);
      }
    } catch (final Throwable e) {
      e.printStackTrace();
    }
  }

  /**
   * Begins a NURBS curve
   *
   * @param o_curve
   *            curve object
   */
  public void do_bgncurve(final O_curve o_curve) {
    if (inCurve > 0) {
      do_nurbserror(6);
      endcurve();
    }
    inCurve = 1;
    currentCurve = o_curve;

    currentCurve.curvetype = CT_NONE;

    if (inTrim) {
      if (!nextCurve.equals(o_curve)) {
        // isCurveModified = 1;
        nextCurve = o_curve;
      }
    } else {
      if (!playBack)
        bgnrender();
      isDataValid = 1;
    }
    nextCurve = o_curve.next;
    // kind of solution of union
    // nextPwlcurve = o_curve.o_pwlcurve;
    nextNurbscurve = o_curve.o_nurbscurve;
  }

  /**
   * Begins new surface
   *
   * @param o_surface
   *            surface object
   */
  public void do_bgnsurface(final O_surface o_surface) {
    // DONE
    if (inSurface > 0) {
      do_nurbserror(27);
      endsurface();
    }
    inSurface = 1;
    if (!playBack)
      bgnrender();

    // isTrimModified = false;
    // isDataSurfaceModified = false;
    isDataValid = 1;
    numTrims = 0;
    currentSurface = o_surface;
    nextTrim = o_surface.o_trim;
    // nextNurbssurface = o_surface.o_nurbssurface;
  }

  /**
   * End a curve
   */
  public void endcurve() {
    // DONE
    thread2("do_endcurve");
  }

  /**
   * Ends surface
   */
  public void do_endsurface() {
    // DONE
    if (inTrim) {
      do_nurbserror(12);
      endtrim();
    }

    if (inSurface <= 0) {
      do_nurbserror(13);
      return;
    }

    inSurface = 0;

    // nextNurbssurface = null;

    if (isDataValid <= 0) {
      return;
    }

    if (nextTrim != null) {
      // isTrimModified = true;
      nextTrim = null;
    }

    // TODO errval ??
    if (numTrims > 0) {
      //                System.out.println("TODO glunurbs.do_endsurface - numtrims > 0");
    }

    subdivider.beginQuilts(new GL2Backend());
    for (O_nurbssurface n = currentSurface.o_nurbssurface; n != null; n = n.next) {
      subdivider.addQuilt(n.bezier_patches);
    }
    subdivider.endQuilts();
    subdivider.drawSurfaces();
    if (!playBack)
      endrender();

  }

  /**
   * Ends a curve
   */
  public void do_endcurve() {
    // DONE
    //            // System.out.println("do_endcurve");
    if (inCurve <= 0) {
      do_nurbserror(7);
      return;
    }
    inCurve = 0;

    nextCurve = null;

    if (currentCurve.curvetype == CT_NURBSCURVE) {
      // nextNurbscurve = null;
      // currentCurve.o_nurbscurve=null;
    } else {
      // nextPwlcurve = null;
      // currentCurve.o_pwlcurve=null;
    }
    if (!inTrim) {
      if (isDataValid <= 0) {
        return;
      }
      // TODO errval?
      if (currentCurve.curvetype == CT_NURBSCURVE) {
        subdivider.beginQuilts(new GL2Backend());

        for (O_nurbscurve n = currentCurve.o_nurbscurve; n != null; n = n.next)
          subdivider.addQuilt(n.bezier_curves);

        subdivider.endQuilts();
        subdivider.drawCurves();
        if (!playBack)
          endrender();
      } else {
        if (!playBack)
          endrender();
        do_nurbserror(9);
      }
    }

  }

  /**
   * Method for handling error codes
   *
   * @param i
   *            error code
   */
  private void do_nurbserror(final int i) {
    // TODO nurberror
    //            System.out.println("TODO nurbserror " + i);
  }

  /**
   * Begin rendering
   */
  private void bgnrender() {
    // DONE
    if (autoloadmode) {
      loadGLMatrices();
    }
  }

  /**
   * Load matrices from OpenGL state machine
   */
  private void loadGLMatrices() {
    // TODO Auto-generated method stub
    //            System.out.println("TODO glunurbs.loadGLMatrices");
  }

  /**
   * End rendering
   */
  private void endrender() {
    // DONE
  }

  /**
   * Make a NURBS curve
   *
   * @param nknots
   *            number of knots in knot vector
   * @param knot
   *            knot vector
   * @param stride
   *            number of control points coordinates
   * @param ctlarray
   *            control points
   * @param order
   *            order of the curve
   * @param realType
   *            type of the curve
   */
  public void nurbscurve(final int nknots, final float[] knot, final int stride,
                         final float[] ctlarray, final int order, final int realType) {
    // DONE
    final Mapdesc mapdesc = maplist.locate(realType);
    if (mapdesc == null) {
      do_nurbserror(35);
      isDataValid = 0;
      return;
    }
    if (ctlarray == null) {
      do_nurbserror(36);
      isDataValid = 0;
      return;
    }
    if (stride < 0) {
      do_nurbserror(34);
      isDataValid = 0;
      return;
    }
    final Knotvector knots = new Knotvector(nknots, stride, order, knot);

    if (!do_check_knots(knots, "curve"))
      return;

    final O_nurbscurve o_nurbscurve = new O_nurbscurve(realType);
    o_nurbscurve.bezier_curves = new Quilt(mapdesc);
    final CArrayOfFloats ctrlcarr = new CArrayOfFloats(ctlarray);
    o_nurbscurve.bezier_curves.toBezier(knots, ctrlcarr, mapdesc
                                        .getNCoords());
    thread("do_nurbscurve", o_nurbscurve);
  }

  /**
   * Check knot vector specification
   *
   * @param knots
   *            knot vector
   * @param msg
   *            error message
   * @return knot vector is / is not valid
   */
  public boolean do_check_knots(final Knotvector knots, final String msg) {
    // DONE
    final int status = knots.validate();
    if (status > 0) {
      do_nurbserror(status);
      if (renderhints.errorchecking != NurbsConsts.N_NOMSG)
        knots.show(msg);
    }
    return (status > 0) ? false : true;
  }

  /**
   * Draw a curve
   *
   * @param o_nurbscurve
   *            NURBS curve object
   */
  public void do_nurbscurve(final O_nurbscurve o_nurbscurve) {
    // DONE

    if (inCurve <= 0) {
      bgncurve();
      inCurve = 2;
    }

    if (o_nurbscurve.used) {
      do_nurbserror(23);
      isDataValid = 0;
      return;
    } else
      o_nurbscurve.used = true;

    if (currentCurve.curvetype == CT_NONE) {
      currentCurve.curvetype = CT_NURBSCURVE;
    } else if (currentCurve.curvetype != CT_NURBSCURVE) {
      do_nurbserror(24);
      isDataValid = 0;
      return;
    }

    // it was necessary to overcome problem with pointer to pointer here

    // if(!o_nurbscurve.equals(nextNurbscurve)){
    if (!o_nurbscurve.equals(currentCurve.o_nurbscurve)) {
      // isCurveModified = 1;
      currentCurve.o_nurbscurve = o_nurbscurve;
      // nextNurbscurve=o_nurbscurve;

    }

    nextNurbscurve = o_nurbscurve.next;

    if (!currentCurve.equals(o_nurbscurve.owner)) {
      // isCurveModified = 1;
      o_nurbscurve.owner = currentCurve;
    }


    /**
    if (o_nurbscurve.owner == null) {
      isCurveModified = 1;
    } */

    if (inCurve == 2) {
      endcurve();
    }
  }

  /**
   * Draw NURBS surface
   *
   * @param o_nurbssurface
   *            NURBS surface object
   */
  public void do_nurbssurface(final O_nurbssurface o_nurbssurface) {
    // DONE
    if (inSurface <= 0) {
      bgnsurface();
      inSurface = 2;
    }
    if (o_nurbssurface.used) {
      do_nurbserror(25);
      isDataValid = 0;
      return;
    } else
      o_nurbssurface.used = true;

    // Always true, instances of diff classes are compared: if (!o_nurbssurface.equals(nextNurbscurve)) {
      // isSurfaceModified = true;
      // nextNurbssurface=o_nurbssurface;
      currentSurface.o_nurbssurface = o_nurbssurface;
    // }

    if (!currentSurface.equals(o_nurbssurface.owner)) {
      // isSurfaceModified = true;
      o_nurbssurface.owner = currentSurface;
    }

    // nextNurbssurface = o_nurbssurface.next;

    if (inSurface == 2)
      endsurface();
  }

  /**
   * (Re)Inicialize maps
   */
  public void redefineMaps() {
    // DONE
    maplist.initialize();
  }

  /**
   * Define a map of given properties
   *
   * @param type
   *            map type
   * @param rational
   *            is rational
   * @param ncoords
   *            number of control point coordinates
   */
  public void defineMap(final int type, final int rational, final int ncoords) {
    // DONE
    maplist.define(type, rational, ncoords);
  }

  /**
   * Set NURBS property
   *
   * @param type
   *            property type
   * @param tag
   *            property tag
   * @param value
   *            property value
   */
  public void setnurbsproperty(final int type, final int tag, final float value) {
    // DONE
    final Mapdesc mapdesc = maplist.locate(type);
    if (mapdesc == null) {
      do_nurbserror(35);
      return;
    }
    if (!mapdesc.isProperty(tag)) {
      do_nurbserror(26);
      return;
    }
    final Property prop = new Property(type, tag, value);
    thread("do_setnurbsproperty2", prop);
  }

  /**
   * Set parameters of existing property
   *
   * @param prop
   *            property
   */
  public void do_setnurbsproperty2(final Property prop) {
    final Mapdesc mapdesc = maplist.find(prop.type);
    mapdesc.setProperty(prop.tag, prop.value);
  }

  /**
   * Set given property to rendering hints
   *
   * @param prop
   *            property to be set
   */
  public void do_setnurbsproperty(final Property prop) {
    // DONE
    renderhints.setProperty(prop);
    // TODO freeproperty?
  }

  /**
   * Sets wheteher we use domain distance sampling
   *
   * @param i
   *            domain distance sampling flag
   */
  public void set_is_domain_distance_sampling(final int i) {
    // DONE
    subdivider.set_is_domain_distance_sampling(i);
  }

  /**
   * Begin new surface
   */
  public void bgnsurface() {
    // DONE
    final O_surface o_surface = new O_surface();
    // TODO nuid
    //            System.out.println("TODO glunurbs.bgnsurface nuid");
    thread("do_bgnsurface", o_surface);
  }

  /**
   * End current surface
   */
  public void endsurface() {
    // DONE
    thread2("do_endsurface");
  }

  /**
   * End surface trimming
   */
  private void endtrim() {
    // TODO Auto-generated method stub
    //            System.out.println("TODO glunurbs.endtrim");
  }

  /**
   * Make NURBS surface
   *
   * @param sknot_count
   *            number of knots in s direction
   * @param sknot
   *            knot vector in s direction
   * @param tknot_count
   *            number of knots in t direction
   * @param tknot
   *            knot vector in t direction
   * @param s_stride
   *            number of coords of control points in s direction
   * @param t_stride
   *            number of coords of control points in t direction
   * @param ctlarray
   *            control points
   * @param sorder
   *            order of curve in s direction
   * @param torder
   *            order of curve in t direction
   * @param type
   *            NURBS surface type (rational,...)
   */
  public void nurbssurface(final int sknot_count, final float[] sknot, final int tknot_count,
                           final float[] tknot, final int s_stride, final int t_stride, final float[] ctlarray,
                           final int sorder, final int torder, final int type) {
    // DONE
    final Mapdesc mapdesc = maplist.locate(type);
    if (mapdesc == null) {
      do_nurbserror(35);
      isDataValid = 0;
      return;
    }
    if (s_stride < 0 || t_stride < 0) {
      do_nurbserror(34);
      isDataValid = 0;
      return;
    }
    final Knotvector sknotvector = new Knotvector(sknot_count, s_stride, sorder,
                                            sknot);
    if (!do_check_knots(sknotvector, "surface"))
      return;
    final Knotvector tknotvector = new Knotvector(tknot_count, t_stride, torder,
                                            tknot);
    if (!do_check_knots(tknotvector, "surface"))
      return;

    final O_nurbssurface o_nurbssurface = new O_nurbssurface(type);
    o_nurbssurface.bezier_patches = new Quilt(mapdesc);

    final CArrayOfFloats ctrlarr = new CArrayOfFloats(ctlarray);
    o_nurbssurface.bezier_patches.toBezier(sknotvector, tknotvector,
                                           ctrlarr, mapdesc.getNCoords());
    thread("do_nurbssurface", o_nurbssurface);
  }
}
