/*
 * HomeControllerTest.java 15 mai 2006
 * 
 * Copyright (c) 2006 Emmanuel PUYBARET / eTeks <info@eteks.com>. All Rights
 * Reserved.
 * 
 * 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.junit;

import java.awt.Component;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.swing.Action;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JRootPane;
import javax.swing.JTable;

import junit.framework.TestCase;

import com.eteks.sweethome3d.io.DefaultUserPreferences;
import com.eteks.sweethome3d.io.HomeFileRecorder;
import com.eteks.sweethome3d.model.CatalogPieceOfFurniture;
import com.eteks.sweethome3d.model.CollectionEvent;
import com.eteks.sweethome3d.model.CollectionListener;
import com.eteks.sweethome3d.model.Content;
import com.eteks.sweethome3d.model.FurnitureCategory;
import com.eteks.sweethome3d.model.Home;
import com.eteks.sweethome3d.model.HomeApplication;
import com.eteks.sweethome3d.model.HomeFurnitureGroup;
import com.eteks.sweethome3d.model.HomePieceOfFurniture;
import com.eteks.sweethome3d.model.HomeRecorder;
import com.eteks.sweethome3d.model.LengthUnit;
import com.eteks.sweethome3d.model.Selectable;
import com.eteks.sweethome3d.model.UserPreferences;
import com.eteks.sweethome3d.model.Wall;
import com.eteks.sweethome3d.swing.FurnitureCatalogTree;
import com.eteks.sweethome3d.swing.FurnitureTable;
import com.eteks.sweethome3d.swing.HomePane;
import com.eteks.sweethome3d.swing.SwingViewFactory;
import com.eteks.sweethome3d.tools.URLContent;
import com.eteks.sweethome3d.viewcontroller.FurnitureCatalogController;
import com.eteks.sweethome3d.viewcontroller.FurnitureController;
import com.eteks.sweethome3d.viewcontroller.HomeController;
import com.eteks.sweethome3d.viewcontroller.HomeView;
import com.eteks.sweethome3d.viewcontroller.PlanController;
import com.eteks.sweethome3d.viewcontroller.ViewFactory;

/**
 * Tests home controller.
 * @author Emmanuel Puybaret
 */
public class HomeControllerTest extends TestCase {
  private ViewFactory          viewFactory;
  private UserPreferences      preferences;
  private Home                 home;
  private HomeController       homeController;
  private FurnitureCatalogTree catalogTree;
  private FurnitureController  furnitureController;
  private FurnitureTable       furnitureTable;

  @Override
  protected void setUp() {
    this.viewFactory = new SwingViewFactory();
    this.preferences = new DefaultUserPreferences();
    this.preferences.setFurnitureCatalogViewedInTree(true);
    this.home = new Home();
    this.homeController = 
        new HomeController(this.home, this.preferences, viewFactory);
    FurnitureCatalogController catalogController = 
        homeController.getFurnitureCatalogController();
    this.catalogTree = (FurnitureCatalogTree)catalogController.getView();
    this.furnitureController = 
        homeController.getFurnitureController();
    this.furnitureTable = 
        (FurnitureTable)furnitureController.getView();
  }

  /**
   * Tests add and delete furniture in home.
   */
  public void testHomeFurniture() {
    // 2. Select the two first pieces of furniture in catalog and add them to the table
    catalogTree.expandRow(0); 
    catalogTree.addSelectionInterval(1, 2);
    homeController.addHomeFurniture();

    // Check the table contains two pieces
    assertEquals("Table doesn't contain 2 pieces", 
        2, furnitureTable.getRowCount());
    //  Check the two pieces in table are selected
    assertEquals("Table doesn't display 2 selected pieces", 
        2, furnitureTable.getSelectedRowCount());

    // 3. Select the first piece in table, delete it
    furnitureTable.setRowSelectionInterval(0, 0);
    homeController.getFurnitureController().deleteSelection();
    // Check the table contains only one piece
    assertEquals("Table doesn't contain 1 piece", 
        1, furnitureTable.getRowCount());
    // Check the table doesn't display any selection
    assertEquals("Table selection isn't empty", 
        0, furnitureTable.getSelectedRowCount());
  }

