package uk.ac.stir.cs.terrawiz;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;

import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.*;

import org.apache.xerces.dom.DOMImplementationImpl;
import org.apache.xerces.parsers.DOMParser;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
  Class to deal with beginner mode.

  @author	Tao (Fred) Lu, Kenneth J. Turner
  @version	1.0 (6th April 2009)
*/

public class Beginner extends JFrame implements ActionListener {

  // widgets
  private JLabel labNote, labLake, labMountain, labGrass, labCloud;
  private JCheckBox cboxLake, cboxMountain, cboxGrass, cboxCloud;
  private JButton btnSave, btnAdvanced, btnLang;
  private JFileChooser fileChooser;

  // panels
  private JPanel panelUp, panelDown;

  // strings
  private String settingsFullPath, strLang;
  private String homeDirectory = System.getProperty("user.home");

  // objects for DOM parser
  private File selectedFile;

  private Properties translation;

  private DOMParser parser;

  private Document domTGD, domTGW, domSetting;

  private Element rootSetting, recentFile, language, rootTGW, mode,
    lighting, camera, lakeTGD, waterShader,
    fractalShader, surfaceLayer,
    surfaceShader, cloudShader,
    cloudLayer, lakeTGW, mountain, surface,
    cloud, rootTGD;

  /** Constructor of Beginner object. */

  public Beginner() {
    setWindowTitle("");
    setSize(Common.beginnerWidth, Common.beginnerHeight);
    int w = (Toolkit.getDefaultToolkit().getScreenSize().width -
      Common.beginnerWidth) / 2;
    int h = (Toolkit.getDefaultToolkit().getScreenSize().height -
      Common.beginnerHeight) / 2;
    setLocation(w, h);
    initParser();					// initialize parser
    if (!loadSettingDOM(Common.settingsFile))		// settings loaded?
      initSettingFile();				// if not, initialize

    createGUI();
    resetLang();					// reset language
    pack();
    setVisible(true);
  }

  /**
    Constructor of Beginner object.

    @param lang		user language
  */

  public Beginner(String lang) {
    setTitle(translate("title.beginnermodenew"));
    setSize(Common.beginnerWidth, Common.beginnerHeight);
    int w = (Toolkit.getDefaultToolkit().getScreenSize().width -
      Common.beginnerWidth) / 2;
    int h = (Toolkit.getDefaultToolkit().getScreenSize().height -
      Common.beginnerHeight) / 2;
    this.setLocation(w, h);
    initSettingFile(lang);
    initParser();					// initialize parser
    if (!loadSettingDOM(Common.settingsFile))		// settings loaded?
      initSettingFile();				// if not, initialize

    createGUI();
    resetLang();					// reset language
    this.pack();
    this.setVisible(true);
  }

  /**
    Get input stream for resource/library file.

    @param fileName	file name relative to program base directory
    @return		file input stream
  */

  private InputStream getResourceStream(String fileName) {
    return(getClass().getClassLoader().getResourceAsStream(fileName));
  }

  /**
    Initialize settings file.

    @param lang		user language
  */

  private void initSettingFile(String lang) {
    strLang = lang;
    // create a DOM
    domSetting =
      DOMImplementationImpl.getDOMImplementation().
	createDocument(null, null, null);
    // create a root element
    rootSetting = domSetting.createElement("Setting");
    // recently opened files
    recentFile = domSetting.createElement("RecentFile");
    // preferred language
    language = domSetting.createElement("Language");
    language.appendChild(domSetting.createTextNode(lang));
    // preferred mode
    mode = domSetting.createElement("Mode");
    mode.appendChild(domSetting.createTextNode("Beginner"));

    rootSetting.appendChild(recentFile);
    rootSetting.appendChild(language);
    rootSetting.appendChild(mode);
    domSetting.appendChild(rootSetting);

    // create the setting file
    settingsFullPath = homeDirectory + Common.settingsFile;
    Common.writeXML(domSetting, settingsFullPath);
  }

  /** Initialize the parser, maintaining two DOM trees (TGD, TGW). */

  private void initParser() {
    loadTGDDOM(Common.terragenTemplate);		// load standard file
    createTGWDOM();
  }

  /**
    Load TGD DOM tree from a specific file.

    @param fileName	TGD file name
  */

