/*
 *  Draco Econometrics
 *  Copyright (C) 2006-2007 Approximatrix, LLC
 *  http://www.approximatrix.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 3 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, see <http://www.gnu.org/licenses/>.
 */

package com.approximatrix.draco.io.XMLio;

import java.util.Iterator;
import java.util.Map;
import java.util.ArrayList;
import java.awt.Color;
import java.beans.PropertyVetoException;


// Openchart includes

import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;  
import org.xml.sax.SAXParseException;

import com.approximatrix.charting.Axis;
import com.approximatrix.charting.coordsystem.CoordSystem;
import com.approximatrix.charting.model.ChartDataModel;
import com.approximatrix.charting.model.MultiScatterDataModel;
import com.approximatrix.charting.render.AbstractChartRenderer;
import com.approximatrix.charting.render.BarChartRenderer;
import com.approximatrix.charting.render.MultiScatterChartRenderer;
import com.approximatrix.charting.render.RowColorModel;
import com.approximatrix.charting.swing.AbstractChartPanel;
import com.approximatrix.draco.plot.ui.DataPlot;
import com.approximatrix.draco.plot.ui.GenericPlotInterface;
import com.approximatrix.draco.plot.ui.HistogramPlot;
import com.approximatrix.draco.plot.ui.PlotDialog;
import com.approximatrix.draco.application.ChildFrameRegistrar;

public class PlotIO {