  /**
   * Tests undo add and delete furniture in home.
   */
  public void testHomeFurnitureUndoableActions() {
    // Check all actions are disabled
    assertActionsEnabled(false, false, false, false);
    
    // 2. Select the two first pieces of furniture in catalog
    catalogTree.expandRow(0); 
    catalogTree.addSelectionInterval(1, 2);
    // Check only Add action is enabled
    assertActionsEnabled(true, false, false, false);
    
    // 3. Add the selected furniture to the table
    runAction(HomePane.ActionType.ADD_HOME_FURNITURE);
    List<HomePieceOfFurniture> furniture = home.getFurniture();
    //  Check Add, Delete and Undo actions are enabled
    assertActionsEnabled(true, true, true, false);
    
    // 4. Select the first piece in table and delete it
    furnitureTable.setRowSelectionInterval(0, 0);
    runAction(HomePane.ActionType.DELETE_HOME_FURNITURE);
    //  Check Add and Undo actions are enabled
    assertActionsEnabled(true, false, true, false);
    
    // 5. Undo last operation
    runAction(HomePane.ActionType.UNDO);
    // Check home contains the deleted piece
    HomePieceOfFurniture firstPiece = furniture.get(0);
    assertEquals("Deleted piece isn't undeleted", 
        firstPiece, home.getFurniture().get(0));
    assertEquals("Deleted piece isn't selected", 
        firstPiece, home.getSelectedItems().get(0));
    //  Check all actions are enabled
    assertActionsEnabled(true, true, true, true);

    // 6. Undo first operation
    runAction(HomePane.ActionType.UNDO);
    // Check home is empty
    assertTrue("Home furniture isn't empty", 
        home.getFurniture().isEmpty());
    //  Check Add and Redo actions are enabled
    assertActionsEnabled(true, false, false, true);
    
    // 7. Redo first operation
    runAction(HomePane.ActionType.REDO);
    // Check home contains the two previously added pieces
    assertEquals("Home doesn't contain the two previously added pieces",
        furniture, home.getFurniture());
    // Check they are selected
    assertEquals("Added pieces are selected",
        furniture, home.getSelectedItems());
    //  Check all actions are enabled
    assertActionsEnabled(true, true, true, true);

    // 8. Redo second operation
    runAction(HomePane.ActionType.REDO);
    // Check home contains only the second piece
    assertEquals("Home doesn't contain the second piece",
        furniture.get(1), home.getFurniture().get(0));
    // Check selection is empty
    assertTrue("Selection isn't empty",
        home.getSelectedItems().isEmpty());
    //  Check Add and Undo actions are enabled
    assertActionsEnabled(true, false, true, false);
  }
  
