
package org.javafxdata.datasources.protocol;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.util.Callback;
import javax.xml.bind.DataBindingException;
import javax.xml.bind.JAXB;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.dom.DOMSource;
import org.javafxdata.datasources.io.DataSourceReader;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 *
 * @author johan
 */
public class ObjectDataSource<T> extends DataSource<T> implements ListWrapper, TableWrapper {

	// Once the TableColumns are created, they are cached
	private ObservableList<TableColumn<T,?>> tableColumns;
	private Map<String, Method> methods = new HashMap<String, Method>();
	private List<String> selectedColumnNames = new LinkedList<String>();
	private DataSourceReader reader;
	private String tag;
	private String[] cols;
	private Class x;
	private DataRetriever retriever;
	private ObservableList<T> localData = FXCollections.observableArrayList();

	public ObjectDataSource() {
	}

	public ObjectDataSource reader(DataSourceReader reader) {
		retriever = new DataRetriever(reader);
		return this;
	}

	public ObjectDataSource tag(String tag) {
		this.tag = tag;
		return this;
	}

	public ObjectDataSource clazz(Class clazz) {
		this.x = clazz;
		return this;
	}

	public ObjectDataSource columns(String... cols) {
		this.cols = cols;
		return this;
	}

	public Service retrieve() {
		retriever.start();
		return retriever;
	}

	public ObservableList<T> getData() {
		return localData;
	}
  
  @Override
      public TableColumn getNamedColumn(final String name) {
        int idx = selectedColumnNames.indexOf(name);
        if (idx > -1) {
            return getColumns().get(idx);
        }
        else {
            return new TableColumn();
        }
    }
  
	public ObjectDataSource(DataSourceReader reader, String tag, Class x, String... cols) {
		this.reader = reader;
		this.tag = tag;
		this.x = x;

		this.cols = cols;
		for (String col : cols) {
			selectedColumnNames.add(col);
		}
    parseMethods (x);
		DataRetriever retriever = new DataRetriever(reader);
		retriever.start();
	}

	public ObjectDataSource(List<T> objects, Class x, String... cols) {
		reader = null;
		tag = null;
		this.cols = cols;
		this.x = x;
		for (String col : cols) {
			selectedColumnNames.add(col);
		}
		int i = 0;
		for (T object : objects) {
			localData.add(object);
		}
    parseMethods(x);
  }
  
  private void parseMethods(Class x) {
		// parse getter methods
		Method[] xMethods = x.getMethods();
		for (Method xMethod : xMethods) {
			if (xMethod.getName().startsWith("get")
					&& xMethod.getParameterTypes().length == 0
					&& !void.class.equals(xMethod.getReturnType())) {
				String shortenedMethodName = xMethod.getName().substring(3, 4).toLowerCase() + xMethod.getName().substring(4);
				methods.put(shortenedMethodName, xMethod);
			}
		}
	}

	public String getMyElement(T row, String columnName) {
		//   DataRow<T> row = get(rowIndex);
		if (row != null) {
			Method m = methods.get(columnName);
     	if (m != null) {
				try {
					return m.invoke(row).toString();
				} catch (IllegalAccessException ex) {
					Logger.getLogger(ObjectDataSource.class.getName()).log(Level.SEVERE, null, ex);
				} catch (IllegalArgumentException ex) {
					Logger.getLogger(ObjectDataSource.class.getName()).log(Level.SEVERE, null, ex);
				} catch (InvocationTargetException ex) {
					Logger.getLogger(ObjectDataSource.class.getName()).log(Level.SEVERE, null, ex);
				}
			}
		}
		return null;
	}

	@Override
	public ObservableList<TableColumn<T,?>> getColumns() {
		if (selectedColumnNames == null) {
			return FXCollections.emptyObservableList();
		}

		if (tableColumns != null) {
			return tableColumns;
		}

		// build a tableColumn for each selectedColumnName
		tableColumns = FXCollections.observableArrayList();
		for (final String columnName : selectedColumnNames) {
			TableColumn<T,String> tc = new TableColumn<T, String>(columnName);

			tc.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<T, String>, ObservableValue<String>>() {

				@Override
				public ObservableValue<String> call(CellDataFeatures<T, String> cdf) {
					T myRow = (T) cdf.getValue();
					return (ObservableValue<String>) new ReadOnlyStringWrapper(getMyElement(myRow, columnName));
				}
			});

			tableColumns.add(tc);


		}
    System.out.println("I will return "+tableColumns);
		return tableColumns;
	}

	class DataRetriever extends Service {

		final DataSourceReader myReader;

		public DataRetriever(DataSourceReader reader) {
			this.myReader = reader;
		}

		@Override
		protected Task createTask() {
			Task task = new Task() {

				@Override
				protected Object call() throws Exception {
					try {
						InputStream is = myReader.getInputStream();
						DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

						DocumentBuilder db = dbf.newDocumentBuilder();
						Document doc = db.parse(is);
						NodeList childNodes = doc.getElementsByTagName(tag);
						int count = childNodes.getLength();
						for (int i = 0; i < count; i++) {
							Node node = childNodes.item(i);
							DOMSource source = new DOMSource(node);
							final T entry = (T) JAXB.unmarshal(source, x);
							Platform.runLater(new Runnable() {

								@Override
								public void run() {
									localData.add(entry);
								}
							});
						}
					} catch (SAXException ex) {
						Logger.getLogger(ObjectDataSource.class.getName()).log(Level.SEVERE, null, ex);
					} catch (IOException ex) {
						Logger.getLogger(ObjectDataSource.class.getName()).log(Level.SEVERE, null, ex);
					} catch (ParserConfigurationException ex) {
						Logger.getLogger(ObjectDataSource.class.getName()).log(Level.SEVERE, null, ex);
					} catch (DataBindingException ex) {
						Logger.getLogger(ObjectDataSource.class.getName()).log(Level.SEVERE, null, ex);
					} catch (Exception e) {
						System.out.println("Major exception: ");
						e.printStackTrace();
					}
					return this;
				}
			};
			return task;
		}
	}
}
