package demos.nurbs.curveapp;

import java.io.File;
import java.util.Vector;

import simple.xml.Element;
import simple.xml.ElementList;
import simple.xml.Root;
import simple.xml.Serializer;
import simple.xml.load.Persister;

/**
 * Třída definice NURBS křivky, vystavěna podle návrhového vzoru Singleton
 * @author Tomáš Hráský
 *
 */
@Root(name="curve")
public class Curve
{
  /**
   * Odkaz na instanci třídy
   */
  private static Curve singleton;
  /**
   * Indikuje, zda je zadání křivky kompletní
   */
  @Element(name="finished")
    private boolean isCurveFinished;

  /**
   * Index aktuálního vybraného řídícího bodu
   */
  private int bodIndex = -1;

  /**
   * Stupeň křivky
   */
  @Element(name="order")
    private int order=3;
	
  /**
   * Pole souřadnic řídícíh bodů 
   * 
   */
  private float[] ctrlPoints;
	
  /**
   * Pole hodnot uzlového vektoru
   */
  private	float knots[];

  /**
   * Kolekce vektor pro persistenci souřadnic řídících bodů
   */
  @ElementList(name="ctrlpoints",type=MyFloat.class)
    private Vector<MyFloat> ctrlVector;
	
  /**
   * Kolekce vektor pro persistenci uzlového vektoru
   */
  @ElementList(name="knots",type=MyFloat.class)
    private Vector<MyFloat> knotVector;
	
  /**
   * Vytvoří prázdnou definici křivky
   */
  public void clear(){
    isCurveFinished=false;
    ctrlPoints=new float[0];
    knots=new float[0];
    order=3;
  }
	
  /**
   * Pomocí framweorku Simple serializuje definici křivky do XML souboru
   * @param f soubor pro uložení
   */
  public void persist(File f){
    ctrlVector=new Vector<MyFloat>(ctrlPoints.length);
    knotVector=new Vector<MyFloat>(knots.length);
		
    for(Float ff:ctrlPoints)
      ctrlVector.add(new MyFloat(ff));
		
    for(Float ff:knots)
      knotVector.add(new MyFloat(ff));
		
    Serializer s=new Persister(); 
    try {
      System.out.println("ukládám");
      s.write(Curve.getInstance(),f);
    } catch (Exception e1) {
      e1.printStackTrace();
    }


  }
	
  /**
   * Vytvoří pomocí frameworku Simple křivku z definice uložené v XML souboru 
   * @param f soubor,z něhož se má definice načíst
   * @throws Exception chyba při čtení ze souboru
   */
  public void unPersist(File f) throws Exception{
    Serializer s=new Persister();
    Curve c=s.read(Curve.class,f);
    initFromCurve(c);
  }
	
  /**
   * Inicializuje objekt podle jiného objektu typu Curve
   * @param c referenční objekt - křivka
   */
  private void initFromCurve(Curve c) {
    this.order=c.getOrder();
    this.ctrlPoints=new float[c.getCtrlVector().size()];
    this.knots=new float[c.getKnotVector().size()];
    int i=0;
    for(MyFloat f:c.getCtrlVector())
      ctrlPoints[i++]=f.getValue();
    i=0;
    for(MyFloat f:c.getKnotVector())
      knots[i++]=f.getValue();
		
    this.isCurveFinished=c.isCurveFinished();
  }

  /**
   * Konstruktor, nastaví prázdné hodnoty polí definujících NURBS křivku
   */
  private Curve(){
    ctrlPoints=new float[0];
    knots=new float[0];
    isCurveFinished=false;
  }
	
  /**
   * Vrací instanci třídy (podle návrhového vzoru Singleton)
   * @return instance třídy Curve
   */
  public static Curve getInstance() {
    if (singleton == null)
      singleton = new Curve();
    return singleton;

  }
	
  /**
   * Vrací pole uzlového vektoru
   * @return pole hodnot uzlového vektoru
   */
  public float[] getKnots() {
    return this.knots;
  }

  /**
   * Vrací pole s hodnotami souřadnic řídících bodů
   * @return pole souřadnic řídících bodů
   */
  public float[] getCtrlPoints() {
    return this.ctrlPoints;
  }

  /**
   * Vrací stupeň NURBS křivky
   * @return stupeň NURBS křivky
   */
  public int getOrder() {
    return this.order;
  }

  /**
   * Vrací index aktuálně vybraného řídícího bodu
   * @return index aktuálně vybraného řídícího bodu
   */
  public int getBodIndex() {
    return bodIndex;
  }