  private void loadTGDDOM(String fileName) {
    parser = new DOMParser();
    try {
      parser.parse(new InputSource(getResourceStream(fileName)));
    }
    catch (SAXException e1) {
      e1.printStackTrace();
    }
    catch (IOException e1) {				// file doesn't exist
      JOptionPane.showMessageDialog(this, translate("err.filenotexist") +
	" - " + fileName);
      setWindowTitle("");
      Common.fileState = Common.createdFile;	// set created file
      return;
    }

    domTGD = parser.getDocument();
    rootTGD = domTGD.getDocumentElement();
  }

  /** Create TGW DOM tree from scratch. */

  private void createTGWDOM() {
    // create a DOM
    domTGW = DOMImplementationImpl.getDOMImplementation().createDocument(
      null, null, null);
    // create a root element
    rootTGW = domTGW.createElement("terragen");
    // set its schema
    rootTGW.setAttribute("xmlns:xsi",
      "http://www.w3.org/2001/XMLSchema-instance");
    rootTGW.setAttribute("xsi:noNamespaceSchemaLocation", Common.wizardURL);

    lighting = domTGW.createElement("lighting");
    camera = domTGW.createElement("camera");
    lighting.setAttribute("heading", Common.tgwLightingHeading1);
    lighting.setAttribute("elevation", Common.tgwLightingElevation1);
    camera.setAttribute("height", Common.tgwCameraView1);
    camera.setAttribute("heading", Common.tgwCameraHeading1);

    rootTGW.appendChild(lighting);
    rootTGW.appendChild(camera);
    domTGW.appendChild(rootTGW);
  }

  /** Initialize recently opened file. */

  private void initSettingFile() {
    // create a DOM
    domSetting = DOMImplementationImpl.getDOMImplementation().
      createDocument(null, null, null);
    // create a root element
    rootSetting = domSetting.createElement("Setting");
    recentFile = domSetting.createElement("RecentFile");

    language = domSetting.createElement("Language");
    language.appendChild(domSetting.createTextNode("en-GB"));
    mode = domSetting.createElement("Mode");
    mode.appendChild(domSetting.createTextNode("Advanced"));

    rootSetting.appendChild(recentFile);
    rootSetting.appendChild(language);
    rootSetting.appendChild(mode);

    domSetting.appendChild(rootSetting);
    // create the setting file
    settingsFullPath = homeDirectory + Common.settingsFile;
    Common.writeXML(domSetting, settingsFullPath);
  }

  /**
    Load recent DOM tree from a file.

    @param fileName	DOM file name
    @return		true if loaded successfully
  */

  private boolean loadSettingDOM(String fileName) {
    parser = new DOMParser();
    try {
      settingsFullPath = homeDirectory + fileName;
      parser.parse(settingsFullPath);
    }
    catch (SAXException e1) {
      return false;
    }
    catch (IOException e1) {
      return false;
    }

    domSetting = parser.getDocument();
    rootSetting = domSetting.getDocumentElement();
    // look for RecentFile node
    NodeList nodeList1 = rootSetting.getElementsByTagName("RecentFile");
    if (nodeList1.getLength() == 0)
      return false;
    else
      recentFile = (Element) nodeList1.item(0);

    // look for Language node
    NodeList nodeList2 = rootSetting.getElementsByTagName("Language");
    if (nodeList2.getLength() == 0)
      return false;
    else
      strLang = nodeList2.item(0).getFirstChild().getNodeValue();

    return true;
  }

  /** Create GUI. */