  /**
   * Tests zoom and alignment tools. 
   */
  public void testZoomAndAligment() {
    // Add the first two pieces of catalog first category to home
    FurnitureCategory firstCategory = this.preferences.getFurnitureCatalog().getCategories().get(0);
    HomePieceOfFurniture firstPiece = new HomePieceOfFurniture(firstCategory.getFurniture().get(0));
    this.home.addPieceOfFurniture(firstPiece);
    HomePieceOfFurniture secondPiece = new HomePieceOfFurniture(firstCategory.getFurniture().get(1));
    secondPiece.setAngle(15);
    this.home.addPieceOfFurniture(secondPiece);
    // Add a wall to home
    this.home.addWall(new Wall(0, 0, 100, 0, 7, this.home.getWallHeight()));
    
    PlanController planController = this.homeController.getPlanController();
    float scale = planController.getScale();
    
    // 1. Zoom in 
    runAction(HomePane.ActionType.ZOOM_IN);    
    // Check scale changed
    assertEquals("Scale is incorrect", scale * 1.5f, planController.getScale()); 
    
    // 2. Zoom out 
    runAction(HomePane.ActionType.ZOOM_OUT);    
    // Check scale is back to its previous value
    assertEquals("Scale is incorrect", scale, planController.getScale());
    
    // 3. Select all while table has focus
    this.homeController.focusedViewChanged(this.furnitureTable);
    runAction(HomePane.ActionType.SELECT_ALL);
    // Check selection contains the two pieces
    assertEquals("Selection doesn't contain home furniture", 
        this.home.getFurniture(), this.home.getSelectedItems());

    // 4. Select all while plan has focus
    this.homeController.focusedViewChanged(planController.getView());
    runAction(HomePane.ActionType.SELECT_ALL);
    // Check selection contains the two pieces, the wall and the compass
    assertEquals("Selection doesn't contain home objects", 
        4, this.home.getSelectedItems().size());
    
    // 5. Select the first two pieces 
    this.home.setSelectedItems(Arrays.asList(new Selectable [] {firstPiece, secondPiece}));
    float secondPieceX = secondPiece.getX();
    float secondPieceY = secondPiece.getY();
    // Align on bottom
    runAction(HomePane.ActionType.ALIGN_FURNITURE_ON_BOTTOM);
    // Check bottom of second piece equals bottom of first piece
    TestUtilities.assertEqualsWithinEpsilon("Second piece isn't aligned on bottom of first piece",
        getMaxY(firstPiece), getMaxY(secondPiece), 10E-4f);

    // 6. Align on top
    runAction(HomePane.ActionType.ALIGN_FURNITURE_ON_TOP);
    // Check bottom of second piece equals bottom of first piece
    TestUtilities.assertEqualsWithinEpsilon("Second piece isn't aligned on top of first piece",
        getMinY(firstPiece), getMinY(secondPiece), 10E-4f);
    
    // 7. Align on left
    runAction(HomePane.ActionType.ALIGN_FURNITURE_ON_LEFT);
    // Check bottom of second piece equals bottom of first piece
    TestUtilities.assertEqualsWithinEpsilon("Second piece isn't aligned on left of first piece",
        getMinX(firstPiece), getMinX(secondPiece), 10E-4f);
    
    // 8. Align on right
    runAction(HomePane.ActionType.ALIGN_FURNITURE_ON_RIGHT);
    // Check bottom of second piece equals bottom of first piece
    TestUtilities.assertEqualsWithinEpsilon("Second piece isn't aligned on right of first piece",
        getMaxX(firstPiece), getMaxX(secondPiece), 10E-4f);
    float alignedPieceX = secondPiece.getX();
    float alignedPieceY = secondPiece.getY();

    // 9. Undo alignments
    runAction(HomePane.ActionType.UNDO);
    runAction(HomePane.ActionType.UNDO);
    runAction(HomePane.ActionType.UNDO);
    runAction(HomePane.ActionType.UNDO);
    // Check second piece is back to its place
    TestUtilities.assertEqualsWithinEpsilon("Second piece abcissa is incorrect",
        secondPieceX, secondPiece.getX(), 10E-4f);
    TestUtilities.assertEqualsWithinEpsilon("Second piece ordinate is incorrect",
        secondPieceY, secondPiece.getY(), 10E-4f);

    // 10. Redo alignments
    runAction(HomePane.ActionType.REDO);
    runAction(HomePane.ActionType.REDO);
    runAction(HomePane.ActionType.REDO);
    runAction(HomePane.ActionType.REDO);
    // Check second piece is back to its place
    TestUtilities.assertEqualsWithinEpsilon("Second piece abcissa is incorrect",
        alignedPieceX, secondPiece.getX(), 10E-4f);
    TestUtilities.assertEqualsWithinEpsilon("Second piece ordinate is incorrect",
        alignedPieceY, secondPiece.getY(), 10E-4f);
  }
  
  /**
   * Tests furniture visible properties changes.
   */
  public void testFurnitureVisibleProperties() {
    // 1. Add the first piece of catalog first category to home
    FurnitureCategory firstCategory = this.preferences.getFurnitureCatalog().getCategories().get(0);
    HomePieceOfFurniture piece = new HomePieceOfFurniture(firstCategory.getFurniture().get(0));
    this.home.addPieceOfFurniture(piece);
    // Use centimeter as unit
    this.preferences.setUnit(LengthUnit.CENTIMETER);
    // Check displayed values in table
    assertFurnitureFirstRowEquals(this.furnitureTable, piece.getName(),
        piece.getWidth(), piece.getDepth(), piece.getHeight(), piece.isVisible());
    
    // 2. Make name property invisible
    runAction(HomePane.ActionType.DISPLAY_HOME_FURNITURE_NAME);
    // Check displayed values in table doesn't contain piece name
    assertFurnitureFirstRowEquals(this.furnitureTable, 
        piece.getWidth(), piece.getDepth(), piece.getHeight(), piece.isVisible());

    // 3. Make y property visible
    runAction(HomePane.ActionType.DISPLAY_HOME_FURNITURE_Y);
    // Check displayed values in table contains piece ordinate after piece depth
    assertFurnitureFirstRowEquals(this.furnitureTable, 
        piece.getWidth(), piece.getDepth(), piece.getHeight(), piece.getY(), piece.isVisible());

    // 4. Make name property visible again
    runAction(HomePane.ActionType.DISPLAY_HOME_FURNITURE_NAME);
    // Check displayed values in table contains piece name in first position
    assertFurnitureFirstRowEquals(this.furnitureTable, piece.getName(),
        piece.getWidth(), piece.getDepth(), piece.getHeight(), piece.getY(), piece.isVisible());
    
    // 5. Change visible properties order and list
    this.furnitureController.setFurnitureVisibleProperties(
        Arrays.asList(new HomePieceOfFurniture.SortableProperty [] {
            HomePieceOfFurniture.SortableProperty.MOVABLE,
            HomePieceOfFurniture.SortableProperty.NAME}));
    // Check displayed values in table contains piece name and visible properties
    assertFurnitureFirstRowEquals(this.furnitureTable, piece.isMovable(), piece.getName());

    // 6. Make visible property visible
    runAction(HomePane.ActionType.DISPLAY_HOME_FURNITURE_VISIBLE);
    // Check displayed values in table contains piece visible property after movable property
    assertFurnitureFirstRowEquals(this.furnitureTable, 
        piece.isMovable(), piece.isVisible(), piece.getName());
  }
  