	public static void SavePlot(Element parent, PlotDialog plot_window) {
		Document base = parent.getOwnerDocument();
		
		Element plot = base.createElement(PlotTags.PLOT_ELEMENT);
		plot.setAttribute(PlotTags.PLOT_NAME_SUBELEMENT, plot_window.getTitle());
		
		AbstractChartPanel panel = plot_window.getPlotter().getPlotPanel();
		
		// Title
		String text = panel.getTitle().getText();
		if(text != null) {
			Element title = base.createElement(PlotTags.PLOT_TITLE_ELEMENT);
			title.appendChild(base.createTextNode(text));
			plot.appendChild(title);
		}
		
		// Retrieve the Axes from the plot
		CoordSystem coords = panel.getCoordSystem();
		
		// X Axis
		Axis x = coords.getXAxis();
		if(x != null) {
			Element axis = base.createElement(PlotTags.PLOT_AXIS_ELEMENT);
			axis.setAttribute(PlotTags.PLOT_AXIS_DIRECTION_SUBELEMENT, "x");
			
			// Scaling
			if(panel.getChartDataModel().isAutoScale()) 
				axis.setAttribute(PlotTags.PLOT_AXIS_SCALING_SUBELEMENT, "auto");
			else {
				axis.setAttribute(PlotTags.PLOT_AXIS_SCALING_SUBELEMENT, "manual");
				double manual_max = panel.getChartDataModel().getManualMaximumColumnValue(); 
				double manual_min = panel.getChartDataModel().getManualMinimumColumnValue(); 
				if(!panel.getChartDataModel().isColumnNumeric()) {
					manual_min = 0;
					manual_max = 1;
					for(int i=0;i<panel.getChartDataModel().getDataSetNumber();i++)
						manual_max = Math.max(manual_max,panel.getChartDataModel().getDataSetLength(i));
				}
					
				Element scaling = base.createElement(PlotTags.PLOT_AXIS_SCALE_ELEMENT);
				scaling.setAttribute(PlotTags.PLOT_AXIS_SCALE_START_SUBELEMENT, new Double(manual_min).toString());
				scaling.setAttribute(PlotTags.PLOT_AXIS_SCALE_STOP_SUBELEMENT, new Double(manual_max).toString());
				axis.appendChild(scaling);
			}
			
			// Label
			Element label = base.createElement(PlotTags.PLOT_AXIS_LABEL_ELEMENT);
			label.appendChild(base.createTextNode(coords.getXAxisUnit()));
			axis.appendChild(label);
			
			plot.appendChild(axis);
		}
		
		// Y Axis
		Axis y = coords.getXAxis();
		if(y != null) {
			Element axis = base.createElement(PlotTags.PLOT_AXIS_ELEMENT);
			axis.setAttribute(PlotTags.PLOT_AXIS_DIRECTION_SUBELEMENT, "y");
			
			// Scaling
			if(panel.getChartDataModel().isAutoScale()) 
				axis.setAttribute(PlotTags.PLOT_AXIS_SCALING_SUBELEMENT, "auto");
			else {
				
				Number maximum = panel.getChartDataModel().getManualMaximumValue();
				Number minimum = panel.getChartDataModel().getManualMinimumValue();
				
				if(maximum == null || minimum == null) {
					//axis.setAttribute(PlotTags.PLOT_AXIS_SCALING_SUBELEMENT, "auto");
					
					for(int i=0;i<panel.getChartDataModel().getDataSetNumber();i++) {
						Number min_temp = panel.getChartDataModel().getChartDataModelConstraints(i).getMinimumValue();
						Number max_temp = panel.getChartDataModel().getChartDataModelConstraints(i).getMaximumValue();
						
						if(maximum == null)
							maximum = max_temp;
						else if(max_temp != null)
							maximum = new Double(Math.max(max_temp.doubleValue(), maximum.doubleValue()));
						
						if(minimum == null)
							minimum = min_temp;
						else if(min_temp != null)
							minimum = new Double(Math.min(min_temp.doubleValue(), minimum.doubleValue()));
						
					}
					
				} 
				
				if(maximum == null || minimum == null) 
					axis.setAttribute(PlotTags.PLOT_AXIS_SCALING_SUBELEMENT, "auto");
				else { 
				
					axis.setAttribute(PlotTags.PLOT_AXIS_SCALING_SUBELEMENT, "manual");

					Element scaling = base.createElement(PlotTags.PLOT_AXIS_SCALE_ELEMENT);
					scaling.setAttribute(PlotTags.PLOT_AXIS_SCALE_START_SUBELEMENT, minimum.toString());
					scaling.setAttribute(PlotTags.PLOT_AXIS_SCALE_STOP_SUBELEMENT, maximum.toString());
					axis.appendChild(scaling);
				}
			}
			
			// Label
			Element label = base.createElement(PlotTags.PLOT_AXIS_LABEL_ELEMENT);
			label.appendChild(base.createTextNode(coords.getYAxisUnit()));
			axis.appendChild(label);
			
			plot.appendChild(axis);
		}
		
		// Legend
		Element legend = base.createElement(PlotTags.PLOT_LEGEND_ELEMENT);
		if(panel.getLegend() != null)
			legend.setAttribute(PlotTags.PLOT_LEGEND_ENABLE_SUBELEMENT, "true");
		else
			legend.setAttribute(PlotTags.PLOT_LEGEND_ENABLE_SUBELEMENT, "false");
		plot.appendChild(legend);
		
		// The actual plot information - Cycle through all renders
		Map renderers = panel.getChartRenderer();
        for(Iterator<AbstractChartRenderer>iter = renderers.values().iterator();iter.hasNext();) {
        	AbstractChartRenderer now = iter.next();
        	if(now != null) {
        		
        		Element render = base.createElement(PlotTags.PLOT_RENDER_ELEMENT);
        		if(now.getClass().isAssignableFrom(MultiScatterChartRenderer.class))
        			render.setAttribute(PlotTags.PLOT_RENDER_TYPE_SUBELEMENT, PlotTags.PLOT_SCATTER_TYPE);
                else if(now.getClass().isAssignableFrom(BarChartRenderer.class))
        			render.setAttribute(PlotTags.PLOT_RENDER_TYPE_SUBELEMENT,PlotTags.PLOT_BAR_TYPE);
                else
                	render.setAttribute(PlotTags.PLOT_RENDER_TYPE_SUBELEMENT, PlotTags.PLOT_SCATTER_TYPE);
        		
        		ChartDataModel model = now.getChartDataModel();
                render.setAttribute(PlotTags.PLOT_RENDER_SERIES_COUNT_SUBELEMENT, new Integer(model.getDataSetNumber()).toString());
                
                // Add the data series now
                for(int j=0;j<model.getDataSetNumber();j++) {
                	Element series = base.createElement(PlotTags.PLOT_SERIES_ELEMENT);
                	series.setAttribute(PlotTags.PLOT_SERIES_LABEL_SUBELEMENT, model.getDataSetName(j));
                	
                	// Series handling changes between types somewhat
                	if(model.getClass().isAssignableFrom(MultiScatterDataModel.class)) {
                		MultiScatterDataModel ms_model = (MultiScatterDataModel)model;
                		
                		if(ms_model.getSeriesMarker(j)) {
                			series.setAttribute(PlotTags.PLOT_SERIES_MARKER_ENABLE_SUBELEMENT, "true");
                			
                			Element marker = base.createElement(PlotTags.PLOT_SERIES_MARKER_ELEMENT);
                			marker.setAttribute(PlotTags.PLOT_SERIES_MARKER_SIZE_SUBELEMENT, "auto");
                			marker.setAttribute(PlotTags.PLOT_SERIES_MARKER_TYPE_SUBELEMENT, "auto");
                			
                			series.appendChild(marker);
                		} else
                			series.setAttribute(PlotTags.PLOT_SERIES_MARKER_ENABLE_SUBELEMENT, "false");
                		
                		if(ms_model.getSeriesLine(j)) 
                			series.setAttribute(PlotTags.PLOT_SERIES_LINE_ENABLE_SUBELEMENT, "true");
                		else
                			series.setAttribute(PlotTags.PLOT_SERIES_LINE_ENABLE_SUBELEMENT, "false");
                		
                		
                	}
                	
                	// Color - all graphs use this
                	if(j < now.getRowColorModel().getRowCount())
                		series.setAttribute(PlotTags.PLOT_SERIES_COLOR_SUBELEMENT, Integer.toHexString(now.getRowColorModel().getColor(j).getRGB()));
                	
                	// Now the data table is needed for this series
                	Element data = base.createElement(PlotTags.PLOT_SERIES_DATA_ELEMENT);
                	data.setAttribute(PlotTags.PLOT_SERIES_DATA_LENGTH_SUBELEMENT, new Integer(model.getDataSetLength(j)).toString());
                	Object[][] series_data = new Object[model.getDataSetLength(j)][2];
                	for(int bd = 0;bd<series_data.length;bd++) {
                		series_data[bd][0] = model.getColumnValueAt(j, bd);
                		series_data[bd][1] = model.getValueAt(j, bd);
                	}
                	Element table = XMLTableUtilities.CreateTable(base,series_data);
                	data.appendChild(table);
                	series.appendChild(data);
                	
                	render.appendChild(series);
                }
        		
                plot.appendChild(render);
        	}
        }
		
		parent.appendChild(plot);
	}
	