  private void createGUI() {
    // set icon
    ImageIcon titleIcon = new ImageIcon(Common.iconPath);
    setIconImage(titleIcon.getImage());

    setDefaultCloseOperation(EXIT_ON_CLOSE);
    Container window = getContentPane();
    window.setLayout(new BorderLayout());

    // panel for buttons
    panelDown = new JPanel();
    panelDown.setPreferredSize(new Dimension(400, 50));
    panelDown.setLayout(new FlowLayout());

    // panel for widgets
    panelUp = new JPanel();
    panelUp.setPreferredSize(new Dimension(400, 300));
    panelUp.setLayout(new GridBagLayout());
    GridBagConstraints constraints = new GridBagConstraints();

    constraints.gridx = 0;
    constraints.gridy = 0;
    constraints.gridwidth = 2;
    constraints.anchor = GridBagConstraints.WEST;
    labNote = new JLabel(translate("beginner.label.labnote") + ":");
    panelUp.add(labNote, constraints);

    constraints.gridwidth = 1;
    constraints.gridx = 0;
    constraints.gridy = 1;
    constraints.anchor = GridBagConstraints.CENTER;
    panelUp.add(new JLabel(), constraints);

    // lake
    constraints.gridwidth = 1;
    constraints.gridx = 0;
    constraints.gridy = 2;
    constraints.anchor = GridBagConstraints.CENTER;
    labLake = new JLabel(translate("beginner.label.lake"));
    panelUp.add(labLake, constraints);

    constraints.gridx = 1;
    constraints.gridy = 2;
    cboxLake = new JCheckBox();
    panelUp.add(cboxLake, constraints);

    // mountain
    constraints.gridx = 0;
    constraints.gridy = 3;
    labMountain = new JLabel(translate("beginner.label.mountain"));
    panelUp.add(labMountain, constraints);

    constraints.gridx = 1;
    constraints.gridy = 3;
    cboxMountain = new JCheckBox();
    panelUp.add(cboxMountain, constraints);

    // grass
    constraints.gridx = 0;
    constraints.gridy = 5;
    labGrass = new JLabel(translate("beginner.label.grass"));
    panelUp.add(labGrass, constraints);

    constraints.gridx = 1;
    constraints.gridy = 5;
    cboxGrass = new JCheckBox();
    panelUp.add(cboxGrass, constraints);

    // cloud
    constraints.gridx = 0;
    constraints.gridy = 6;
    labCloud = new JLabel(translate("beginner.label.cloud"));
    panelUp.add(labCloud, constraints);

    constraints.gridx = 1;
    constraints.gridy = 6;
    cboxCloud = new JCheckBox();
    panelUp.add(cboxCloud, constraints);

    // buttons
    btnSave = new JButton(translate("beginner.btn.save"));
    btnSave.addActionListener(this);
    panelDown.add(btnSave);

    btnAdvanced = new JButton(translate("beginner.btn.advancedmode"));
    btnAdvanced.addActionListener(this);
    panelDown.add(btnAdvanced);

    btnLang = new JButton(translate("beginner.btn.lang"));
    btnLang.addActionListener(this);
    panelDown.add(btnLang);

    window.add("North", panelUp);
    window.add("South", panelDown);
  }

  /**
    Respond to action event.

    @param e  action event
  */

  public void actionPerformed(ActionEvent e) {
    if (e.getSource() == btnSave) {
      // add a file chooser with two file extension to choose (tgd and tgw)
      addFileChooser();
      int saveReply = fileChooser.showSaveDialog(this);
      if (saveReply == JFileChooser.APPROVE_OPTION) {
	// file state changes to opened file
	Common.fileState = Common.openedFile;
	selectedFile = fileChooser.getSelectedFile();
	// get the extension of the chosen file
	String fileExtension = Common.getFileExtension(selectedFile);
	// file extension is tgd or tgw
	if (fileExtension.equalsIgnoreCase("tgd") ||
	  fileExtension.equalsIgnoreCase("tgw"))
	  setWindowTitle(selectedFile.getAbsolutePath());
	else
	  setWindowTitle(selectedFile.getAbsolutePath() +
	    "." + fileChooser.getFileFilter().getDescription());

	if (fileExtension.equalsIgnoreCase("tgd")) {
	  // file catgory becomes tgdfile
	  Common.fileCategory = Common.tgdfile;
	  // change the TGD DOM according to the current setting
	  changeTGDDOM();
	  // sve and give a warning
	  warnSaveCompleted(domTGD, selectedFile, "none");
	}
	else if (fileExtension.equalsIgnoreCase("tgw")) {
	  // file catgory becomes tgwfile
	  Common.fileCategory = Common.tgwfile;
	  // change the TGW DOM according to the current setting
	  changeTGWDOM();
	  // save and give a warning
	  warnSaveCompleted(domTGW, selectedFile, "none");
	}
	else
	  if (fileChooser.getFileFilter().getDescription().
		equalsIgnoreCase("tgd")) {
	    // file category becomes TGD
	    Common.fileCategory = Common.tgdfile;
	    // change the TGD DOM according to the current setting
	    changeTGDDOM();
	    // save and give a warning
	    warnSaveCompleted(domTGD, selectedFile, "tgd");
	  }
	  else if (fileChooser.getFileFilter().getDescription().
	    equalsIgnoreCase("tgw")) {
	    // file catgory becomes TGW
	    Common.fileCategory = Common.tgwfile;
	    // change the TGW DOM according to the current setting
	    changeTGWDOM();
	    // save and give a warning
	    warnSaveCompleted(domTGW, selectedFile, "tgw");
	  }

	// file didn't change
	Common.fileChanged = false;
      }
    }

    if (e.getSource() == btnAdvanced) {
      // change mode to advanced
      changeSetting("Advanced", "Mode");
      JOptionPane.showMessageDialog(this, translate("beginner.popup.mode"));
    }

    if (e.getSource() == btnLang) {
      // look for Language node
      NodeList nodeList1 = rootSetting.getElementsByTagName("Language");
      strLang = nodeList1.item(0).getFirstChild().getNodeValue();

      if (strLang.equals("en-GB"))
	changeSetting("zh-CN", "Language");
      else if (strLang.equals("zh-CN"))
	changeSetting("en-GB", "Language");

      resetLang();
    }
  }