  /**
   * Tests furniture group / ungroup.
   */
  public void testFurnitureGroup() {
    assertGroupActionsEnabled(false, false);
    
    // 1. Add the first two pieces of catalog first category to home
    FurnitureCategory firstCategory = this.preferences.getFurnitureCatalog().getCategories().get(0);
    List<CatalogPieceOfFurniture> catalogPieces = Arrays.asList(new CatalogPieceOfFurniture [] {
        firstCategory.getFurniture().get(0), firstCategory.getFurniture().get(1)});
    this.homeController.getFurnitureCatalogController().setSelectedFurniture(catalogPieces);
    runAction(HomePane.ActionType.ADD_HOME_FURNITURE);
    List<HomePieceOfFurniture> pieces = this.home.getFurniture();
    // Check home contains two selected pieces
    assertEquals("Home doesn't contain 2 pieces", 2, pieces.size());
    assertEquals("Home doesn't contain 2 selected items", 2, this.home.getSelectedItems().size());
    assertGroupActionsEnabled(true, false);
    HomePieceOfFurniture piece1 = pieces.get(0);
    HomePieceOfFurniture piece2 = pieces.get(1);
    piece2.move(100, 100);
    piece2.setElevation(100);
    
    // 2. Group selected furniture
    runAction(HomePane.ActionType.GROUP_FURNITURE);
    // Check home contains one selected piece that replaced added pieces
    pieces = this.home.getFurniture();
    assertEquals("Home doesn't contain 1 piece", 1, pieces.size());
    assertEquals("Home doesn't contain 1 selected item", 1, this.home.getSelectedItems().size());
    assertFalse("Home still contains first added piece", pieces.contains(piece1));
    assertFalse("Home still contains scond added piece", pieces.contains(piece2));
    assertGroupActionsEnabled(false, true);
    HomeFurnitureGroup group = (HomeFurnitureGroup)pieces.get(0);
    // Compare surrounding rectangles
    Rectangle2D piecesRectangle = getSurroundingRectangle(piece1);
    piecesRectangle.add(getSurroundingRectangle(piece2));
    Rectangle2D groupRectangle = getSurroundingRectangle(group);
    assertEquals("Surrounding rectangle is incorrect", piecesRectangle, groupRectangle);
    // Compare elevation and height
    assertEquals("Wrong elevation", Math.min(piece1.getElevation(), piece2.getElevation()), group.getElevation());
    assertEquals("Wrong height", 
        Math.max(piece1.getElevation() + piece1.getHeight(), piece2.getElevation() + piece2.getHeight()) 
        - Math.min(piece1.getElevation(), piece2.getElevation()), group.getHeight());
    
    // 3. Rotate group
    float angle = (float)(Math.PI / 2);
    group.setAngle(angle);
    // Check pieces rotation
    assertEquals("Piece angle is wrong", angle, piece1.getAngle());
    assertEquals("Piece angle is wrong", angle, piece2.getAngle());
    assertEquals("Group angle is wrong", angle, group.getAngle());
    // Check surrounding rectangles
    Rectangle2D rotatedGroupRectangle = new GeneralPath(groupRectangle).createTransformedShape(
        AffineTransform.getRotateInstance(angle, groupRectangle.getCenterX(), groupRectangle.getCenterY())).getBounds2D();
    groupRectangle = getSurroundingRectangle(group);
    assertEquals("Surrounding rectangle is incorrect", rotatedGroupRectangle, groupRectangle);
    piecesRectangle = getSurroundingRectangle(piece1);
    piecesRectangle.add(getSurroundingRectangle(piece2));
    assertEquals("Surrounding rectangle is incorrect", piecesRectangle.getWidth(), groupRectangle.getWidth());
    assertEquals("Surrounding rectangle is incorrect", piecesRectangle.getHeight(), groupRectangle.getHeight());
    
    // 4. Undo / Redo
    assertActionsEnabled(true, true, true, false);
    runAction(HomePane.ActionType.UNDO);
    pieces = this.home.getFurniture();
    assertEquals("Home doesn't contain 2 pieces", 2, pieces.size());
    assertEquals("Home doesn't contain 2 selected items", 2, this.home.getSelectedItems().size());
    assertFalse("Home contains group", pieces.contains(group));
    assertGroupActionsEnabled(true, false);

    assertActionsEnabled(true, true, true, true);
    runAction(HomePane.ActionType.REDO);
    pieces = this.home.getFurniture();
    assertEquals("Home doesn't contain 1 piece", 1, pieces.size());
    assertEquals("Home doesn't contain 1 selected item", 1, this.home.getSelectedItems().size());
    assertTrue("Home doesn't contain group", pieces.contains(group));
    assertGroupActionsEnabled(false, true);
    
    // 5. Add one more piece to home
    catalogPieces = Arrays.asList(new CatalogPieceOfFurniture [] {firstCategory.getFurniture().get(0)});
    this.homeController.getFurnitureCatalogController().setSelectedFurniture(catalogPieces);
    runAction(HomePane.ActionType.ADD_HOME_FURNITURE);
    pieces = this.home.getFurniture();
    // Check home contains two pieces with one selected
    assertEquals("Home doesn't contain 2 pieces", 2, pieces.size());
    assertEquals("Home doesn't contain 1 selected item", 1, this.home.getSelectedItems().size());
    assertGroupActionsEnabled(false, false);
    
    // 6. Group it with the other group
    HomePieceOfFurniture piece3 = pieces.get(1);
    this.home.setSelectedItems(Arrays.asList(new Selectable [] {group, piece3}));
    assertEquals("Home doesn't contain 2 selected items", 2, this.home.getSelectedItems().size());
    assertGroupActionsEnabled(true, true);
    runAction(HomePane.ActionType.GROUP_FURNITURE);
    pieces = this.home.getFurniture();
    assertEquals("Home doesn't contain 1 piece", 1, pieces.size());
    assertEquals("Home doesn't contain 1 selected item", 1, this.home.getSelectedItems().size());
    HomeFurnitureGroup group2 = (HomeFurnitureGroup)pieces.get(0);
    assertFalse("Home doesn't contain a different group", group == group2);
    assertGroupActionsEnabled(false, true);
    
    // 7. Ungroup furniture
    runAction(HomePane.ActionType.UNGROUP_FURNITURE);
    assertGroupActionsEnabled(true, true);
    pieces = this.home.getFurniture();
    assertEquals("Home doesn't contain 2 piece", 2, pieces.size());
    assertEquals("Home doesn't contain 2 selected item", 2, this.home.getSelectedItems().size());
    assertFalse("Home contains second group", pieces.contains(group2));
    assertTrue("Home doesn't contain group", pieces.contains(group));
    runAction(HomePane.ActionType.UNGROUP_FURNITURE);
    assertGroupActionsEnabled(true, false);
    pieces = this.home.getFurniture();
    assertEquals("Home doesn't contain 3 piece", 3, pieces.size());
    assertEquals("Home doesn't contain 2 selected item", 2, this.home.getSelectedItems().size());
    assertFalse("Home contains group", pieces.contains(group));
  }
    