	public static void BuildPlot(Element plot_node, ChildFrameRegistrar parent) {
		
		// First access the render and its type
		NodeList renderers = plot_node.getElementsByTagName(PlotTags.PLOT_RENDER_ELEMENT);
		if(renderers == null) return;
		if(renderers.getLength() == 0) return;
		
		GenericPlotInterface plot = null;
		
		// Assume only the first renderer for now
		for(int i=0;i<renderers.getLength() && i<1;i++) {
			
			Element render = (Element)renderers.item(i);
			String type = render.getAttribute(PlotTags.PLOT_RENDER_TYPE_SUBELEMENT);
			
			// Build the specific plot
			if(type.compareTo(PlotTags.PLOT_BAR_TYPE)==0) {
				plot = BuildBar(render);
			} else if(type.compareTo(PlotTags.PLOT_SCATTER_TYPE)==0) {
				plot = BuildScatter(render);
			}
			if(plot == null) return;
			
			// Need to apply all the other information including labels and whatnot
			AbstractChartPanel panel = plot.getPlotPanel();
			
			// Title - only one supported for now
			NodeList titles = plot_node.getElementsByTagName(PlotTags.PLOT_TITLE_ELEMENT);
			if(titles != null) {
				for(int titi = 0;titi<titles.getLength() && titi < 1;titi++) {
					Element title = (Element)titles.item(titi);
					panel.getTitle().setText(title.getTextContent());
				}
			}
			
			// Legend enabling
			NodeList legends = plot_node.getElementsByTagName(PlotTags.PLOT_LEGEND_ELEMENT);
			if(titles != null) {
				if(legends.getLength() > 0) {
					Element legend = (Element)legends.item(0);
					String attr = legend.getAttribute(PlotTags.PLOT_LEGEND_ENABLE_SUBELEMENT);
					if(attr != null) {
						if(attr.compareTo("true")==0)
							panel.setLegend(new com.approximatrix.charting.Legend(GetRowColorModel(panel.getChartRenderer())));
						else
							panel.setLegend(null);
					}
				}
			}
			
			// Axes
			NodeList axes = plot_node.getElementsByTagName(PlotTags.PLOT_AXIS_ELEMENT);
			if(axes != null) {
				
				double start_scale_dbl = 0.0;
				double stop_scale_dbl = 0.0;
				
				for(int axi = 0;axi<axes.getLength();axi++) {
					Element axis = (Element)axes.item(axi);
					
					// Autoscaling
					String attr = axis.getAttribute(PlotTags.PLOT_AXIS_SCALING_SUBELEMENT);
					boolean autoscale = true;
					
					if(attr != null)
						autoscale = (attr.compareTo("auto")==0);
					
					// Scaling data
					if(!autoscale) {
						NodeList scaling = axis.getElementsByTagName(PlotTags.PLOT_AXIS_SCALE_ELEMENT);
						if(scaling == null)
							autoscale = true;
						else if(scaling.getLength() == 0)
							autoscale = true;
						else {
							Element scale = (Element) scaling.item(0);
							
							String start_scale = scale.getAttribute(PlotTags.PLOT_AXIS_SCALE_START_SUBELEMENT);
							String stop_scale = scale.getAttribute(PlotTags.PLOT_AXIS_SCALE_STOP_SUBELEMENT);
							
							try {
								start_scale_dbl = new Double(start_scale).doubleValue();
								stop_scale_dbl = new Double(stop_scale).doubleValue();
							} catch(NullPointerException npe) {
								autoscale = true;
							} catch(NumberFormatException nfe) {
								autoscale = true;
							}
						}
					}
					
					// Label
					NodeList labels = axis.getElementsByTagName(PlotTags.PLOT_AXIS_LABEL_ELEMENT);
					String label = null;
					if(labels != null) {
						if(labels.getLength()>0)
							label = ((Element)labels.item(0)).getTextContent();
					}
					
					// Get the direction to see how this is all applied
					String dir = axis.getAttribute(PlotTags.PLOT_AXIS_DIRECTION_SUBELEMENT);
					if(dir == null)
						continue;
					
					if(dir.compareToIgnoreCase("x")==0) {
						panel.getCoordSystem().setXAxisUnit(label);
						panel.getChartDataModel().setAutoScale(autoscale);
						panel.getChartDataModel().setManualScale(!autoscale);
						if(!autoscale) {
							panel.getChartDataModel().setMaximumColumnValue(stop_scale_dbl);
							panel.getChartDataModel().setMinimumColumnValue(start_scale_dbl);
						}
						
					} else if(dir.compareToIgnoreCase("y")==0) {
						panel.getCoordSystem().setYAxisUnit(label);
						panel.getChartDataModel().setAutoScale(autoscale);
						panel.getChartDataModel().setManualScale(!autoscale);
						if(!autoscale) {
							panel.getChartDataModel().setMaximumValue(new Double(stop_scale_dbl));
							panel.getChartDataModel().setMinimumValue(new Double(start_scale_dbl));
						}
					}
					
				}
			
				// This renderer is now built and saved in plot as a GenericPlotInterface.
				// This routine would need to be fancied up for more impressive overlays
				// (e.g.bars and lines together).
			}
		}
		
		// Finally, we need a name
		String name = plot_node.getAttribute(PlotTags.PLOT_NAME_SUBELEMENT);
		if(name == null)
			name = "Plot";
		
		PlotDialog temp = (PlotDialog)parent.openWindow(new PlotDialog(name,plot));
		
		try {
			temp.setIcon(true);
		} catch(PropertyVetoException pve) {
			System.err.println("Can't iconify: "+temp.getTitle());
			pve.printStackTrace();
		}
	}
	
