/*
 * Home3DAttributesController.java 25 juin 07
 *
 * Sweet Home 3D, Copyright (c) 2007 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.viewcontroller;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoableEdit;
import javax.swing.undo.UndoableEditSupport;

import com.eteks.sweethome3d.model.Home;
import com.eteks.sweethome3d.model.HomeEnvironment;
import com.eteks.sweethome3d.model.HomeTexture;
import com.eteks.sweethome3d.model.UserPreferences;

/**
 * A MVC controller for home 3D attributes view.
 * @author Emmanuel Puybaret
 */
public class Home3DAttributesController implements Controller {
  /**
   * The properties that may be edited by the view associated to this controller. 
   */
  public enum Property {GROUND_COLOR, GROUND_PAINT, SKY_COLOR, SKY_PAINT, LIGHT_COLOR, WALLS_ALPHA, WALLS_TOP_COLOR}
  /**
   * The possible values for {@linkplain #getGroundPaint() ground paint type}.
   */
  public enum EnvironmentPaint {COLORED, TEXTURED} 
  
  private final Home                  home;
  private final UserPreferences       preferences;
  private final ViewFactory           viewFactory;
  private final ContentManager        contentManager;
  private final UndoableEditSupport   undoSupport;
  private TextureChoiceController     groundTextureController;
  private TextureChoiceController     skyTextureController;
  private final PropertyChangeSupport propertyChangeSupport;
  private DialogView                  home3DAttributesView;

  private int               groundColor;
  private EnvironmentPaint  groundPaint;
  private int               skyColor;
  private EnvironmentPaint  skyPaint;
  private int               lightColor;
  private float             wallsAlpha;

  /**
   * Creates the controller of 3D view with undo support.
   */
  public Home3DAttributesController(Home home,
                                    UserPreferences preferences,
                                    ViewFactory viewFactory, 
                                    ContentManager contentManager,
                                    UndoableEditSupport undoSupport) {
    this.home = home;
    this.preferences = preferences;
    this.viewFactory = viewFactory;
    this.contentManager = contentManager;
    this.undoSupport = undoSupport;    
    this.propertyChangeSupport = new PropertyChangeSupport(this);
    
    updateProperties();
  }

