/*
 * Label.java 28 nov. 2008
 *
 * Sweet Home 3D, Copyright (c) 2008 Emmanuel PUYBARET / eTeks <info@eteks.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.eteks.sweethome3d.model;

import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

/**
 * A free label.
 * @author Emmanuel Puybaret
 */
public class Label implements Selectable, Serializable, Elevatable {
  private static final long serialVersionUID = 1L;
  
  private static final double TWICE_PI = 2 * Math.PI;

  /**
   * The properties of a label that may change. <code>PropertyChangeListener</code>s added 
   * to a label will be notified under a property name equal to the string value of one these properties.
   */
  public enum Property {TEXT, X, Y, STYLE, ANGLE, LEVEL};
  
  private String    text;
  private float     x;
  private float     y;
  private TextStyle style;
  private float     angle;
  private Level     level;
  
  private transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

  public Label(String text, float x, float y) {
    this.text = text;
    this.x = x;
    this.y = y;
  }
  
  /**
   * Initializes transient fields to their default values 
   * and reads label from <code>in</code> stream with default reading method.
   */
  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    this.propertyChangeSupport = new PropertyChangeSupport(this);
    in.defaultReadObject();
  }

  /**
   * Adds the property change <code>listener</code> in parameter to this label.
   */
  public void addPropertyChangeListener(PropertyChangeListener listener) {
    this.propertyChangeSupport.addPropertyChangeListener(listener);
  }

  /**
   * Removes the property change <code>listener</code> in parameter from this label.
   */
  public void removePropertyChangeListener(PropertyChangeListener listener) {
    this.propertyChangeSupport.removePropertyChangeListener(listener);
  }

  /**
   * Returns the text of this label.
   */
  public String getText() {
    return this.text;
  }

  /**
   * Sets the text of this label. Once this label is updated, 
   * listeners added to this label will receive a change notification.
   */
  public void setText(String text) {
    if (text != this.text
        && (text == null || !text.equals(this.text))) {
      String oldText = this.text;
      this.text = text;
      this.propertyChangeSupport.firePropertyChange(Property.TEXT.name(), oldText, text);
    }
  }
   
  /**
   * Returns the abscissa of the text of this label.
   */
  public float getX() {
    return this.x;
  }

  /**
   * Sets the abscissa of the text of this label. Once this label is updated, 
   * listeners added to this label will receive a change notification.
   */
  public void setX(float x) {
    if (x != this.x) {
      float oldX = this.x;
      this.x = x;
      this.propertyChangeSupport.firePropertyChange(Property.X.name(), oldX, x);
    }
  }
  
  /**
   * Returns the ordinate of the text of this label.
   */
  public float getY() {
    return this.y;
  }

  /**
   * Sets the ordinate of the text of this label. Once this label is updated, 
   * listeners added to this label will receive a change notification.
   */
  public void setY(float y) {
    if (y != this.y) {
      float oldY = this.y;
      this.y = y;
      this.propertyChangeSupport.firePropertyChange(Property.Y.name(), oldY, y);
    }
  }

  /**
   * Returns the style used to display the text of this label.
   */
  public TextStyle getStyle() {
    return this.style;  
  }

  /**
   * Sets the style used to display the text of this label.
   * Once this label is updated, listeners added to this label will receive a change notification.
   */
  public void setStyle(TextStyle style) {
    if (style != this.style) {
      TextStyle oldStyle = this.style;
      this.style = style;
      this.propertyChangeSupport.firePropertyChange(Property.STYLE.name(), oldStyle, style);
    }
  }

  /**
   * Returns the angle in radians used to display this label.
   * @since 3.6 
   */
  public float getAngle() {
    return this.angle;
  }

  /**
   * Sets the angle in radians used to display this label. Once this piece is updated, 
   * listeners added to this piece will receive a change notification.
   * @since 3.6 
   */
  public void setAngle(float angle) {
    // Ensure angle is always positive and between 0 and 2 PI
    angle = (float)((angle % TWICE_PI + TWICE_PI) % TWICE_PI);
    if (angle != this.angle) {
      float oldAngle = this.angle;
      this.angle = angle;
      this.propertyChangeSupport.firePropertyChange(Property.ANGLE.name(), oldAngle, angle);
    }
  }
  
  /**
   * Returns the level which this label belongs to. 
   * @since 3.4
   */
  public Level getLevel() {
    return this.level;
  }

  /**
   * Sets the level of this label. Once this label is updated, 
   * listeners added to this label will receive a change notification.
   * @since 3.4
   */
  public void setLevel(Level level) {
    if (level != this.level) {
      Level oldLevel = this.level;
      this.level = level;
      this.propertyChangeSupport.firePropertyChange(Property.LEVEL.name(), oldLevel, level);
    }
  }

  /**
   * Returns <code>true</code> if this label is at the given level.
   * @since 3.4
   */
  public boolean isAtLevel(Level level) {
    return this.level == level;
  }
  
  /**
   * Returns the point of this label.
   * @return an array of the (x,y) coordinates of this label.
   */
  public float [][] getPoints() {
    return new float [][] {{this.x, this.y}};
  }
  
  /**
   * Returns <code>true</code> if the point of this label is contained
   * in the horizontal rectangle which opposite corners are at points
   * (<code>x0</code>, <code>y0</code>) and (<code>x1</code>, <code>y1</code>).
   */
  public boolean intersectsRectangle(float x0, float y0, 
                                     float x1, float y1) {
    Rectangle2D rectangle = new Rectangle2D.Float(x0, y0, 0, 0);
    rectangle.add(x1, y1);
    return rectangle.contains(this.x, this.y);
  }
  
  /**
   * Returns <code>true</code> if this text is at the point at (<code>x</code>, <code>y</code>)
   * with a given <code>margin</code>.
   */
  public boolean containsPoint(float x, float y, float margin) {
    return Math.abs(x - this.x) <= margin && Math.abs(y - this.y) <= margin;
  }
  
  /**
   * Moves this label of (<code>dx</code>, <code>dy</code>) units.
   */
  public void move(float dx, float dy) {
    setX(getX() + dx);
    setY(getY() + dy);
  }
  
  /**
   * Returns a clone of this label.
   */
  @Override
  public Label clone() {
    try {
      Label clone = (Label)super.clone();
      clone.propertyChangeSupport = new PropertyChangeSupport(clone);
      clone.level = null;
      return clone;
    } catch (CloneNotSupportedException ex) {
      throw new IllegalStateException("Super class isn't cloneable"); 
    }
  }
}