	public static GenericPlotInterface BuildBar(Element render_node) {
		
		HistogramPlot return_val = null;
		
		// Extract the necessary data from the render element
		int series_count = 0;
		try {
			series_count = new Integer(render_node.getAttribute(PlotTags.PLOT_RENDER_SERIES_COUNT_SUBELEMENT)).intValue();
		} catch(NumberFormatException nfe) {
			return null;
		}
		
		NodeList series = render_node.getElementsByTagName(PlotTags.PLOT_SERIES_ELEMENT);
		if(series == null) return null;
		
		// Right now, bar charts only support one series, but this is
		// present for future expansion
		for(int i=0;i<series.getLength() && i < series_count;i++) {
			
			HistogramPlot holding = null;
			
			Element one_set = (Element)series.item(i);
			
			// Get the data element
			NodeList data_elements = one_set.getElementsByTagName(PlotTags.PLOT_SERIES_DATA_ELEMENT);
			if(data_elements == null) continue; // Nothing in this data element
			
			// Only one data element per series is allowed
			Element data = (Element)data_elements.item(0);
			
			// Need to extract a table if present
			NodeList tables = data.getElementsByTagName(TableTags.TBL_TABLE);
			if(tables == null) {
				// Handling for live graphs needs to be added here in the future
			} else {
				// Again, only one table should be present
				Element table = (Element) tables.item(0);
				
				Object[][] table_data = XMLTableUtilities.ReadTableData(table, false);
				
				holding = new HistogramPlot(table_data);
				
				// Set the color for this data series
				String attr = one_set.getAttribute(PlotTags.PLOT_SERIES_COLOR_SUBELEMENT);
				
				if(attr != null) {
					Map renderers = holding.getPlotPanel().getChartRenderer();
			        for(Iterator<AbstractChartRenderer>iter = renderers.values().iterator();iter.hasNext();) {
			        	AbstractChartRenderer now = iter.next();
			        	
			        	// There should be only one active chart renderer (for now)
			        	if(now != null) {
			        		Color setting = null;
			        		//System.out.println(attr);
			        		try {
			        			//setting = new Color(Integer.parseInt(attr.toUpperCase(), 16),true);
			        			int[] components = getRGBAFromString(attr);
			        			setting = new Color(components[0], components[1], components[2], components[3]);
			        			
			        		} catch (NumberFormatException nfe) {
			        			setting = Color.getColor(attr);
			        			System.out.println("Error...");
			        		}
			        		if(setting != null) {
			        			System.out.println(i);
			        			now.getRowColorModel().setColor(i, setting);
			        		}
			        			
			        	}
			        }
				}
				
			}
			
			// Check for multiple series
			if(return_val == null) {
				return_val = holding;
			} else {
				// Overlay the next plot - difficult...
			}	
			
		}
		
		return (GenericPlotInterface)return_val;
		
	}
	
