/*
 * PhotoCreationTest.java 18 sept. 2010
 *
 * Copyright (c) 2024 Space Mushrooms <info@sweethome3d.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.junit;

import java.awt.Component;
import java.awt.Dialog;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.JSlider;
import javax.swing.JSpinner;

import junit.extensions.abbot.ComponentTestFixture;
import abbot.finder.AWTHierarchy;
import abbot.finder.BasicFinder;
import abbot.finder.ComponentNotFoundException;
import abbot.finder.ComponentSearchException;
import abbot.finder.MultipleComponentsFoundException;
import abbot.finder.matchers.ClassMatcher;
import abbot.tester.JComponentTester;
import abbot.tester.JFileChooserTester;
import abbot.tester.JSliderTester;

import com.eteks.sweethome3d.io.DefaultUserPreferences;
import com.eteks.sweethome3d.io.HomeFileRecorder;
import com.eteks.sweethome3d.j3d.PhotoRenderer;
import com.eteks.sweethome3d.model.AspectRatio;
import com.eteks.sweethome3d.model.Compass;
import com.eteks.sweethome3d.model.Home;
import com.eteks.sweethome3d.model.HomeApplication;
import com.eteks.sweethome3d.model.HomeRecorder;
import com.eteks.sweethome3d.model.RecorderException;
import com.eteks.sweethome3d.model.UserPreferences;
import com.eteks.sweethome3d.swing.FileContentManager;
import com.eteks.sweethome3d.swing.HomePane;
import com.eteks.sweethome3d.swing.PhotoPanel;
import com.eteks.sweethome3d.swing.PhotoSizeAndQualityPanel;
import com.eteks.sweethome3d.swing.SwingViewFactory;
import com.eteks.sweethome3d.swing.VideoPanel;
import com.eteks.sweethome3d.tools.OperatingSystem;
import com.eteks.sweethome3d.viewcontroller.ContentManager;
import com.eteks.sweethome3d.viewcontroller.HomeController;
import com.eteks.sweethome3d.viewcontroller.View;

/**
 * Tests photo creation dialog.
 * @author Emmanuel Puybaret
 */
public class PhotoCreationTest extends ComponentTestFixture {
  private UserPreferences preferences;
  private HomeController homeController;

  @Override
  protected void setUp() throws Exception {
    super.setUp();
    System.setProperty("com.eteks.sweethome3d.j3d.rendererClassNames", PhotoRenderer.class.getName());
    this.preferences = new DefaultUserPreferences() {
        @Override
        public void write() throws RecorderException {        
        }
      };
    SwingViewFactory viewFactory = new SwingViewFactory();
    HomeApplication application = new HomeApplication() {
        @Override
        public HomeRecorder getHomeRecorder() {
          return new HomeFileRecorder();
        }
  
        @Override
        public UserPreferences getUserPreferences() {
          return preferences;
        }
      };
    // Create a dummy controller to load a home test file
    HomeController controller = new HomeController(new Home(), application, viewFactory);
    
    String testFile = new File(PhotoCreationTest.class.getResource("resources/home1.sh3d").toURI()).getAbsolutePath();
    controller.open(testFile);
    // Wait home is opened
    for (int i = 0; i < 100 && application.getHomes().size() == 0; i++) {
      Thread.sleep(100);
    }
    
    List<Home> homes = application.getHomes();
    assertEquals("Application doesn't contain one home", 1, homes.size());
    assertEquals("Test home wasn't opened", testFile, homes.get(0).getName());
    ContentManager contentManager = new FileContentManager(preferences) {
        @Override
        public String showSaveDialog(View parentView, String dialogTitle, ContentType contentType, String name) {
          String os = System.getProperty("os.name");
          if (OperatingSystem.isMacOSX()) {
            // Let's pretend the OS isn't Mac OS X to get a JFileChooser instance that works better in test
            System.setProperty("os.name", "dummy");
          }
          try {
            return super.showSaveDialog(parentView, dialogTitle, contentType, name);
          } finally {
            System.setProperty("os.name", os);
          }
        }
      };
    this.homeController = new HomeController(homes.get(0), application, viewFactory, contentManager);
  }
  
  public void testSunLocation() {
    // Tests azimuth and elevation values computed in Compass to avoid any regression
    Compass compass = new Compass(0, 0, 2);
    compass.setLatitude((float)Math.toRadians(45));
    compass.setLongitude((float)Math.toRadians(0));
    compass.setTimeZone("Europe/Paris");
    GregorianCalendar calendar = new GregorianCalendar(2010, GregorianCalendar.SEPTEMBER, 1, 14, 30, 10);
    calendar.setTimeZone(TimeZone.getTimeZone(compass.getTimeZone()));
    assertEquals("Incorrect azimuth", 3.383972f, compass.getSunAzimuth(calendar.getTimeInMillis()), 1E-5f);
    assertEquals("Incorrect elevation", 0.915943f, compass.getSunElevation(calendar.getTimeInMillis()), 1E-5f);
    
    compass = new Compass(0, 0, 2);
    compass.setLatitude((float)Math.toRadians(40));
    compass.setLongitude((float)Math.toRadians(160));
    compass.setTimeZone("Asia/Tokyo");
    calendar = new GregorianCalendar(2011, GregorianCalendar.JANUARY, 31, 8, 0, 0);
    calendar.setTimeZone(TimeZone.getTimeZone(compass.getTimeZone()));
    assertEquals("Incorrect azimuth", 2.44565f, compass.getSunAzimuth(calendar.getTimeInMillis()), 1E-5f);
    assertEquals("Incorrect elevation", 0.38735f, compass.getSunElevation(calendar.getTimeInMillis()), 1E-5f);
  }
  
