/*
 * ImportedFurnitureWizardTest.java 4 juil. 2007
 * 
 * Copyright (c) 2007 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.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JSpinner;
import javax.swing.JTextField;

import junit.extensions.abbot.ComponentTestFixture;
import abbot.finder.AWTHierarchy;
import abbot.finder.BasicFinder;
import abbot.finder.ComponentSearchException;
import abbot.finder.matchers.ClassMatcher;
import abbot.tester.JComponentTester;

import com.eteks.sweethome3d.io.FileUserPreferences;
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.HomePieceOfFurniture;
import com.eteks.sweethome3d.model.LengthUnit;
import com.eteks.sweethome3d.model.RecorderException;
import com.eteks.sweethome3d.model.Selectable;
import com.eteks.sweethome3d.model.UserPreferences;
import com.eteks.sweethome3d.swing.ColorButton;
import com.eteks.sweethome3d.swing.HomePane;
import com.eteks.sweethome3d.swing.ImportedFurnitureWizardStepsPanel;
import com.eteks.sweethome3d.swing.SwingViewFactory;
import com.eteks.sweethome3d.swing.WizardPane;
import com.eteks.sweethome3d.tools.URLContent;
import com.eteks.sweethome3d.viewcontroller.ContentManager;
import com.eteks.sweethome3d.viewcontroller.HomeController;
import com.eteks.sweethome3d.viewcontroller.ImportedFurnitureWizardController;
import com.eteks.sweethome3d.viewcontroller.View;
import com.eteks.sweethome3d.viewcontroller.ViewFactory;

/**
 * Tests imported furniture wizard.
 * @author Emmanuel Puybaret
 */