  /**
   * Test damaged home file management.
   */
  public void testDamagedFile() throws URISyntaxException, InterruptedException, BrokenBarrierException, TimeoutException {
    final String testFile = new File(
        HomeControllerTest.class.getResource("resources/damagedHome.sh3d").toURI()).getAbsolutePath();
    final HomeRecorder recorder = new HomeFileRecorder();
    final HomeApplication application = new HomeApplication() {
        @Override
        public UserPreferences getUserPreferences() {
          return preferences;
        }
        
        @Override
        public HomeRecorder getHomeRecorder() {
          return recorder;
        }
      };    
      
    class OpenDamagedHomeController extends HomeController {
      public OpenDamagedHomeController(final HomeView.OpenDamagedHomeAnswer answer) {
        super(new Home(), application, new SwingViewFactory() {
          @Override
          public HomeView createHomeView(Home home, UserPreferences preferences, HomeController homeController) {
            return new HomePane(home, preferences, homeController) {
                @Override
                public String showOpenDialog() {
                  // Return the damaged file that is tested
                  return testFile;
                }
                
                @Override
                public OpenDamagedHomeAnswer confirmOpenDamagedHome(String homeName, Home damagedHome,
                                                                  List<Content> invalidContent) {
                  assertEquals("Missing damaged content", 5, invalidContent.size());
                  assertEquals("Incorrect furniture count", 5, damagedHome.getFurniture().size());
                  return answer;
                }
              };
          }
        });
      }
    }
       
    final CyclicBarrier openLatch = new CyclicBarrier(2);
    application.addHomesListener(new CollectionListener<Home>() {
        public void collectionChanged(CollectionEvent<Home> ev)  {
          if (ev.getType() == CollectionEvent.Type.ADD) {
            try {
              openLatch.await();
            } catch (Exception ex) {
              fail();
            }
          }
        }
      });
    
    // 1. Try to open home without damaged items
    new OpenDamagedHomeController(HomeView.OpenDamagedHomeAnswer.REMOVE_DAMAGED_ITEMS).open();
    openLatch.await(5, TimeUnit.SECONDS);
    assertFalse("Damaged file wasn't opened", application.getHomes().isEmpty());
    Home home = application.getHomes().get(0);
    assertTrue("Home is not tagged as repaired", home.isRepaired());
    assertEquals("Incorrect furniture count", 4, home.getFurniture().size());
    application.deleteHome(home);
    
    // 2. Try to open home with damaged items replaced by repaired items
    openLatch.reset();
    new OpenDamagedHomeController(HomeView.OpenDamagedHomeAnswer.REPLACE_DAMAGED_ITEMS).open();
    openLatch.await(5, TimeUnit.SECONDS);
    assertFalse("Damaged file wasn't opened", application.getHomes().isEmpty());
    home = application.getHomes().get(0);
    assertTrue("Home is not tagged as repaired", home.isRepaired());
    assertEquals("Incorrect furniture count", 5, home.getFurniture().size());
    assertTrue(((URLContent)home.getFurniture().get(4).getModel()).getURL().toString().endsWith("repairedModel.obj"));
  }
  
