/*
 * PhotoController.java 5 mai 2009
 *
 * Sweet Home 3D, Copyright (c) 2009 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 java.lang.ref.WeakReference;

import com.eteks.sweethome3d.model.AspectRatio;
import com.eteks.sweethome3d.model.Camera;
import com.eteks.sweethome3d.model.Home;
import com.eteks.sweethome3d.model.UserPreferences;

/**
 * The controller of the photo creation view.
 * @author Emmanuel Puybaret
 */
public class PhotoController extends AbstractPhotoController {
  /**
   * The properties that may be edited by the view associated to this controller.
   */
  public enum Property {TIME, LENS, RENDERER}

  private final Home                  home;
  private final UserPreferences       preferences;
  private final ViewFactory           viewFactory;
  private final PropertyChangeSupport propertyChangeSupport;
  private final CameraChangeListener  cameraChangeListener;
  private DialogView                  photoView;

  private long                        time;
  private Camera.Lens                 lens;
  private String                      renderer;

  public PhotoController(Home home,
                         UserPreferences preferences,
                         View view3D, ViewFactory viewFactory,
                         ContentManager contentManager) {
    super(home, preferences, view3D, contentManager);
    this.home = home;
    this.preferences = preferences;
    this.viewFactory = viewFactory;
    this.propertyChangeSupport = new PropertyChangeSupport(this);

    this.cameraChangeListener = new CameraChangeListener(this);
    home.getCamera().addPropertyChangeListener(this.cameraChangeListener);
    home.addPropertyChangeListener(Home.Property.CAMERA, new HomeCameraChangeListener(this));
    updateProperties();
  }

  /**
   * Home camera listener that updates properties when home camera changes. This listener is bound to this controller
   * with a weak reference to avoid strong link between home and this controller.
   */
  private static class HomeCameraChangeListener implements PropertyChangeListener {
    private WeakReference<PhotoController> photoController;

    public HomeCameraChangeListener(PhotoController photoController) {
      this.photoController = new WeakReference<PhotoController>(photoController);
    }

    public void propertyChange(PropertyChangeEvent ev) {
      // If controller was garbage collected, remove this listener from home
      final PhotoController controller = this.photoController.get();
      if (controller == null) {
        ((Home)ev.getSource()).removePropertyChangeListener(Home.Property.CAMERA, this);
      } else {
        ((Camera)ev.getOldValue()).removePropertyChangeListener(controller.cameraChangeListener);
        controller.updateProperties();
        ((Camera)ev.getNewValue()).addPropertyChangeListener(controller.cameraChangeListener);
      }
    }
  }

  /**
   * Camera listener that updates properties when camera changes. This listener is bound to this controller
   * with a weak reference to avoid strong link between home and this controller.
   */
  private static class CameraChangeListener implements PropertyChangeListener {
    private WeakReference<AbstractPhotoController> photoController;

    public CameraChangeListener(AbstractPhotoController photoController) {
      this.photoController = new WeakReference<AbstractPhotoController>(photoController);
    }

    public void propertyChange(PropertyChangeEvent ev) {
      // If controller was garbage collected, remove this listener from camera
      final AbstractPhotoController controller = this.photoController.get();
      if (controller == null) {
        ((Camera)ev.getSource()).removePropertyChangeListener(this);
      } else {
        controller.updateProperties();
      }
    }
  }

  /**
   * Returns the view associated with this controller.
   */
  public DialogView getView() {
    // Create view lazily only once it's needed
    if (this.photoView == null) {
      this.photoView = this.viewFactory.createPhotoView(this.home, this.preferences, this);
    }
    return this.photoView;
  }

  /**
   * 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 photo creation preferences.
   */
  protected void updateProperties() {
    // Update properties only once this object is initialized
    if (this.home != null) {
      super.updateProperties();
      setTime(this.home.getCamera().getTime());
      setLens(this.home.getCamera().getLens());
      String renderer = this.home.getCamera().getRenderer();
      if (renderer == null) {
        renderer = this.preferences.getPhotoRenderer();
      }
      setRenderer(renderer, false);
    }
  }

  /**
   * Sets the edited time in UTC time zone.
   */
  public void setTime(long time) {
    if (this.time != time) {
      long oldTime = this.time;
      this.time = time;
      this.propertyChangeSupport.firePropertyChange(Property.TIME.name(), oldTime, time);
      Camera homeCamera = this.home.getCamera();
      homeCamera.removePropertyChangeListener(this.cameraChangeListener);
      homeCamera.setTime(time);
      homeCamera.addPropertyChangeListener(this.cameraChangeListener);
    }
  }

  /**
   * Returns the edited time in UTC time zone.
   */
  public long getTime() {
    return this.time;
  }

  /**
   * Sets the edited camera lens.
   */
  public void setLens(Camera.Lens lens) {
    if (this.lens != lens) {
      Camera.Lens oldLens = this.lens;
      this.lens = lens;
      this.propertyChangeSupport.firePropertyChange(Property.LENS.name(), oldLens, lens);
      if (lens == Camera.Lens.SPHERICAL) {
        setAspectRatio(AspectRatio.RATIO_2_1);
      } else if (lens == Camera.Lens.FISHEYE) {
        setAspectRatio(AspectRatio.SQUARE_RATIO);
      }
      this.home.getCamera().setLens(this.lens);
    }
  }

  /**
   * Returns the edited camera lens.
   */
  public Camera.Lens getLens() {
    return this.lens;
  }

  /**
   * Sets the edited camera rendering engine.
   */
  public void setRenderer(String renderer) {
    setRenderer(renderer, true);
  }

  private void setRenderer(String renderer, boolean updatePreferences) {
    if (this.renderer != renderer) {
      String oldRenderer = this.renderer;
      this.renderer = renderer;
      this.propertyChangeSupport.firePropertyChange(Property.RENDERER.name(), oldRenderer, renderer);
      this.home.getTopCamera().setRenderer(this.renderer);
      this.home.getObserverCamera().setRenderer(this.renderer);
      if (updatePreferences) {
        this.preferences.setPhotoRenderer(renderer);
      }
    }
  }

  /**
   * Returns the edited camera rendering engine.
   */
  public String getRenderer() {
    return this.renderer;
  }
}
