/**
 * Authoer: Jinghui Yu
 * Last editing date: 6/6/2013
 */

/*
 * Michael Goldenberg, Jinghui Yu, and Ben Borchard modified this file on 10/27/13
 * with the following changes:
 * 
 * 1.) Changed the return value of checkValidity from a boolean to void (the functionality
 * enabled by that boolean value is now controlled by throwing ValidationException)
 * 2.) Changed the edit commit method on the name column so that it calls Validate.nameableObjects()
 * which throws a ValidationException in lieu of returning a boolean value
 * 3.) Moved registersHaveEqualWidths method to the Validate class and changed the return value to void
 * from boolean
 */
package cpusim.gui.editmicroinstruction;

import cpusim.Mediator;
import cpusim.model.Microinstruction;
import cpusim.gui.util.FXMLLoaderFactory;
import cpusim.model.microinstruction.IO;
import cpusim.model.module.Register;
import cpusim.util.Validate;
import cpusim.util.ValidationException;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.ComboBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.util.Callback;

import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;

/**
 * The controller for editing the Logical command in the EditMicroDialog.
 */
public class IOTableController
        extends MicroController implements Initializable {
    @FXML TableView<IO> table;
    @FXML TableColumn<IO,String> name;
    @FXML TableColumn<IO,String> type;
    @FXML TableColumn<IO,Register> buffer;
    @FXML TableColumn<IO,String> direction;

    private ObservableList currentMicros;
    private IO prototype;

    /**
     * Constructor
     * @param mediator the mediator used to store the machine
     */
    public IOTableController(Mediator mediator){
        super(mediator);
        this.mediator = mediator;
        this.machine = this.mediator.getMachine();
        this.currentMicros = machine.getMicros("io");
        Register r = (machine.getAllRegisters().size() == 0 ? null :
                (Register) machine.getAllRegisters().get(0));
        this.prototype = new IO("???", machine, "integer", r, "input");
        clones = (Microinstruction[]) createClones();

        FXMLLoader fxmlLoader = FXMLLoaderFactory.fromRootController(this, "IOTable.fxml");

        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            // should never happen
            assert false : "Unable to load file: IOTable.fxml";
        }

        for (int i = 0; i < clones.length; i++){
            table.getItems().add((IO)clones[i]);
        }
    }

    /**
     * initializes the dialog window after its root element has been processed.
     * makes all the cells editable and the use can edit the cell directly and
     * hit enter to save the changes.
     *
     * @param url the location used to resolve relative paths for the root
     *            object, or null if the location is not known.
     * @param rb  the resources used to localize the root object, or null if the root
     *            object was not localized.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        name.prefWidthProperty().bind(table.prefWidthProperty().divide(100/25.0));
        buffer.prefWidthProperty().bind(table.prefWidthProperty().divide(100/25.0));
        direction.prefWidthProperty().bind(table.prefWidthProperty().divide(100/25.0));
        type.prefWidthProperty().bind(table.prefWidthProperty().divide(100/25.0));

        Callback<TableColumn<IO,String>,TableCell<IO,String>> cellStrFactory =
                new Callback<TableColumn<IO, String>, TableCell<IO, String>>() {
                    @Override
                    public TableCell<IO, String> call(
                            TableColumn<IO, String> setStringTableColumn) {
                        return new cpusim.gui.util.EditingStrCell<IO>();
                    }
                };
        Callback<TableColumn<IO,String>,TableCell<IO,String>> cellTypeFactory =
                new Callback<TableColumn<IO, String>, TableCell<IO, String>>() {
                    @Override
                    public TableCell<IO, String> call(
                            TableColumn<IO, String> setStringTableColumn) {
                        return new ComboBoxTableCell<IO,String>(
                                FXCollections.observableArrayList(
                                        "integer",
                                        "ascii",
                                        "unicode"
                                )
                        );
                    }
                };
        Callback<TableColumn<IO,Register>,TableCell<IO,Register>> cellRegFactory =
                new Callback<TableColumn<IO, Register>, TableCell<IO, Register>>() {
                    @Override
                    public TableCell<IO, Register> call(
                            TableColumn<IO, Register> setStringTableColumn) {
                        return new ComboBoxTableCell<IO,Register>(
                                machine.getAllRegisters());
                    }
                };
        Callback<TableColumn<IO,String>,TableCell<IO,String>> cellDircFactory =
                new Callback<TableColumn<IO, String>, TableCell<IO, String>>() {
                    @Override
                    public TableCell<IO, String> call(
                            TableColumn<IO, String> setStringTableColumn) {
                        return new ComboBoxTableCell<IO,String>(
                                FXCollections.observableArrayList(
                                        "input",
                                        "output"
                                )
                        );
                    }
                };

        name.setCellValueFactory(new PropertyValueFactory<IO, String>("name"));
        type.setCellValueFactory(new PropertyValueFactory<IO, String>("type"));
        buffer.setCellValueFactory(new PropertyValueFactory<IO, Register>("buffer"));
        direction.setCellValueFactory(new PropertyValueFactory<IO, String>("direction"));

        //Add for Editable Cell of each field, in String or in Integer
        name.setCellFactory(cellStrFactory);
        name.setOnEditCommit(
                new EventHandler<TableColumn.CellEditEvent<IO, String>>() {
                    @Override
                    public void handle(TableColumn.CellEditEvent<IO, String> text) {
                        String newName = text.getNewValue();
                        String oldName = text.getOldValue();
                        ( text.getRowValue()).setName(newName);
                        try{
                            Validate.namedObjectsAreUniqueAndNonempty(table.getItems().toArray());
                        } catch (ValidationException ex){
                            (text.getRowValue()).setName(oldName);
                            updateTable();
                        }
                    }
                }
        );

        type.setCellFactory(cellTypeFactory);
        type.setOnEditCommit(
                new EventHandler<TableColumn.CellEditEvent<IO, String>>() {
                    @Override
                    public void handle(TableColumn.CellEditEvent<IO, String> text) {
                        ((IO)text.getRowValue()).setType(
                                text.getNewValue());
                    }
                }
        );

        buffer.setCellFactory(cellRegFactory);
        buffer.setOnEditCommit(
                new EventHandler<TableColumn.CellEditEvent<IO, Register>>() {
                    @Override
                    public void handle(TableColumn.CellEditEvent<IO, Register> text) {
                        ((IO)text.getRowValue()).setBuffer(
                                text.getNewValue());
                    }
                }
        );

        direction.setCellFactory(cellDircFactory);
        direction.setOnEditCommit(
                new EventHandler<TableColumn.CellEditEvent<IO, String>>() {
                    @Override
                    public void handle(TableColumn.CellEditEvent<IO, String> text) {
                        ((IO)text.getRowValue()).setDirection(
                                text.getNewValue());
                    }
                }
        );

    }

    /**
     * getter for prototype of the right subclass
     * @return the prototype of the subclass
     */
    public Microinstruction getPrototype()
    {
        return prototype;
    }

    /**
     * getter for the class object for the controller's objects
     * @return the class object
     */
    public Class getMicroClass()
    {
        return IO.class;
    }

    /**
     * getter for the current IO Microinstructions.
     * @return a list of current microinstructions.
     */
    public ObservableList getCurrentMicros()
    {
        return currentMicros;
    }

    /**
     * returns a string about the type of the table.
     * @return a string about the type of the table.
     */
    public String toString()
    {
        return "IO";
    }

    /**
     * gets properties
     * @return an array of String representations of the
     * various properties of this type of microinstruction
//     */
//    public String[] getProperties()
//    {
//        return new String[]{"name", "type", "buffer", "direction"};
//    }

    /**
     * use clones to replace existing Microinstructions
     * in the machine, and update the machine to delete
     * all references to the deleted Microinstructions.
     */
    public void updateCurrentMicrosFromClones()
    {
        machine.setMicros("io", createNewMicroList(clones));
    }

    /**
     * Set the clones to the new array passed as a parameter.
     * Does not check for validity.
     *
     * @param newClones Object array containing new set of clones
     */
    public void setClones(ObservableList newClones)
    {
        IO[] ios = new IO[newClones.size()];
        for (int i = 0; i < newClones.size(); i++) {
            ios[i] = (IO) newClones.get(i);
        }
        clones = ios;
    }

    /**
     * Check validity of array of Objects' properties.
     * @param micros an array of Objects to check.
     * @return boolean denoting whether array has objects with
     * valid properties or not
     */
    public void checkValidity(ObservableList micros)
    {
        //convert it to an array of io microinstructions
        IO[] ios = new IO[micros.size()];
        for (int i = 0; i < ios.length; i++)
            ios[i] = (IO) micros.get(i);

        //check that all names are unique and nonempty
        Validate.buffersAreWideEnough(ios);
    }



    /**
     * returns true if new micros of this class can be created.
     */
    public boolean newMicrosAreAllowed()
    {
        return (machine.getModule("registers").size() > 0 ||
                machine.getModule("registerArrays").size() > 0);
    }

    /**
     * get the ID of the corresponding help page
     * @return the ID of the page
     */
    public String getHelpPageID()
    {
        return "IO";
    }

    /**
     * updates the table by removing all the items and adding all back.
     * for refreshing the display.
     */
    public void updateTable()
    {
        name.setVisible(false);
        name.setVisible(true);
        double w =  table.getWidth();
        table.setPrefWidth(w-1);
        table.setPrefWidth(w);
    }
}