  /**
   * Runs <code>actionPerformed</code> method matching <code>actionType</code> 
   * in <code>HomePane</code>. 
   */
  private void runAction(HomePane.ActionType actionType) {
    getAction(actionType).actionPerformed(null);
  }

  /**
   * Returns the action matching <code>actionType</code> in <code>HomePane</code>. 
   */
  private Action getAction(HomePane.ActionType actionType) {
    JComponent homeView = (JComponent)this.homeController.getView();
    return homeView.getActionMap().get(actionType);
  }
  
  /**
   * Asserts ADD_HOME_FURNITURE, DELETE_HOME_FURNITURE, 
   * UNDO and REDO actions in <code>HomePane</code> 
   * are enabled or disabled. 
   */
  private void assertActionsEnabled(boolean addActionEnabled, 
                                    boolean deleteActionEnabled, 
                                    boolean undoActionEnabled, 
                                    boolean redoActionEnabled) {
    assertTrue("Add action invalid state", 
        getAction(HomePane.ActionType.ADD_HOME_FURNITURE).isEnabled() == addActionEnabled);
    assertTrue("Delete action invalid state", 
        getAction(HomePane.ActionType.DELETE_HOME_FURNITURE).isEnabled() == deleteActionEnabled);
    assertTrue("Undo action invalid state", 
        getAction(HomePane.ActionType.UNDO).isEnabled() == undoActionEnabled);
    assertTrue("Redo action invalid state", 
        getAction(HomePane.ActionType.REDO).isEnabled() == redoActionEnabled);
  }
  