  /** Reset language according to the settings file.  */

  public void resetLang() {
    initLang();
    labNote.setText(translate("beginner.label.labnote"));
    btnSave.setText(translate("beginner.btn.save"));
    btnAdvanced.setText(translate("beginner.btn.advancedmode"));
    btnLang.setText(translate("beginner.btn.lang"));
    labLake.setText(translate("beginner.label.lake"));
    labMountain.setText(translate("beginner.label.mountain"));
    labGrass.setText(translate("beginner.label.grass"));
    labCloud.setText(translate("beginner.label.cloud"));
  }

  /** Initialize language. */

  private void initLang() {
    parser = new DOMParser();
    try {
      settingsFullPath = homeDirectory + Common.settingsFile;
      parser.parse(settingsFullPath);
    }
    catch (SAXException e1) {
    }
    catch (IOException e1) {
    }

    domSetting = parser.getDocument();
    rootSetting = domSetting.getDocumentElement();
    // look for Language node
    NodeList nodeList1 = rootSetting.getElementsByTagName("Language");
    String lang = nodeList1.item(0).getFirstChild().getNodeValue();

    translation = new Properties();
    InputStream languageFile = getResourceStream("res/" + lang + ".properties");
    try {
      InputStreamReader languageInput =
	new InputStreamReader(languageFile, "UTF-16");
      translation.load(languageInput);
      languageInput.close();
    }
    catch (IOException exception) {
      System.err.println("Wrong path or filename");
    }
    setTitle(translate("title.beginnermodenew"));
  }

  /**
    Translate language term.

    @param word  string from en-GB or zh-CN properties, such as "menu.file"
    @return      translated string
  */

  public String translate(String word) {
    String translated = "";
    if (translation == null ||
      (translated = translation.getProperty(word)) == null)
      translated = word + "?";
    return (translated);
  }

  /**
    Save file with a warning.

    @param dom			document DOM
    @param selectedFile		selected file name
    @param suffix		selected file suffix
  */

  private void warnSaveCompleted(Document dom, File selectedFile,
   String suffix) {
    if (suffix.equals("tgd") || suffix.equals("tgw")) {
      if (!Common.writeXML(dom, selectedFile.getAbsolutePath() + "." + suffix))
	JOptionPane.showMessageDialog(this, translate("warn.savenotcomplete"));
      // else
	// JOptionPane.showMessageDialog(this, translate("warn.savecomplete"));

    }
    else if (suffix.equals("none")) {
      if (!Common.writeXML(dom, selectedFile.getAbsolutePath()))
	JOptionPane.showMessageDialog(this, translate("warn.savenotcomplete"));
      // else
	// JOptionPane.showMessageDialog(this, translate("warn.savecomplete"));
    }

  }

  /** Add a file chooser with two file extension to choose (tgd and tgw). */

  private void addFileChooser() {
    fileChooser = new JFileChooser();
    fileChooser.setCurrentDirectory(new File(homeDirectory));
    fileChooser.setFileFilter(new FileNameExtensionFilter("tgd", "tgd"));
    fileChooser.setFileFilter(new FileNameExtensionFilter("tgw", "tgw"));
  }