  public void testPhotoCreation() throws InterruptedException, ComponentSearchException, NoSuchFieldException, IllegalAccessException, InvocationTargetException, IOException {
    final JComponent view = (JComponent)this.homeController.getView();
    
    // Create a frame that displays a home view 
    JFrame frame = new JFrame("Photo Creation Test");    
    frame.add(view);
    frame.pack();

    showWindow(frame);
    JComponentTester tester = new JComponentTester();
    tester.waitForIdle();

    JDialog photoCreationDialog = showPhotoCreationPanel(this.preferences, this.homeController, frame, tester);
    // Retrieve PhotoPanel components
    PhotoPanel panel = (PhotoPanel)TestUtilities.findComponent(photoCreationDialog, PhotoPanel.class);    
    JButton createButton = (JButton)TestUtilities.getField(panel, "createButton");
    final JButton saveButton = (JButton)TestUtilities.getField(panel, "saveButton");
    JButton closeButton = (JButton)TestUtilities.getField(panel, "closeButton");
    PhotoSizeAndQualityPanel sizePanel = (PhotoSizeAndQualityPanel)TestUtilities.getField(panel, "sizeAndQualityPanel");
    final JSpinner widthSpinner = (JSpinner)TestUtilities.getField(sizePanel, "widthSpinner");
    JSpinner heightSpinner = (JSpinner)TestUtilities.getField(sizePanel, "heightSpinner");
    final JComboBox aspectRatioComboBox = (JComboBox)TestUtilities.getField(sizePanel, "aspectRatioComboBox");
    JSlider qualitySlider = (JSlider)TestUtilities.getField(sizePanel, "qualitySlider");
    
    tester.invokeAndWait(new Runnable() {
        public void run() {
          widthSpinner.setValue(200L);
          aspectRatioComboBox.setSelectedItem(AspectRatio.SQUARE_RATIO);
        }
      });
    assertEquals("Height spinner has wrong value", 200, ((Number)heightSpinner.getValue()).intValue());
    JSliderTester sliderTester = new JSliderTester();
    createButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent ev) {
          assertFalse("Rendering didn't start", saveButton.isEnabled());
        }
      });
    for (int i = qualitySlider.getMinimum(); i <= qualitySlider.getMaximum(); i++) {
      sliderTester.actionSlide(qualitySlider, i);
      // Test image creation at each quality 
      tester.actionClick(createButton);
      // Wait image is generated
      for (int j = 0; j < 1000 && !saveButton.isEnabled(); j++) {
        Thread.sleep(100);
      }
      assertTrue("Rendering didn't end", saveButton.isEnabled());
      saveAndAssertMediaIsSaved(saveButton, ContentManager.ContentType.PNG, 
          preferences.getLocalizedString(PhotoPanel.class, "savePhotoDialog.title"),
          photoCreationDialog, tester);
    }
    tester.actionClick(closeButton);
    tester.close(frame);
  }

  public void testVideoCreation() throws InterruptedException, ComponentSearchException, NoSuchFieldException, IllegalAccessException, IOException {
    final JComponent view = (JComponent)this.homeController.getView();
    
    // Create a frame that displays a home view 
    JFrame frame = new JFrame("Video Creation Test");    
    frame.add(view);
    frame.pack();

    showWindow(frame);
    JComponentTester tester = new JComponentTester();
    tester.waitForIdle();

    JDialog videoCreationDialog = showVideoCreationPanel(this.preferences, this.homeController, frame, tester);
    // Retrieve VideoPanel components
    VideoPanel panel = (VideoPanel)TestUtilities.findComponent(videoCreationDialog, VideoPanel.class);
    JProgressBar progressBar = (JProgressBar)TestUtilities.getField(panel, "progressBar");
    JSlider qualitySlider = (JSlider)TestUtilities.getField(panel, "qualitySlider");
    JButton createButton = (JButton)TestUtilities.getField(panel, "createButton");
    JButton saveButton = (JButton)TestUtilities.getField(panel, "saveButton");
    JButton closeButton = (JButton)TestUtilities.getField(panel, "closeButton");
    JSliderTester sliderTester = new JSliderTester();
    for (int i = qualitySlider.getMinimum(); i < qualitySlider.getMaximum(); i += 2) {
      sliderTester.actionSlide(qualitySlider, i);
      // Test image creation at quality 1 and 3 
      assertFalse("Progress bar is showing", progressBar.isShowing());
      tester.actionClick(createButton);
      assertTrue("Progress bar isn't showing", progressBar.isShowing());
      // Wait image is generated
      for (int j = 0; j < 1000 && !saveButton.isEnabled(); j++) {
        Thread.sleep(100);
      }
      assertTrue("Rendering didn't end", saveButton.isEnabled());
      saveAndAssertMediaIsSaved(saveButton, ContentManager.ContentType.MOV, 
          preferences.getLocalizedString(VideoPanel.class, "saveVideoDialog.title"),
          videoCreationDialog, tester);
    }
    tester.actionClick(closeButton);
    tester.close(frame);
  }
  
  /**
   * Returns the dialog that displays photo panel. 
   */
  private JDialog showPhotoCreationPanel(UserPreferences preferences,
                                        final HomeController controller, 
                                        JFrame parent, JComponentTester tester) throws ComponentSearchException {
    tester.invokeLater(new Runnable() { 
        public void run() {
          // Display dialog box later in Event Dispatch Thread to avoid blocking test thread
          ((JComponent)controller.getView()).getActionMap().get(HomePane.ActionType.CREATE_PHOTO).actionPerformed(null);
        }
      });
    // Wait for 3D view to be shown
    tester.waitForFrameShowing(new AWTHierarchy(), preferences.getLocalizedString(
        PhotoPanel.class, "createPhoto.title"));
    // Check dialog box is displayed
    JDialog photoCreationDialog = (JDialog)new BasicFinder().find(parent, new ClassMatcher (JDialog.class, true));
    assertTrue("Photo creation dialog not showing", photoCreationDialog.isShowing());
    return photoCreationDialog;
  }

  /**
   * Returns the dialog that displays video panel. 
   */
  private JDialog showVideoCreationPanel(UserPreferences preferences,
                                        final HomeController controller, 
                                        JFrame parent, JComponentTester tester) throws ComponentSearchException {
    tester.invokeLater(new Runnable() { 
        public void run() {
          // Display dialog box later in Event Dispatch Thread to avoid blocking test thread
          ((JComponent)controller.getView()).getActionMap().get(HomePane.ActionType.CREATE_VIDEO).actionPerformed(null);
        }
      });
    // Wait for 3D view to be shown
    tester.waitForFrameShowing(new AWTHierarchy(), preferences.getLocalizedString(
        VideoPanel.class, "createVideo.title"));
    // Check dialog box is displayed
    JDialog photoCreationDialog = (JDialog)new BasicFinder().find(parent, new ClassMatcher (JDialog.class, true));
    assertTrue("Video creation dialog not showing", photoCreationDialog.isShowing());
    return photoCreationDialog;
  }
  
  /**
   * Assert the generated media can be saved. 
   */
  private void saveAndAssertMediaIsSaved(JButton saveButton, 
                                  ContentManager.ContentType contentType,
                                  final String fileDialogTitle, 
                                  JDialog parent, 
                                  JComponentTester tester) throws ComponentNotFoundException, MultipleComponentsFoundException, InterruptedException, IOException {
    File tmpDirectory = File.createTempFile("media", "dir");
    tmpDirectory.delete();    
    assertTrue("Couldn't create tmp directory", tmpDirectory.mkdir());
    String mediaBaseName = "test";
    // Show save dialog box
    tester.actionClick(saveButton);
    // Wait for file chooser to be shown
    tester.waitForFrameShowing(new AWTHierarchy(), fileDialogTitle);
    // Check dialog box is displayed
    final Dialog saveDialog = (Dialog)new BasicFinder().find(parent, 
        new ClassMatcher (Dialog.class, true) {
          @Override
          public boolean matches(Component c) {
            if (super.matches(c)) {
              return ((Dialog)c).getTitle().equals(fileDialogTitle);
            } else {
              return false;
            }
          }
        });
    assertTrue("Save dialog not showing", saveDialog.isShowing());
    // Change file in print to PDF file chooser 
    final JFileChooserTester fileChooserTester = new JFileChooserTester();
    final JFileChooser fileChooser = (JFileChooser)new BasicFinder().find(saveDialog, 
        new ClassMatcher(JFileChooser.class));
    fileChooserTester.actionSetDirectory(fileChooser, tmpDirectory.getAbsolutePath());
    fileChooserTester.actionSetFilename(fileChooser, mediaBaseName);
    // Select Ok option to hide dialog box
    fileChooserTester.actionApprove(fileChooser);
    assertFalse("Save dialog still showing", saveDialog.isShowing());
    // Wait saving
    File mediaFile = new File(tmpDirectory, mediaBaseName + "." + contentType.toString().toLowerCase());
    for (int i = 0; i < 100 && !mediaFile.exists(); i++) {
      Thread.sleep(100);
    }
    assertTrue("Media file " + mediaFile + " doesn't exist", mediaFile.exists());
    assertTrue("Media file is empty", mediaFile.length() > 0);
    mediaFile.delete();
    tmpDirectory.delete();
  }
}