	public static GenericPlotInterface BuildScatter(Element render_node) {
		DataPlot return_val = null;
		
		// Extract the necessary data from the render element
		int series_count = 0;
		try {
			series_count = new Integer(render_node.getAttribute(PlotTags.PLOT_RENDER_SERIES_COUNT_SUBELEMENT)).intValue();
		} catch(NumberFormatException nfe) {
			return null;
		}
		
		// Declare the new ArrayList for passing data to the DataPlot class
		ArrayList all_data = new ArrayList(series_count);
		String[] names = new String[series_count];
		
		NodeList series = render_node.getElementsByTagName(PlotTags.PLOT_SERIES_ELEMENT);
		if(series == null) return null;
		
		// Build the data only in this loop.  Attributes can be set later
		for(int i=0;i<series.getLength() && i < series_count;i++) {
			
			Element one_set = (Element)series.item(i);
			
			// Get the data element
			NodeList data_elements = one_set.getElementsByTagName(PlotTags.PLOT_SERIES_DATA_ELEMENT);
			if(data_elements == null) continue; // Nothing in this data element
			
			// Only one data element per series is allowed
			Element data = (Element)data_elements.item(0);
			
			// Need to extract a table if present
			NodeList tables = data.getElementsByTagName(TableTags.TBL_TABLE);
			if(tables == null) {
				// Handling for live graphs needs to be added here in the future
			} else {
				// Again, only one table should be present
				Element table = (Element) tables.item(0);
				
				double[][] table_data = XMLTableUtilities.ReadTableDataDouble(table, false);
				if(table_data != null)
					all_data.add(table_data);

			}
			
			names[i] = one_set.getAttribute(PlotTags.PLOT_SERIES_LABEL_SUBELEMENT);
		}

		// Generate the data plot
		return_val = new DataPlot(all_data,names);
		
		// Set some attributes now
		MultiScatterDataModel model = (MultiScatterDataModel)return_val.getPlotPanel().getChartDataModel();
		for(int i=0;i<series.getLength() && i<model.getDataSetNumber();i++) {
			Element one_set = (Element)series.item(i);
			
			// Check for line drawing
			String attr = one_set.getAttribute(PlotTags.PLOT_SERIES_LINE_ENABLE_SUBELEMENT);
			if(attr != null) {
				if(attr.compareTo("true")==0)
					model.setSeriesLine(names[i], true);
				else
					model.setSeriesLine(names[i], false);
			}
			
			// Check for marker drawing
			attr = one_set.getAttribute(PlotTags.PLOT_SERIES_MARKER_ENABLE_SUBELEMENT);
			if(attr != null) {
				if(attr.compareTo("true")==0)
					model.setSeriesMarker(names[i], true);
				else
					model.setSeriesMarker(names[i], false);
			}
			
			// Color drawing is more difficult
			attr = one_set.getAttribute(PlotTags.PLOT_SERIES_COLOR_SUBELEMENT);
			if(attr != null) {
				
				Map renderers = return_val.getPlotPanel().getChartRenderer();
				
				Color setting = null;
        		try {
        			setting = new Color(Integer.parseInt(attr, 16),true);
        		} catch (NumberFormatException nfe) {
        			setting = Color.getColor(attr);
        		}
        		if(setting != null)
        			GetRowColorModel(renderers).setColor(i, setting);
			}
			
			
			// Note: as of right now, no other attributes are required or can be used
			// by the plot algorithm.  This will need upgrading in the future.
		}
		
		return return_val;
	}
	
	/** Returns the first non-null color model from the char renderer
	 * 
	 * @param renderers the renderers to search
	 * @return the first color model in the renderer
	 */
	private static RowColorModel GetRowColorModel(Map renderers) {
		for(Iterator<AbstractChartRenderer>iter = renderers.values().iterator();iter.hasNext();) {
        	AbstractChartRenderer now = iter.next();
        	
        	// There should be only one active chart renderer (for now)
        	if(now != null) {
        		return now.getRowColorModel();
        	}
        }
		return null;
	}
	
	private static int[] getRGBAFromString(String value) {
		String alpha = value.substring(0, 2);
		String r = value.substring(2, 4);
		String g = value.substring(4, 6);
		String b = value.substring(6, 8);
		
		int[] return_val = new int[4];
		return_val[0] = Integer.parseInt(r, 16);
		return_val[1] = Integer.parseInt(g, 16);
		return_val[2] = Integer.parseInt(b, 16);
		return_val[3] = Integer.parseInt(alpha, 16);
		
		return return_val;
	}
	
}