  /** Change TGD DOM according to the current setting. */

  private void changeTGDDOM() {
    // lake
    if (cboxLake.isSelected()) {
      if (Common.checkNodeExistNum(rootTGD, "lake") == 0)
	Common.enableTGDLakeNodes(rootTGD, domTGD, lakeTGD, waterShader);
    }
    else
      Common.disableTGDLakeNodes(rootTGD);

    // mountain
    boolean isTGDMountExist = false;
    if (cboxMountain.isSelected()) {
      // look for power_fractal_shader_v3 node
      NodeList aList = rootTGD.getElementsByTagName("power_fractal_shader_v3");
      for (int i = 0; i < aList.getLength(); i++) {
	NamedNodeMap attributes = aList.item(i).getAttributes();
	for (int j = 0; j < attributes.getLength(); j++) {
	  Node theAttribute = attributes.item(j);
	  // there might be more than one power_fractal_shader_v3 node
	  // so we have to look for gui_group node to distinguish them
	  if (theAttribute.getNodeName().equals("gui_group") &&
	    theAttribute.getNodeValue().equals("Terrain"))
	    isTGDMountExist = true;			// there is a mountain
	}
      }

      if (!isTGDMountExist) {
	Common.enableTGDMountNodes(rootTGD, domTGD, fractalShader);
	Common.changeAttr(rootTGD, "compute_terrain", "input_node",
	  "Fractal terrain 01", 0);
      }
    }
    else
      Common.disableTGDMountNodes(rootTGD);

    // grass
    if (cboxGrass.isSelected()) {
      if (Common.checkNodeExistNum(rootTGD, "surface_layer") == 0)
	Common.enableTGDSurfaceNodes(rootTGD, domTGD, surfaceLayer,
	  surfaceShader, cboxMountain);

      Common.changeAttr(rootTGD, "planet", "surface_shader",
	"Surface layer 01", 0);
      Common.changeAttr(rootTGD, "surface_layer", "diffuse_colour",
	Common.tgdSurfaceSkin2, 0);
    }
    else
      Common.disableTGDSurfaceNodes(rootTGD);

    // cloud
    if (cboxCloud.isSelected()) {
      if (Common.checkNodeExistNum(rootTGD, "cloud_layer_v2") == 0)
	Common.enableTGDCloudNodes(rootTGD, domTGD, cloudLayer,
	  cloudShader);
    }
    else
      Common.disableTGDCloudNodes(rootTGD);
  }

  /** Change TGW DOM according to the current setting. */

  private void changeTGWDOM() {
    // lake
    if (cboxLake.isSelected()) {
      if (Common.checkNodeExistNum(rootTGW, "lake") == 0)
	Common.enableTGWLakeNodes(rootTGW, domTGW, lakeTGW);
    }
    else
      Common.disableTGWLakeNodes(rootTGW);

    // mountain
    if (cboxMountain.isSelected()) {
      if (Common.checkNodeExistNum(rootTGW, "mountain") == 0)
	Common.enableTGWMountNodes(rootTGW, domTGW, mountain);
    }
    else
      Common.disableTGWMountNodes(rootTGW);

    // snow
    if (cboxGrass.isSelected()) {
      if (Common.checkNodeExistNum(rootTGW, "surface") == 0)
	Common.enableTGWSurfaceNodes(rootTGW, domTGW, surface);
    }
    else
      Common.disableTGWSurfaceNodes(rootTGW);

    // cloud
    if (cboxCloud.isSelected()) {
      if (Common.checkNodeExistNum(rootTGW, "cloud") == 0)
	Common.enableTGWCloudNodes(rootTGW, domTGW, cloud);
    }
    else
      Common.disableTGWCloudNodes(rootTGW);
  }

  /**
    Change the setting of a certain element.

    @param value	value to change to
    @param nodeName	node to change
  */

  public void changeSetting(String value, String nodeName) {
    Common.changeElem(rootSetting, nodeName, value);
    Common.writeXML(domSetting, homeDirectory + Common.settingsFile);
  }

  /**
    Show message in window title.

    @param message	message
  */

  private void setWindowTitle(String message) {
    setTitle(translate("title.beginnermode") + " " + message);
  }

}