public class ImportedFurnitureWizardTest extends ComponentTestFixture {
  public void testImportFurnitureWizard() throws ComponentSearchException, InterruptedException, 
      NoSuchFieldException, IllegalAccessException, InvocationTargetException {
    String language = Locale.getDefault().getLanguage();
    final UserPreferences preferences = new FileUserPreferences();
    // Ensure we use default language and centimeter unit
    preferences.setLanguage(language);
    preferences.setUnit(LengthUnit.CENTIMETER);
    final ViewFactory viewFactory = new SwingViewFactory();
    final URL testedModelName = ImportedFurnitureWizardTest.class.getResource("resources/test.obj");
    // Create a dummy content manager
    final ContentManager contentManager = new ContentManager() {
      public Content getContent(String contentName) throws RecorderException {
        try {
          // Let's consider contentName is a URL
          return new URLContent(new URL(contentName));
        } catch (IOException ex) {
          fail();
          return null;
        }
      }

      public String getPresentationName(String contentName, ContentType contentType) {
        return "test";
      }

      public boolean isAcceptable(String contentName, ContentType contentType) {
        return true;
      }

      public String showOpenDialog(View parentView, String dialogTitle, ContentType contentType) {
        // Return tested model name URL
        return testedModelName.toString();
      }

      public String showSaveDialog(View parentView, String dialogTitle, ContentType contentType, String name) {
        return null;
      }      
    };
    Home home = new Home();
    final HomeController controller = 
        new HomeController(home, preferences, viewFactory, contentManager);
    final JComponent homeView = (JComponent)controller.getView();
    final List<CatalogPieceOfFurniture> addedCatalogFurniture = new ArrayList<CatalogPieceOfFurniture>();
    preferences.getFurnitureCatalog().addFurnitureListener(new CollectionListener<CatalogPieceOfFurniture>() {
        public void collectionChanged(CollectionEvent<CatalogPieceOfFurniture> ev) {
          addedCatalogFurniture.add(ev.getItem());
        }
      });

    // 1. Create a frame that displays a home view 
    JFrame frame = new JFrame("Imported Furniture Wizard Test");    
    frame.add(homeView);
    frame.pack();

    // Show home plan frame
    showWindow(frame);
    JComponentTester tester = new JComponentTester();
    tester.waitForIdle();    

    // 2. Transfer focus to plan view 
    ((JComponent)controller.getPlanController().getView()).requestFocusInWindow();      
    tester.waitForIdle();    

    // Check plan view has focus
    assertTrue("Plan component doesn't have the focus", 
        ((JComponent)controller.getPlanController().getView()).isFocusOwner());
    // Open wizard to import a test object (1 width x 2 depth x 3 height) in plan
    tester.invokeLater(new Runnable() { 
        public void run() {
          // Display dialog box later in Event Dispatch Thread to avoid blocking test thread
          homeView.getActionMap().get(HomePane.ActionType.IMPORT_FURNITURE).actionPerformed(null);
        }
      });
    // Wait for import furniture view to be shown
    tester.waitForFrameShowing(new AWTHierarchy(), preferences.getLocalizedString(
        ImportedFurnitureWizardController.class, "importFurnitureWizard.title"));
    // Check dialog box is displayed
    JDialog wizardDialog = (JDialog)TestUtilities.findComponent(frame, JDialog.class);
    assertTrue("Wizard view dialog not showing", wizardDialog.isShowing());

    // Retrieve ImportedFurnitureWizardStepsPanel components
    ImportedFurnitureWizardStepsPanel panel = (ImportedFurnitureWizardStepsPanel)TestUtilities.findComponent(
        frame, ImportedFurnitureWizardStepsPanel.class);
    final JButton modelChoiceOrChangeButton = (JButton)TestUtilities.getField(panel, "modelChoiceOrChangeButton");
    final JButton turnLeftButton = (JButton)TestUtilities.getField(panel, "turnLeftButton");
    final JButton turnDownButton = (JButton)TestUtilities.getField(panel, "turnDownButton");
    JCheckBox backFaceShownCheckBox = (JCheckBox)TestUtilities.getField(panel, "backFaceShownCheckBox");
    final JTextField nameTextField = (JTextField)TestUtilities.getField(panel, "nameTextField");
    final JCheckBox addToCatalogCheckBox = (JCheckBox)TestUtilities.getField(panel, "addToCatalogCheckBox");
    final JComboBox categoryComboBox = (JComboBox)TestUtilities.getField(panel, "categoryComboBox");
    final JSpinner widthSpinner = (JSpinner)TestUtilities.getField(panel, "widthSpinner");
    JSpinner heightSpinner = (JSpinner)TestUtilities.getField(panel, "heightSpinner");
    JSpinner depthSpinner = (JSpinner)TestUtilities.getField(panel, "depthSpinner");
    final JCheckBox keepProportionsCheckBox = (JCheckBox)TestUtilities.getField(panel, "keepProportionsCheckBox");
    JSpinner elevationSpinner = (JSpinner)TestUtilities.getField(panel, "elevationSpinner");
    JCheckBox movableCheckBox = (JCheckBox)TestUtilities.getField(panel, "movableCheckBox");
    JCheckBox doorOrWindowCheckBox = (JCheckBox)TestUtilities.getField(panel, "doorOrWindowCheckBox");
    ColorButton colorButton = (ColorButton)TestUtilities.getField(panel, "colorButton");
    final JButton clearColorButton = (JButton)TestUtilities.getField(panel, "clearColorButton");
    
    // Check current step is model
    tester.waitForIdle();
    assertStepShowing(panel, true, false, false, false);    
    
    // 3. Choose tested model
    String modelChoiceOrChangeButtonText = modelChoiceOrChangeButton.getText();
    tester.invokeAndWait(new Runnable() {
        public void run() {
          modelChoiceOrChangeButton.doClick();
        }
      });
    // Wait 1 s to let time to Java 3D to load the model
    Thread.sleep(1000);
    // Check choice button text changed
    assertFalse("Choice button text didn't change", 
        modelChoiceOrChangeButtonText.equals(modelChoiceOrChangeButton.getText()));
    // Click on next button
    WizardPane view = (WizardPane)TestUtilities.findComponent(frame, WizardPane.class);
    // Retrieve wizard view next button
    final JButton nextFinishOptionButton = (JButton)TestUtilities.getField(view, "nextFinishOptionButton"); 
    assertTrue("Next button isn't enabled", nextFinishOptionButton.isEnabled());    
    tester.invokeAndWait(new Runnable() {
        public void run() {
          nextFinishOptionButton.doClick();
        }
      });
    // Check current step is rotation
    assertStepShowing(panel, false, true, false, false);    
    // Check back face shown check box isn't selected by default
    assertFalse("Back face shown check box is selected", backFaceShownCheckBox.isSelected());
    
    // 4. Click on left button
    float width = (Float)widthSpinner.getValue();
    float depth = (Float)depthSpinner.getValue();
    float height = (Float)heightSpinner.getValue();
    tester.invokeAndWait(new Runnable() {
        public void run() {
          turnLeftButton.doClick();
        }
      });
    // Check depth and width values were swapped 
    float newWidth = (Float)widthSpinner.getValue();
    float newDepth = (Float)depthSpinner.getValue();
    float newHeight = (Float)heightSpinner.getValue();
    TestUtilities.assertEqualsWithinEpsilon("Incorrect width", depth, newWidth, 1E-3f);
    TestUtilities.assertEqualsWithinEpsilon("Incorrect depth", width, newDepth, 1E-3f);
    TestUtilities.assertEqualsWithinEpsilon("Incorrect height", height, newHeight, 1E-3f);
    // Click on down button
    width = newWidth;
    depth = newDepth;
    height = newHeight;
    tester.invokeAndWait(new Runnable() {
        public void run() {
          turnDownButton.doClick();
        }
      });
    // Check height and depth values were swapped 
    newWidth = (Float)widthSpinner.getValue();
    newDepth = (Float)depthSpinner.getValue();
    newHeight = (Float)heightSpinner.getValue();
    TestUtilities.assertEqualsWithinEpsilon("Incorrect width", width, newWidth, 1E-3f);
    TestUtilities.assertEqualsWithinEpsilon("Incorrect depth", height, newDepth, 1E-3f);
    TestUtilities.assertEqualsWithinEpsilon("Incorrect height", depth, newHeight, 1E-3f);
    
    // 5. Click on next button
    assertTrue("Next button isn't enabled", nextFinishOptionButton.isEnabled());
    tester.invokeAndWait(new Runnable() {
        public void run() {
          nextFinishOptionButton.doClick();
        }
      });
    // Check current step is attributes
    assertStepShowing(panel, false, false, true, false);    
        
    // 6. Check default furniture name is the presentation name proposed by content manager
    assertEquals("Wrong default name",  
        contentManager.getPresentationName(testedModelName.toString(), ContentManager.ContentType.MODEL),
        nameTextField.getText());
    // Check Add to catalog check box isn't selected and category combo box 
    // is disabled when furniture is imported in home
    assertFalse("Add to catalog check box is selected", addToCatalogCheckBox.isSelected());
    assertFalse("Category combo box isn't disabled", categoryComboBox.isEnabled());
    // Check default category is first category  
    final FurnitureCategory firstCategory = preferences.getFurnitureCatalog().getCategories().get(0);
    assertEquals("Wrong default category", firstCategory, categoryComboBox.getSelectedItem());
    // Rename furniture with an empty name
    tester.invokeAndWait(new Runnable() {
        public void run() {
          nameTextField.setText("");
        }
      });
    // Check next button is disabled 
    assertFalse("Next button isn't disabled", nextFinishOptionButton.isEnabled());
    // Select Add to catalog check box
    tester.invokeAndWait(new Runnable() {
        public void run() {
          addToCatalogCheckBox.setSelected(true);
        }
      });
    // Check next button is disabled because imported furniture has a wrong name
    assertFalse("Next button isn't disabled", nextFinishOptionButton.isEnabled());
    // Rename furniture and its category 
    final String pieceTestName = "#@" + System.currentTimeMillis() + "@#";
    final String categoryTestName = "sdfghjkl";
    tester.invokeAndWait(new Runnable() {
        public void run() {
          nameTextField.setText(pieceTestName);
          categoryComboBox.getEditor().selectAll();
        }
      });
    tester.actionKeyString(categoryComboBox.getEditor().getEditorComponent(), categoryTestName);    
    // Check next button is enabled again
    assertTrue("Next button isn't enabled", nextFinishOptionButton.isEnabled());
    
    // 7. Check keep proportions check box is selected by default
    assertTrue("Keep proportions check box isn't selected", keepProportionsCheckBox.isSelected());
    // Change width with a value 10 times greater
    width = newWidth;
    depth = newDepth;
    height = newHeight;
    final float enteredWidth = newWidth * 10;
    tester.invokeAndWait(new Runnable() {
        public void run() {
          widthSpinner.setValue(enteredWidth);
        }
      });
    // Check height and depth values are 10 times greater 
    newWidth = (Float)widthSpinner.getValue();
    newDepth = (Float)depthSpinner.getValue();
    newHeight = (Float)heightSpinner.getValue();
    TestUtilities.assertEqualsWithinEpsilon("Incorrect width", 10 * width, newWidth, 1E-3f);
    TestUtilities.assertEqualsWithinEpsilon("Incorrect depth", 10 * depth, newDepth, 1E-3f);
    TestUtilities.assertEqualsWithinEpsilon("Incorrect height", 10 * height, newHeight, 1E-3f);
    // Deselect keep proportions check box 
    tester.invokeAndWait(new Runnable() {
        public void run() {
          keepProportionsCheckBox.setSelected(false);
        }
      });       
    // Change width with a value 2 times greater
    width = newWidth;
    depth = newDepth;
    height = newHeight;
    final float twiceValue = newWidth * 2;
    tester.invokeAndWait(new Runnable() {
        public void run() {
          widthSpinner.setValue(twiceValue);
        }
      });
    // Check height and depth values didn't change
    newWidth = (Float)widthSpinner.getValue();
    newDepth = (Float)depthSpinner.getValue();
    newHeight = (Float)heightSpinner.getValue();
    TestUtilities.assertEqualsWithinEpsilon("Incorrect width", 2 * width, newWidth, 1E-3f);
    TestUtilities.assertEqualsWithinEpsilon("Incorrect depth", depth, newDepth, 1E-3f);
    TestUtilities.assertEqualsWithinEpsilon("Incorrect height", height, newHeight, 1E-3f);

    // 8. Change elevation, movable, door or window, color default values
    assertEquals("Wrong default elevation", 0f, (Float)elevationSpinner.getValue());
    elevationSpinner.setValue(10);
    assertTrue("Movable check box isn't selected", movableCheckBox.isSelected());
    movableCheckBox.setSelected(false);
    assertFalse("Door or window check box is selected", doorOrWindowCheckBox.isSelected());
    doorOrWindowCheckBox.setSelected(true);
    assertEquals("Wrong default color", null, colorButton.getColor());
    assertFalse("Clear color button isn't disabled", clearColorButton.isEnabled());
    // Change color
    colorButton.setColor(0x000000);
    // Check clear color button is enabled
    assertTrue("Clear color button isn't enabled", clearColorButton.isEnabled());
    // Click on clear color button
    tester.invokeAndWait(new Runnable() {
        public void run() {
          clearColorButton.doClick();
        }
      });
    // Check color is null and clear color button is disabled
    assertEquals("Wrong color", null, colorButton.getColor());
    assertFalse("Clear color button isn't disabled", clearColorButton.isEnabled());
 
    // 9. Click on next button
    tester.invokeAndWait(new Runnable() {
        public void run() {
          nextFinishOptionButton.doClick();
        }
      });
    // Check current step is icon
    assertStepShowing(panel, false, false, false, true);    
    tester.invokeAndWait(new Runnable() {
        public void run() {
          // Click on Finish to hide dialog box in Event Dispatch Thread
          nextFinishOptionButton.doClick(); 
        }
      });
    
    // 10. Check the matching new catalog piece of furniture was created
    List<CatalogPieceOfFurniture> selectedCatalogFurniture = 
        controller.getFurnitureCatalogController().getSelectedFurniture();
    assertEquals("Wrong selected furniture count in catalog", 0, selectedCatalogFurniture.size());
    assertEquals("Incorrect count of created catalog piece", 1, addedCatalogFurniture.size());    
    CatalogPieceOfFurniture catalogPiece = addedCatalogFurniture.get(0);
    assertEquals("Wrong catalog piece name", pieceTestName, catalogPiece.getName());
    assertEquals("Wrong catalog piece category name", categoryTestName, catalogPiece.getCategory().getName());
    assertTrue("Catalog doesn't contain new piece", 
        preferences.getFurnitureCatalog().getCategories().contains(catalogPiece.getCategory()));
    TestUtilities.assertEqualsWithinEpsilon("Incorrect width", newWidth, catalogPiece.getWidth(), 1E-3f);
    TestUtilities.assertEqualsWithinEpsilon("Incorrect depth", newDepth, catalogPiece.getDepth(), 1E-3f);
    TestUtilities.assertEqualsWithinEpsilon("Incorrect height", newHeight, catalogPiece.getHeight(), 1E-3f);
    TestUtilities.assertEqualsWithinEpsilon("Incorrect elevation", 10, catalogPiece.getElevation(), 1E-3f);
    assertFalse("Catalog piece is movable", catalogPiece.isMovable());
    assertTrue("Catalog piece isn't a door or window", catalogPiece.isDoorOrWindow());
    assertEquals("Wrong catalog piece color", null, catalogPiece.getColor());
    assertTrue("Catalog piece isn't modifiable", catalogPiece.isModifiable());
    
    // Check a new home piece of furniture was created and it's the selected piece in home
    List<Selectable> homeSelectedItems = home.getSelectedItems();
    assertEquals("Wrong selected furniture count in home", 1, homeSelectedItems.size());
    HomePieceOfFurniture homePiece = (HomePieceOfFurniture)homeSelectedItems.get(0);
    assertEquals("Wrong home piece name", pieceTestName, homePiece.getName());
    
    // 11. Transfer focus to tree 
    JComponent catalogView = (JComponent)controller.getFurnitureCatalogController().getView();
    tester.focus(catalogView);        
    // Check plan view has focus
    assertTrue("Catalog tree doesn't have the focus", catalogView.isFocusOwner());
    // Select the piece added to catalog
    controller.getFurnitureCatalogController().setSelectedFurniture(addedCatalogFurniture);
    // Delete new catalog piece of furniture
    final Action deleteAction = homeView.getActionMap().get(HomePane.ActionType.DELETE);
    assertTrue("Delete action isn't enable", deleteAction.isEnabled());
    tester.invokeLater(new Runnable() { 
        public void run() {
          deleteAction.actionPerformed(null);
        }
      });
    // Wait for confirm dialog to be shown
    tester.waitForFrameShowing(new AWTHierarchy(), preferences.getLocalizedString(
        HomePane.class, "confirmDeleteCatalogSelection.title"));
    // Find displayed dialog box
    JDialog confirmDeleteCatalogSelectionDialog = (JDialog)new BasicFinder().find(frame, 
        new ClassMatcher (JDialog.class, true));
    // Click on Ok in dialog box
    final JOptionPane optionPane = (JOptionPane)TestUtilities.findComponent(
        confirmDeleteCatalogSelectionDialog, JOptionPane.class);
    tester.invokeAndWait(new Runnable() {
        public void run() {
          // Select delete option to hide dialog box in Event Dispatch Thread
          optionPane.setValue(preferences.getLocalizedString(
              HomePane.class, "confirmDeleteCatalogSelection.delete")); 
        }
      });
    // Check selection is empty
    selectedCatalogFurniture = controller.getFurnitureCatalogController().getSelectedFurniture();
    assertTrue("Catalog selected furniture isn't empty", selectedCatalogFurniture.isEmpty());
    // Check catalog doesn't contain the new piece
    assertFalse("Piece is still in catalog", 
        preferences.getFurnitureCatalog().getCategories().contains(catalogPiece.getCategory()));
    // Check home piece of furniture is still in home and selected
    assertTrue("Home piece isn't in home", home.getFurniture().contains(homePiece));
    assertTrue("Home piece isn't selected", home.getSelectedItems().contains(homePiece));
    
    // 12. Undo furniture creation in home
    tester.invokeAndWait(new Runnable() {
        public void run() {
          homeView.getActionMap().get(HomePane.ActionType.UNDO).actionPerformed(null);
        }
      });
    // Check home is empty
    assertTrue("Home isn't empty", home.getFurniture().isEmpty());
    // Redo
    tester.invokeAndWait(new Runnable() {
        public void run() {
          homeView.getActionMap().get(HomePane.ActionType.REDO).actionPerformed(null);
        }
      });
    // Check home piece of furniture is in home and selected
    assertTrue("Home piece isn't in home", home.getFurniture().contains(homePiece));
    assertTrue("Home piece isn't selecteed", home.getSelectedItems().contains(homePiece));
  }

  /**
   * Asserts if each <code>panel</code> step preview component is showing or not. 
   */
  private void assertStepShowing(ImportedFurnitureWizardStepsPanel panel,
                                 boolean modelStepShwing,
                                 boolean rotationStepShowing,
                                 boolean attributesStepShowing,
                                 boolean iconStepShowing) throws NoSuchFieldException, IllegalAccessException {
    
    assertEquals("Wrong model step visibility", modelStepShwing,
        ((JComponent)TestUtilities.getField(panel, "modelPreviewComponent")).isShowing());
    assertEquals("Wrong rotation step visibility", rotationStepShowing,
        ((JComponent)TestUtilities.getField(panel, "rotationPreviewComponent")).isShowing());
    assertEquals("Wrong attributes step visibility", attributesStepShowing,
        ((JComponent)TestUtilities.getField(panel, "attributesPreviewComponent")).isShowing());
    assertEquals("Wrong icon step visibility", iconStepShowing,
        ((JComponent)TestUtilities.getField(panel, "iconPreviewComponent")).isShowing());
  }
}