  /**
   * Asserts GROUP_FURNITURE, UNGROUP_FURNITURE are enabled or disabled. 
   */
  private void assertGroupActionsEnabled(boolean groupActionEnabled, 
                                         boolean ungroupActionEnabled) {
    assertTrue("Group action invalid state", 
        getAction(HomePane.ActionType.GROUP_FURNITURE).isEnabled() == groupActionEnabled);
    assertTrue("Ungroup action invalid state", 
        getAction(HomePane.ActionType.UNGROUP_FURNITURE).isEnabled() == ungroupActionEnabled);
  }
  
  /**
   * Returns the minimum abcissa of the vertices of <code>piece</code>.  
   */
  private float getMinX(HomePieceOfFurniture piece) {
    float [][] points = piece.getPoints();
    float minX = Float.POSITIVE_INFINITY;
    for (float [] point : points) {
      minX = Math.min(minX, point [0]);
    } 
    return minX;
  }

  /**
   * Returns the maximum abcissa of the vertices of <code>piece</code>.  
   */
  private float getMaxX(HomePieceOfFurniture piece) {
    float [][] points = piece.getPoints();
    float maxX = Float.NEGATIVE_INFINITY;
    for (float [] point : points) {
      maxX = Math.max(maxX, point [0]);
    } 
    return maxX;
  }

  /**
   * Returns the minimum ordinate of the vertices of <code>piece</code>.  
   */
  private float getMinY(HomePieceOfFurniture piece) {
    float [][] points = piece.getPoints();
    float minY = Float.POSITIVE_INFINITY;
    for (float [] point : points) {
      minY = Math.min(minY, point [1]);
    } 
    return minY;
  }

  /**
   * Returns the maximum ordinate of the vertices of <code>piece</code>.  
   */
  private float getMaxY(HomePieceOfFurniture piece) {
    float [][] points = piece.getPoints();
    float maxY = Float.NEGATIVE_INFINITY;
    for (float [] point : points) {
      maxY = Math.max(maxY, point [1]);
    } 
    return maxY;
  }

  /**
   * Asserts that <code>table</code> first row values are correct.
   */
  private void assertFurnitureFirstRowEquals(JTable table, Object ... values) {
    assertEquals("Wrong column count", values.length, table.getColumnCount());

    for (int column = 0, n = table.getColumnCount(); column < n; column++) {
      Component cellRendererComponent = table.getCellRenderer(0, column).
          getTableCellRendererComponent(table, table.getValueAt(0, column), false, false, 0, column);
      if (values [column] instanceof Number) {
        assertEquals("Wrong value at column " + column,  
            this.preferences.getLengthUnit().getFormat().format(values [column]), 
            ((JLabel)cellRendererComponent).getText());
      } else if (values [column] instanceof Boolean) {
        assertEquals("Wrong value at column " + column, values [column], 
            ((JCheckBox)cellRendererComponent).isSelected());
      } else {
        assertEquals("Wrong value at column " + column, values [column], 
            ((JLabel)((JComponent)cellRendererComponent).getComponent(1)).getText());
      }
    }    
  }

  /**
   * Returns the rectangle surrounding the given <code>piece</code>.
   */
  private Rectangle2D getSurroundingRectangle(HomePieceOfFurniture piece) {
    float [][] points = piece.getPoints();
    Rectangle2D rectangle = new Rectangle2D.Float(points [0][0], points [0][1], 0, 0);
    for (int i = 1; i < points.length; i++) {
      rectangle.add(points [i][0], points [i][1]);
    }
    return rectangle;
  }
  
  public static void main(String [] args) {
    ViewFactory viewFactory = new SwingViewFactory();
    UserPreferences preferences = new DefaultUserPreferences();
    Home home = new Home();
    new ControllerTest(home, preferences, viewFactory);
  }

  private static class ControllerTest extends HomeController {
    public ControllerTest(Home home, 
                          UserPreferences preferences,
                          ViewFactory viewFactory) {
      super(home, preferences, viewFactory);
      new ViewTest(this).displayView();
    }
  }

  private static class ViewTest extends JRootPane {
    public ViewTest(final HomeController controller) {
      // Display main view in this pane
      getContentPane().add((JComponent)controller.getView());
    }

    public void displayView() {
      JFrame frame = new JFrame("Home Controller Test") {
        {
          setRootPane(ViewTest.this);
        }
      };
      frame.pack();
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setVisible(true);
    } 
  }
}
