/*
 *  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.ArrayList;

import java.io.InputStream;
import java.io.IOException;

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

import com.approximatrix.draco.main.ui.MainWindow;
import com.approximatrix.draco.datamodel.Constant;
import com.approximatrix.draco.datamodel.DateObject;
import com.approximatrix.draco.datamodel.ParseDate;
import com.approximatrix.draco.datamodel.StatTableModel;
import com.approximatrix.draco.plot.ui.PlotDialog;


public class ODDatabase {
	//protected Collection entries;
	
	public static final String VARIABLE_TABLE = "variables";
	public static final String CONSTANT_TABLE = "constants";
	public static final String DESCRIPTION_TABLE = "descriptions";

	private StatTableModel model = null;
	private MainWindow master = null;
	
	private Document result = null;
	
	private int time_series_type = -1;
	private int time_series_column = -1;
	
	private boolean consider_ods_charts = false;
	
	public ODDatabase(StatTableModel model) {
		this.model = model;
	}
	
	public ODDatabase(MainWindow parent) {
		this(parent.getWindowTableModel());
		this.master = parent;
	}
	
	public ODDatabase() {
		// Just a dummy
	}
	
	public boolean loadDOMrepresentation(InputStream in) {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		
		try {
			DocumentBuilder builder = factory.newDocumentBuilder();
			result = builder.parse(in);
		} catch (SAXParseException spe) {
			return false;
		} catch (SAXException sxe) {
			// Error generated during parsing
			Exception  x = sxe;
			if (sxe.getException() != null)
				x = sxe.getException();
			x.printStackTrace();
			return false;
		} catch (ParserConfigurationException pce) {
			// Parser with specified options can't be built
			pce.printStackTrace();
			return false;
		} catch (IOException ioe) {
			ioe.printStackTrace();
			return false;
	    }
		return true;
	}
	
	public NodeList getContentTables() {
		return result.getElementsByTagName("table:table");
	}
	
	public Element getTable(String table) {
		NodeList tables = getContentTables();
		if(tables == null) return null;
		
		Element return_val = null;
		for(int i=0;i<tables.getLength();i++) {
			String this_one = ((Element)tables.item(i)).getAttribute("table:name");
			if(this_one.compareTo(table) == 0) {
				return_val = (Element)tables.item(i);
				break;
			}
		}
		return return_val;
	}
	
	public String[] getContentTableNames() {

		NodeList tables = getContentTables();
		if(tables == null) {
			printNotFound("table:table");
			return null;
		}
		
		String[] return_val = new String[tables.getLength()];
		for(int i=0;i<tables.getLength();i++) {
			return_val[i] = ((Element)tables.item(i)).getAttribute("table:name");
		}
		
		return return_val;
	}
	
	public String[] getColumnHeadings(Element table, int row) {
		NodeList rows = table.getElementsByTagName("table:table-row");
		if(rows == null) {
			System.err.println("No rows in table!");
			return null;
		}
		if(rows.getLength() < 1) {
			System.err.println("No data in table!!!");
			return null;
		}
		
		return getHeaderData((Element)rows.item(row));
	}
	
	public String[] getColumnHeadings(Element table) {
		return getColumnHeadings(table,0);
	}
	
	public Object[] getColumn(Object id, Element table) {
		
		NodeList rows = table.getElementsByTagName("table:table-row");
		if(rows == null) return null;
		if(rows.getLength() < 1) return null;
		
		int data_start_row = 1;
		int column_index = 0;
		
		if(id.getClass() == String.class) {
			String[] headers = getHeaderData((Element)rows.item(0));
			if(headers == null) return null;
			for(column_index = 0;column_index<headers.length;column_index++) {
				if(headers[column_index].compareTo((String)id) == 0) {
					break;
				}
			}
			if(column_index >= headers.length) return null;
		} else if(id.getClass() == Integer.class) {
			column_index = ((Integer)id).intValue();
			data_start_row = 0;
		}
		
		Object[] return_val = new Object[rows.getLength()-data_start_row];
		DateObject base = null;
		for(int i = data_start_row;i<rows.getLength();i++) {
			Object[] data = getRowData((Element)rows.item(i));
			
			if(column_index<data.length) {
				Object this_entry = data[column_index];
				
				// DateObjects need a base
				
				if(this_entry != null) {
					if(i == data_start_row && this_entry.getClass() == DateObject.class) {
						base = (DateObject)this_entry;
					}
				}
				
				// Store as appropriate
				if(data == null) {
					return_val[i-data_start_row] = null;
				} else {
					if(this_entry != null) {
						if(this_entry.getClass() == DateObject.class && i != data_start_row)
							((DateObject)this_entry).setBase(base);
					}
					
					return_val[i-data_start_row] = this_entry;
				}
			
			// This is used if a column number was requested that doesn't exist
			} else if(id.getClass() == Integer.class && column_index>=data.length) {
				return null;
			}
		}
		return return_val;
	}
	
	public Object[] getColumn(int column_index, Element table, int start_row, int end_row) {
		
		NodeList rows = table.getElementsByTagName("table:table-row");
		if(rows == null) return null;
		if(rows.getLength() < 1) return null;
		
		int data_start_row = start_row;
		int data_end_row = end_row;
		if(data_end_row == 0)
			data_end_row = rows.getLength()-1;
		else
			data_end_row = Math.min(data_end_row,rows.getLength()-1);
		
		Object[] return_val = new Object[(data_end_row+1)-data_start_row];
		DateObject base = null;
		for(int i = data_start_row;i<=data_end_row;i++) {
			Object[] data = getRowData((Element)rows.item(i));
			
			if(column_index<data.length) {
				Object this_entry = data[column_index];
				
				// DateObjects need a base
				if(i == data_start_row && this_entry.getClass() == DateObject.class) {
					base = (DateObject)this_entry;
				}
				
				// Store as appropriate
				if(data == null) {
					return_val[i-data_start_row] = null;
				} else {
					if(this_entry.getClass() == DateObject.class && i != data_start_row) {
						((DateObject)this_entry).setBase(base);
					}
					
					return_val[i-data_start_row] = this_entry;
				}
			
			// This is used if a column number was requested that doesn't exist
			} else if(column_index>=data.length) {
				return null;
			}
		}
		return return_val;
	}
	
	private String[] getRowStrings(Element row) {
		//NodeList cells = row.getElementsByTagName("table:cell");
		NodeList cell_contents = row.getElementsByTagName("text:p");
		if(cell_contents == null) return null;
		if(cell_contents.getLength() < 1) return null;
		
		String[] return_val = new String[cell_contents.getLength()];
		for(int i=0;i<cell_contents.getLength();i++) {
			return_val[i] = ((Element)cell_contents.item(i)).getNodeValue();
		}
		
		return return_val;
	}
	
	private String[] getHeaderData(Element row) {
		NodeList cell_contents = row.getElementsByTagName("table:table-cell");
		if(cell_contents == null) {
			System.err.println("No data on header line (1)!");
			return null;
		}
		if(cell_contents.getLength() < 1) {
			System.err.println("No data on header line (2)!");
			return null;
		}
		
		String[] return_val = new String[cell_contents.getLength()];
		for(int i=0;i<cell_contents.getLength();i++) {
			Element cell = (Element)cell_contents.item(i);
			if(cell.getAttribute("office:value-type").compareTo("string") != 0) {
				// The first row apparently does not contain headers!
				System.err.println("Wrong type on header line - no strings!");
				System.err.println(cell.getAttribute("office:value-type"));
				
				//return null;
			}
			
			NodeList cell_values = cell.getElementsByTagName("text:p");
			return_val[i] = ((Element)cell_values.item(0)).getTextContent();
			//System.out.println("-> "+return_val[i]);
			
			// Check if this is the time column
			String time_series = cell.getAttribute("table:column-data-type");
			if(time_series != null) {
				if(time_series.compareTo("time-series") == 0) {
					time_series_column = i;
					String type = cell.getAttribute("table:time-series-type");
					time_series_type = DateObject.getTypeInt(type);
					if(time_series_type == -1)
						time_series_column = -1;						
				}
			}
		}
		
		return return_val;
	}
	
	private Object[] getRowData(Element row) {
		//NodeList cells = row.getElementsByTagName("table:cell");
		//NodeList cell_contents = row.getElementsByTagName("text:p");
		NodeList cell_contents = row.getElementsByTagName("table:table-cell");
		if(cell_contents == null) return null;
		if(cell_contents.getLength() < 1) return null;
		
		Object[] return_val = new Object[cell_contents.getLength()];
		for(int i=0;i<cell_contents.getLength();i++) {
			Element one_cell = (Element)cell_contents.item(i);
			String type = one_cell.getAttribute("office:value-type");
			
			String cell_text = ((Element)one_cell.getElementsByTagName("text:p").item(0)).getTextContent();
			//System.out.println(cell_text);
			
			if(type.compareTo("date")==0) {
				return_val[i] = ParseDate.ParseTextToDate(cell_text,time_series_type);
			} else if(type.compareTo("float")==0) {
				try {
					return_val[i] = new Double(cell_text);
				} catch(NumberFormatException nfe) {
					return_val[i] = null;
				}
			} else {
				// Other types are not supported!
				// Assume its a string
				return_val[i] = new String(cell_text);
				if(((String)return_val[i]).compareTo(TableTags.NO_DATA) == 0) {
					return_val[i] = null;
				}
			}
		}
		
		return return_val;
	}
	
	public Document getDOMrepresentation() {
		return getDOMrepresentation(true);
	}
	
    public Document getDOMrepresentation(boolean include_data) {
    	
    	if(result != null) return result;
    	
        result = null;
        try {
            DocumentBuilder dbuild = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            result = dbuild.newDocument();
            Element collection = result.createElement("office:document-content");
            
            TableTags.GenerateNS(collection);

            Element el = result.createElement("office:script");
            collection.appendChild(el);

            el = result.createElement("office:automatic-styles");
            Element el2 = result.createElement("style:style");
            el2.setAttribute("style:name", "ro1");
            el2.setAttribute("style:family", "table-row");
            Element el3 = result.createElement("style.properties");
            el3.setAttribute("style:row-height", "0.1681inch");
            el3.setAttribute("fo:break-before", "auto");
            el3.setAttribute("style:use-optimal-row-height", "true");
            el2.appendChild(el3);
            el.appendChild(el2);
            el2 = result.createElement("style:style");
            el2.setAttribute("style:name", "ta1");
            el2.setAttribute("style:family", "table");
            el2.setAttribute("style:master-page-name", "Default");
            el3 = result.createElement("style:properties");
            el3.setAttribute("table:display", "true");
            el2.appendChild(el3);
            el.appendChild(el2);
            collection.appendChild(el);

            Element body = result.createElement("office:body"),
                    sprdsht = result.createElement("office:spreadsheet"),
                    table = result.createElement("table:table");
            table.setAttribute("table:name", VARIABLE_TABLE);
            table.setAttribute("table.style-name", "ta1");

            Element row = result.createElement("table:table-row");
            row.setAttribute("table:style-name", "ro1");
            
            String[] variables = model.getColumnNames();
            if(variables == null) return null;
            
            Element col = result.createElement("table:table-column");
            col.setAttribute("table:number-columns-repeated",Integer.toString(variables.length));
            table.appendChild(col);
            
            time_series_column = model.getDateColumn();
            
            // First row - variable names
            for(int i=0;i<variables.length;i++) {
            	addTableCell(result, row, variables[i]);
            	if(i == time_series_column) {
            		NodeList entries = row.getElementsByTagName("table:table-cell");
            		Element last = (Element)entries.item(entries.getLength()-1);
            		last.setAttribute("table:column-data-type","time-series");
            		time_series_type = model.getDateType(); 
            		last.setAttribute("table:time-series-type",DateObject.getTypeString(time_series_type));
            	}
            }
            table.appendChild(row);
            
            // Second row + - values
            for(int i=0;i<model.getRowCount() && include_data;i++) {
            	row = result.createElement("table:table-row");
            	row.setAttribute("table:style-name", "ro1");
            	for(int j=0;j<variables.length;j++) {
            		Number raw = model.getNumberAt(i,j);
            		//String output = NO_DATA;
            		//if(raw != null) {;
            		//	output = raw.toString();
            		//}
            		addTableCell(result,row,raw);
            	}
            	table.appendChild(row);
            }
            
            // All done with data
            sprdsht.appendChild(table);
            
            // Need to check for constants
            ArrayList model_constants = model.getConstants();
            if(model_constants.size() > 0) {
                table = result.createElement("table:table");
                table.setAttribute("table:name", CONSTANT_TABLE);
                table.setAttribute("table:style-name", "ta1");
                col = result.createElement("table:table-column");
                col.setAttribute("table:number-columns-repeated",Integer.toString(2));
                table.appendChild(col);
                for(int i=0; i<model_constants.size() && include_data;i++) {
                	row = result.createElement("table:table-row");
                	row.setAttribute("table:style-name", "ro1");
                	Constant this_one = ((Constant)model_constants.get(i));
                	addTableCell(result,row,this_one.getName());
                	addTableCell(result,row,this_one.getValue().toString());
                	table.appendChild(row);
                }
                sprdsht.appendChild(table);
            }
            
            // Descriptions where necessary
            boolean needs_writing = false;
            for(int i = 0;i<model.getColumnCount();i++) {
            	if(model.getDescription(i) != null) 
            		needs_writing = true;
            }
            if(needs_writing) {
            	table = result.createElement(TableTags.TBL_TABLE);
                table.setAttribute(TableTags.TBL_TABLE_NAME_SUBELEMENT, DESCRIPTION_TABLE);
                table.setAttribute(TableTags.TBL_TABLE_STYLE_SUBELEMENT, "ta1");
                col = result.createElement(TableTags.TBL_COL);
                col.setAttribute(TableTags.TBL_COL_REPEATED_SUBELEMENT,Integer.toString(2));
                table.appendChild(col);
                for(int i=0; i<model.getColumnCount();i++) {
                	if(model.getDescription(i) != null) {
                		row = result.createElement(TableTags.TBL_ROW);
                    	row.setAttribute(TableTags.TBL_TABLE_STYLE_SUBELEMENT, "ro1");
                    	addTableCell(result,row,model.getColumnName(i));
                    	addTableCell(result,row,model.getDescription(i));
                    	table.appendChild(row);
                	}
                	
                }
                sprdsht.appendChild(table);
            }
            
            
            // Check for charts to save
            if(master != null && consider_ods_charts) {
            	//PlotDialog[] charts = (PlotDialog[])manager.getWindowsByClass(PlotDialog.class);
            	Object[] charts = master.getWindowsByClass(PlotDialog.class);
            	if(charts != null) {
            		for(int i=0;i<charts.length;i++) {
            			table = result.createElement(TableTags.TBL_TABLE);
            			table.setAttribute(TableTags.TBL_TABLE_NAME_SUBELEMENT, ((PlotDialog)charts[i]).getTitle());
            			table.setAttribute(TableTags.TBL_TABLE_STYLE_SUBELEMENT, "ta1");
            			
            			col = result.createElement(TableTags.TBL_COL);
            			table.appendChild(col);
            			
            			row = result.createElement(TableTags.TBL_ROW);
            			
            			Element cht_cell = result.createElement(TableTags.TBL_CELL);
            			
            			Element frame = result.createElement(TableTags.TBL_DRAW_FRM);
            			Element object = result.createElement(TableTags.TBL_DRAW_OBJ);
            			object.setAttribute(TableTags.TBL_XLINK_HREF_SUBELEMENT, "./Object "+Integer.toString(i));
            			object.setAttribute(TableTags.TBL_XLINK_TYPE_SUBELEMENT, "simple");
            			object.setAttribute(TableTags.TBL_XLINK_SHOW_SUBELEMENT, "embed");
            			object.setAttribute(TableTags.TBL_XLINK_ACT_SUBELEMENT, "onLoad");
            			
            			frame.appendChild(object);
            			cht_cell.appendChild(frame);
            			row.appendChild(cht_cell);
            			table.appendChild(row);
            			sprdsht.appendChild(table);
            		}
            	}
            }
            
            body.appendChild(sprdsht);
            
            // Finish up
            collection.appendChild(body);
            result.appendChild(collection);

        } catch (Exception e) {
            System.out.println("Exception caught..." + e);
            e.printStackTrace();
        }
        return result;
    }

    protected void addTableCell(Document doc, Element parent, Object content) {
        Element cell = doc.createElement("table:table-cell"),
                text = doc.createElement("text:p");
        if(content == null) {
        	content = TableTags.NO_DATA;
        } 
        if(content.getClass() == String.class) {
        	cell.setAttribute("office:value-type","string");
        } else if(content.getClass() == Double.class || content.getClass() == Integer.class ) {
        	cell.setAttribute("office:value-type","float");
        	cell.setAttribute("office:value",content.toString());
        } else if(content.getClass() == DateObject.class) {
        	cell.setAttribute("office:value-type","date");
        	cell.setAttribute("office:value",content.toString());
        }
        Text textNode = doc.createTextNode(content.toString());
    	text.appendChild(textNode);
        cell.appendChild(text);
        parent.appendChild(cell);
    }
    
    private void printNotFound(String item) {
    	System.err.println("ODDatabase -> Element '"+item+"' not found");
    }
    
    public String[] getChartWorksheets() {
    	ArrayList return_data = new ArrayList();
    	
    	String[] tables = this.getContentTableNames();
    	NodeList table_contents = this.getContentTables();
    	if(tables == null) return null;
    	for(int i=0;i<tables.length;i++) {
    		// skip variables and constants table
    		if(tables[i].compareTo(VARIABLE_TABLE) == 0 || tables[i].compareTo(CONSTANT_TABLE) == 0 ) continue;
    		
    		// Check for graphs
    		NodeList charts = ((Element)table_contents.item(i)).getElementsByTagName(TableTags.TBL_DRAW_OBJ);
    		if(charts == null) continue;
    		if(charts.getLength() == 0) continue;
    		
    		// Save this worksheet
    		return_data.add(tables[i]);
    	}
    	
    	if(return_data.size()==0) return null;
    	
    	String[] return_array = new String[return_data.size()];
    	for(int i=0;i<return_data.size();i++) return_array[i] = (String)return_data.get(i);
    	
    	return return_array;
    }
    
    public String[] getChartObjectIDs() {
    	String[] charts = getChartWorksheets();
    	if(charts == null) return null;
    	
    	String[] ids = new String[charts.length];
    	for(int i=0;i<charts.length;i++) {
    		Element table = this.getTable(charts[i]);
    		
    		NodeList object = table.getElementsByTagName(TableTags.TBL_DRAW_OBJ);
    		
    		if(object == null) continue;
    		if(object.getLength() == 0) continue;
    		
    		ids[i] = ((Element)object.item(0)).getAttribute(TableTags.TBL_XLINK_HREF_SUBELEMENT);
    	}
    	return ids;
    }
    
    public void allowODSCharts(boolean allow) {
    	consider_ods_charts = allow;
    }
    
    public boolean isAllowODSCharts() {
    	return consider_ods_charts;
    }
}