  /**
   * Nastavuje index požadovaného aktuálně vybraného řídícího bodu
   * @param bodIndex index požadovaného aktuálně vybraného řídícího bodu
   */
  public void setBodIndex(int bodIndex) {
    this.bodIndex = bodIndex;
  }
	
  /**
   * Vrací X-ovou souadnici aktuálně vybraného řídícího bodu, přepočítává hodnotu z homogenních souřadnic
   * @return X-ová souadnice aktuálně vybraného řídícího bodu
   */
  public float getActiveX(){
    if(bodIndex>=0){
      return ctrlPoints[bodIndex*4]/ctrlPoints[bodIndex*4+3];
    }
    else return 0;
  }
  /**
   * Vrací Y-ovou souadnici aktuálně vybraného řídícího bodu, přepočítává hodnotu z homogenních souřadnic
   * @return Y-ová souadnice aktuálně vybraného řídícího bodu
   */
  public float getActiveY(){
    if(bodIndex>=0){
      return ctrlPoints[bodIndex*4+1]/ctrlPoints[bodIndex*4+3];
    }
    else return 0;
  }
	
  /**
   * Vrací váhu aktuálně vybraného řídícího bodu
   * @return váha aktuálně vybraného řídícího bodu
   */
  public float getActiveW(){
    if(bodIndex>=0){
      return ctrlPoints[bodIndex*4+3];
    }
    else return 0;
  }
	
  /**
   * Nastavuje X-ovou souadnici aktuálně vybraného řídícího bodu, přepočítává hodnotu do homogenních souřadnic
   * @param x X-ová souřadnice aktuálně vybraného řídícího bodu
   */
  public void setActiveX(float x){
    if(bodIndex>=0){
      ctrlPoints[bodIndex*4]=x*ctrlPoints[bodIndex*4+3];
    }
  }
  /**
   * Nastavuje Y-ovou souadnici aktuálně vybraného řídícího bodu, přepočítává hodnotu do homogenních souřadnic
   * @param y Y-ová souřadnice aktuálně vybraného řídícího bodu
   */
	
  public void setActiveY(float y){
    if(bodIndex>=0){
      ctrlPoints[bodIndex*4+1]=y*ctrlPoints[bodIndex*4+3];
    }
  }
	
  /**
   *Nastavuje váhu aktuálně vybraného řídícího bodu, upravuje hodnoty stávajícíh souřadic vzhledem k váze a použití homogenních souřadnic 
   * @param w váha aktuálně vybraného řídícího bodu
   */
  public void setActiveW(float w){
    if(bodIndex>=0){
      float oldW=ctrlPoints[bodIndex*4+3];
      if(w>0){
        ctrlPoints[bodIndex*4+3]=w;
        //úprava souřadnic
        ctrlPoints[bodIndex*4]=ctrlPoints[bodIndex*4]/oldW*w;
        ctrlPoints[bodIndex*4+1]=ctrlPoints[bodIndex*4+1]/oldW*w;
      }
			
    }
  }

  /**
   * Nastavuje uzlový vektor
   * @param knots nový uzlový vektor
   */
  public void setKnots(float[] knots) {
    this.knots = knots;
  }

  /**
   * Vrací informaci o stavu dokončení definice křvky
   * @return true pokud je definice křivky kompletní, jinak false
   */
  public boolean isCurveFinished() {
    return isCurveFinished;
  }

  /**
   * Nastavuje řídící body
   * @param ctrlPoints pole souřadnic řídících bodů 
   */
  public void setCtrlPoints(float[] ctrlPoints) {
    this.ctrlPoints = ctrlPoints;
  }

  /**
   * Nastavuje stav dokončení definice křivky
   * @param b stav dokončení definice křivky
   *
   */
  public void setIsCurveFinished(boolean b) {
    isCurveFinished=b;
  }

  /**
   * Vrací vektor souřadnic řídích bodů pro serializaci
   * @return vektor souřadnic řídících bodů
   */
  private Vector<MyFloat> getCtrlVector() {
    return ctrlVector;
  }

  /**
   * Vrací vektor prvků uzlového vektoru pro serializaci
   * @return vektor prvků uzlového vektoru
   */
  private Vector<MyFloat> getKnotVector() {
    return knotVector;
  }

  /**
   * Nastaví stupeň křivky
   * @param order požadovaný stupeň
   */
  public void setOrder(int order) {
    this.order = order;
  }
}