  /**
   * Returns the texture controller of the ground.
   */
  public TextureChoiceController getGroundTextureController() {
    // Create sub controller lazily only once it's needed
    if (this.groundTextureController == null) {      
      this.groundTextureController = new TextureChoiceController(
          this.preferences.getLocalizedString(Home3DAttributesController.class, "groundTextureTitle"), 
          this.preferences, this.viewFactory, this.contentManager);
      this.groundTextureController.addPropertyChangeListener(TextureChoiceController.Property.TEXTURE,
          new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent ev) {
              setGroundPaint(EnvironmentPaint.TEXTURED);
            }
          });
    }
    return this.groundTextureController;
  }

  /**
   * Returns the texture controller of the sky.
   */
  public TextureChoiceController getSkyTextureController() {
    // Create sub controller lazily only once it's needed
    if (this.skyTextureController == null) {
      this.skyTextureController = new TextureChoiceController(
          this.preferences.getLocalizedString(Home3DAttributesController.class, "skyTextureTitle"), 
          this.preferences, this.viewFactory, this.contentManager);
      this.skyTextureController.addPropertyChangeListener(TextureChoiceController.Property.TEXTURE,
          new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent ev) {
              setSkyPaint(EnvironmentPaint.TEXTURED);
            }
          });
    }
    return this.skyTextureController;
  }

  /**
   * Returns the view associated with this controller.
   */
  public DialogView getView() {
    // Create view lazily only once it's needed
    if (this.home3DAttributesView == null) {
      this.home3DAttributesView = this.viewFactory.createHome3DAttributesView(
          this.preferences, this); 
    }
    return this.home3DAttributesView;
  }
  
  /**
   * Displays the view controlled by this controller.
   */
  public void displayView(View parentView) {
    getView().displayView(parentView);
  }

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

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

  /**
   * Updates edited properties from the 3D attributes of the home edited by this controller.
   */
  protected void updateProperties() {
    HomeEnvironment homeEnvironment = this.home.getEnvironment();
    setGroundColor(homeEnvironment.getGroundColor());
    HomeTexture groundTexture = homeEnvironment.getGroundTexture();
    getGroundTextureController().setTexture(groundTexture);
    if (groundTexture != null) {
      setGroundPaint(EnvironmentPaint.TEXTURED);
    } else {
      setGroundPaint(EnvironmentPaint.COLORED);
    }
    setSkyColor(homeEnvironment.getSkyColor());
    HomeTexture skyTexture = homeEnvironment.getSkyTexture();
    getSkyTextureController().setTexture(skyTexture);
    if (skyTexture != null) {
      setSkyPaint(EnvironmentPaint.TEXTURED);
    } else {
      setSkyPaint(EnvironmentPaint.COLORED);
    }
    setLightColor(homeEnvironment.getLightColor());
    setWallsAlpha(homeEnvironment.getWallsAlpha());
  }
  
  /**
   * Sets the edited ground color.
   */
  public void setGroundColor(int groundColor) {
    if (groundColor != this.groundColor) {
      int oldGroundColor = this.groundColor;
      this.groundColor = groundColor;
      this.propertyChangeSupport.firePropertyChange(Property.GROUND_COLOR.name(), oldGroundColor, groundColor);
      
      setGroundPaint(EnvironmentPaint.COLORED);
    }
  }

  /**
   * Returns the edited ground color.
   */
  public int getGroundColor() {
    return this.groundColor;
  }

  /**
   * Sets whether the ground is colored or textured.
   */
  public void setGroundPaint(EnvironmentPaint groundPaint) {
    if (groundPaint != this.groundPaint) {
      EnvironmentPaint oldGroundPaint = this.groundPaint;
      this.groundPaint = groundPaint;
      this.propertyChangeSupport.firePropertyChange(Property.GROUND_PAINT.name(), oldGroundPaint, groundPaint);
    }
  }

  /**
   * Returns whether the ground is colored or textured.
   */
  public EnvironmentPaint getGroundPaint() {
    return this.groundPaint;
  }

  /**
   * Sets the edited sky color.
   */
  public void setSkyColor(int skyColor) {
    if (skyColor != this.skyColor) {
      int oldSkyColor = this.skyColor;
      this.skyColor = skyColor;
      this.propertyChangeSupport.firePropertyChange(Property.SKY_COLOR.name(), oldSkyColor, skyColor);
    }
  }

  /**
   * Returns the edited sky color.
   */
  public int getSkyColor() {
    return this.skyColor;
  }

  /**
   * Sets whether the sky is colored or textured.
   */
  public void setSkyPaint(EnvironmentPaint skyPaint) {
    if (skyPaint != this.skyPaint) {
      EnvironmentPaint oldSkyPaint = this.skyPaint;
      this.skyPaint = skyPaint;
      this.propertyChangeSupport.firePropertyChange(Property.SKY_PAINT.name(), oldSkyPaint, skyPaint);
    }
  }

  /**
   * Returns whether the sky is colored or textured.
   */
  public EnvironmentPaint getSkyPaint() {
    return this.skyPaint;
  }

  /**
   * Sets the edited light color.
   */
  public void setLightColor(int lightColor) {
    if (lightColor != this.lightColor) {
      int oldLightColor = this.lightColor;
      this.lightColor = lightColor;
      this.propertyChangeSupport.firePropertyChange(Property.LIGHT_COLOR.name(), oldLightColor, lightColor);
    }
  }

  /**
   * Returns the edited light color.
   */
  public int getLightColor() {
    return this.lightColor;
  }

  /**
   * Sets the edited walls transparency alpha.
   */
  public void setWallsAlpha(float wallsAlpha) {
    if (wallsAlpha != this.wallsAlpha) {
      float oldWallsAlpha = this.wallsAlpha;
      this.wallsAlpha = wallsAlpha;
      this.propertyChangeSupport.firePropertyChange(Property.WALLS_ALPHA.name(), oldWallsAlpha, wallsAlpha);
    }
  }

  /**
   * Returns the edited walls transparency alpha.
   */
  public float getWallsAlpha() {
    return this.wallsAlpha;
  }

  /**
   * Controls the modification of the 3D attributes of the edited home.
   */
  public void modify3DAttributes() {
    int   groundColor = getGroundColor();
    HomeTexture groundTexture = getGroundPaint() == EnvironmentPaint.TEXTURED
        ? getGroundTextureController().getTexture()
        : null;
    int   skyColor = getSkyColor();
    HomeTexture skyTexture = getSkyPaint() == EnvironmentPaint.TEXTURED
        ? getSkyTextureController().getTexture()
        : null;
    int   lightColor  = getLightColor();
    float wallsAlpha = getWallsAlpha();

    HomeEnvironment homeEnvironment = this.home.getEnvironment();
    int   oldGroundColor = homeEnvironment.getGroundColor();
    HomeTexture oldGroundTexture = homeEnvironment.getGroundTexture();
    int   oldSkyColor = homeEnvironment.getSkyColor();
    HomeTexture oldSkyTexture = homeEnvironment.getSkyTexture();
    int   oldLightColor = homeEnvironment.getLightColor();
    float oldWallsAlpha = homeEnvironment.getWallsAlpha();
    
    // Apply modification
    doModify3DAttributes(home, groundColor, groundTexture, skyColor,
        skyTexture, lightColor, wallsAlpha); 
    if (this.undoSupport != null) {
      UndoableEdit undoableEdit = new Home3DAttributesModificationUndoableEdit(
          this.home, this.preferences,
          oldGroundColor, oldGroundTexture, oldSkyColor,
          oldSkyTexture, oldLightColor, oldWallsAlpha,
          groundColor, groundTexture, skyColor, 
          skyTexture, lightColor, wallsAlpha);
      this.undoSupport.postEdit(undoableEdit);
    }
  }

  /**
   * Undoable edit for 3D attributes modification. This class isn't anonymous to avoid
   * being bound to controller and its view.
   */
  private static class Home3DAttributesModificationUndoableEdit extends AbstractUndoableEdit {
    private final Home            home;
    private final UserPreferences preferences;
    private final int             oldGroundColor;
    private final HomeTexture     oldGroundTexture;
    private final int             oldSkyColor;
    private final HomeTexture     oldSkyTexture;
    private final int             oldLightColor;
    private final float           oldWallsAlpha;
    private final int             groundColor;
    private final HomeTexture     groundTexture;
    private final int             skyColor;
    private final HomeTexture     skyTexture;
    private final int             lightColor;
    private final float           wallsAlpha;

    private Home3DAttributesModificationUndoableEdit(Home home,
                                                     UserPreferences preferences,
                                                     int oldGroundColor,
                                                     HomeTexture oldGroundTexture,
                                                     int oldSkyColor, 
                                                     HomeTexture oldSkyTexture,
                                                     int oldLightColor,
                                                     float oldWallsAlpha,
                                                     int groundColor,
                                                     HomeTexture groundTexture,
                                                     int skyColor,
                                                     HomeTexture skyTexture,
                                                     int lightColor,
                                                     float wallsAlpha) {
      this.home = home;
      this.preferences = preferences;
      this.oldGroundColor = oldGroundColor;
      this.oldGroundTexture = oldGroundTexture;
      this.oldSkyColor = oldSkyColor;
      this.oldSkyTexture = oldSkyTexture;
      this.oldLightColor = oldLightColor;
      this.oldWallsAlpha = oldWallsAlpha;
      this.groundColor = groundColor;
      this.groundTexture = groundTexture;
      this.skyColor = skyColor;
      this.skyTexture = skyTexture;
      this.lightColor = lightColor;
      this.wallsAlpha = wallsAlpha;
    }

    @Override
    public void undo() throws CannotUndoException {
      super.undo();
      doModify3DAttributes(this.home, this.oldGroundColor, this.oldGroundTexture, this.oldSkyColor,
          this.oldSkyTexture, this.oldLightColor, this.oldWallsAlpha); 
    }

    @Override
    public void redo() throws CannotRedoException {
      super.redo();
      doModify3DAttributes(this.home, this.groundColor, this.groundTexture, this.skyColor,
          this.skyTexture, this.lightColor, this.wallsAlpha); 
    }

    @Override
    public String getPresentationName() {
      return this.preferences.getLocalizedString(
          Home3DAttributesController.class, "undoModify3DAttributesName");
    }
  }

  /**
   * Modifies the 3D attributes of the given <code>home</code>.
   */
  private static void doModify3DAttributes(Home home,
                                           int groundColor, 
                                           HomeTexture groundTexture, 
                                           int skyColor, 
                                           HomeTexture skyTexture, int lightColor, 
                                           float wallsAlpha) {
    HomeEnvironment homeEnvironment = home.getEnvironment();
    homeEnvironment.setGroundColor(groundColor);
    homeEnvironment.setGroundTexture(groundTexture);
    homeEnvironment.setSkyColor(skyColor);
    homeEnvironment.setSkyTexture(skyTexture);
    homeEnvironment.setLightColor(lightColor);
    homeEnvironment.setWallsAlpha(wallsAlpha);
  }
}
