/*
 *  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.io.*;
import java.util.zip.*;
import org.w3c.dom.*;

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.general.ui.InfoWindow;
import com.approximatrix.draco.io.ImportExport;
import com.approximatrix.draco.plot.ui.PlotDialog;
import com.approximatrix.draco.main.ui.MainWindow;

import java.util.ArrayList;
import java.beans.PropertyVetoException;

public class LoadWorksheet {
	
	private static final String DEFAULT_COLUMN_TITLE = "ser";
	
	private String filename = null;
	private MainWindow parent = null;
	
	//private String variables_worksheet = ODDatabase.VARIABLE_TABLE;
	//private String constants_worksheet = ODDatabase.CONSTANT_TABLE;
	
	private ODDatabase document = null;
	private AnalysisDatabase analyses = null;
	private TextDatabase texts = null;
	private PlotDatabase plots = null;
	
	private ODSChartDatabase[] charts = null;
	
	private StatTableModel loaded_model = null;
	
	private String error_string = null;
	
	private boolean using_quickload = false;
	
	private boolean allow_ODS_charts = false;
	
	public LoadWorksheet(String filename, MainWindow parent) {
		this.filename = filename;
		this.parent = parent;
	}
	
	public String getError() {
		return error_string;
	}
	
	public boolean openIntoMemory() {
		return loadDocument();
	}
	
	public boolean createdByEasy() {
		boolean return_val = false;
		
		String[] table_names = getTables();
		
		if(table_names == null) return false;
		
		for(int i=0;i<table_names.length;i++) {
			if(table_names[i].compareTo(ODDatabase.VARIABLE_TABLE)==0) {
				return_val = true;
				break;
			}
		}
		
		return return_val;
	}
	
	public boolean load(String worksheet, int header_row, int start_row, int end_row) {
		boolean return_val = true;
		
		// Interupt this loader if quick loading is available
		if(using_quickload) {
			return loadQuick(worksheet);
		}
		
		String[] table_names = getTables();
		if(table_names == null)
			return false;
		
		Element variables = document.getTable(worksheet);
		if(variables == null) {
			System.err.println("WORKSHEET "+worksheet+" NOT FOUND!!!");
			return false;
		}
		
		String[] headings = null;
		if(header_row >= 0) {
			headings = document.getColumnHeadings(variables,header_row);
			if(headings == null) {
				System.err.println("NO HEADERS FOUND!!!");
				return false;
			}
		} 
		
		StatTableModel new_model = null;
		int column_counter = 0;
		Object[] column = document.getColumn(column_counter,variables,start_row,end_row);
		while(column != null) {
			
			String this_heading = DEFAULT_COLUMN_TITLE+Integer.toString(column_counter);
			if(headings != null) {
				if(column_counter<headings.length) {
					this_heading = headings[column_counter];
				}
			}
			
			column_counter++;
			
			if(loaded_model == null) {
				loaded_model = new StatTableModel(column,this_heading);
			} else {
				loaded_model.addColumn(column,this_heading);
			}
			column = document.getColumn(new Integer(column_counter),variables,start_row,end_row);
		}
		
		return return_val;
	}
	
	public boolean load() {
		boolean return_val = true;

		if(!createdByEasy()) return false;
		
		// Interupt this loader if quick loading is available
		if(using_quickload) {
			return_val = loadQuick(ODDatabase.VARIABLE_TABLE);
			return loadQuick(ODDatabase.CONSTANT_TABLE);
		} else {



			Element variables = document.getTable(ODDatabase.VARIABLE_TABLE);

			if(variables == null) {
				System.err.println("NO VARIABLES FOUND!!!");
				error_string = "No variables found in specified file";
				return false;
			}

			String[] headings = document.getColumnHeadings(variables);

			if(headings == null) {
				System.err.println("NO HEADERS FOUND!!!");
				error_string = "No variable names found in specified file";
				return false;
			}

			//System.out.print("Retrieved Headers (0 =");
			//System.out.print(headings[0]);
			//System.out.println(")");

			StatTableModel new_model = null;

			for(int i=0;i<headings.length;i++) {

				//System.out.print("Loading Column: "+headings[i]+" -> ");

				Object[] column = document.getColumn(headings[i],variables);

				//if(column != null) {
				//	System.out.print(column.length);
				//	System.out.println(" entries");
				//} else {
				//	System.out.println("NO DATA");
				//}

				if(new_model == null) {
					new_model = new StatTableModel(column,headings[i]);
				} else {
					new_model.addColumn(column,headings[i]);
				}

			}

			// Try to load the constants
			Element constants = document.getTable(ODDatabase.CONSTANT_TABLE);
			if(constants != null) {
				Object[] names = document.getColumn(new Integer(0),constants);
				Object[] values = document.getColumn(new Integer(1),constants);
				for(int i=0;i<Math.min(names.length,values.length);i++) {
					new_model.getConstants().add(new Constant((String)names[i],(Number)values[i]));				
				}
			}
			
			loaded_model = new_model;
		}
		
		// Try loading descriptions
		Element descriptions = document.getTable(ODDatabase.DESCRIPTION_TABLE);
		if(descriptions != null) {
			Object[] names = document.getColumn(new Integer(0),descriptions);
			Object[] descs = document.getColumn(new Integer(1),descriptions);
			for(int i=0;i<Math.min(names.length,descs.length);i++) {
				loaded_model.setDescription((String)names[i], (String)descs[i]);
			}
		}
		
		// Set the new model to be the current model
		parent.setWindowTableModel(loaded_model, filename);
		
		return return_val;
	}
	
	public StatTableModel getLoadedModel() {
		return loaded_model;
	}
	
	private boolean loadDocument() {

		ZipInputStream in = openStream(new File(filename));

		ZipEntry current = getEntry(SaveWorksheet.QUICK_DATA_FILE,in);
		using_quickload = (current != null);
		try {
			in.close();
		} catch(IOException ioe) {

		}
		if(using_quickload) return true;


		// Data ------------------------------------------------------
		in = openStream(new File(filename));
		File content = getDecompressedEntry("content.xml",in);
		if(content == null) {
			System.err.println("Problem with content.xml decompress");
			error_string = "Problem accessing document content";
			return false;
		}

		try {
			in.close();
		} catch(IOException ioe) {
			error_string = "IO error encountered";
			ioe.printStackTrace();
		}

		document = new ODDatabase();
		try {
			FileInputStream fin = new FileInputStream(content);
			boolean result = document.loadDOMrepresentation(fin);
			fin.close();
			content.delete();

			if(!result) {
				error_string = "Error parsing and loading data";
				return false;
			}

		} catch(java.lang.OutOfMemoryError memory_error) {
			System.err.println("Failed to create document in memory!");
			error_string = "Out of memory while loading document";
			memory_error.printStackTrace();
			return false;
		} catch(IOException ioe) {
			error_string = "IO error encountered";
			ioe.printStackTrace();
			return false;
		}

		// Analyses --------------------------------------------------
		in = openStream(new File(filename));
		content = getDecompressedEntry(AnalysisTags.ANALYSIS_FILENAME,in);
		try {
			in.close();
		} catch(IOException ioe) {
			error_string = "IO error encountered";
			ioe.printStackTrace();
		}

		// It's ok if the analysis.xml file is not present.  Do not fret...
		if(content != null) {

			// However, once we're here, cause an error condition if the analysis
			// is msessed up			

			analyses = new AnalysisDatabase();
			try {
				FileInputStream fin = new FileInputStream(content);
				boolean result = analyses.loadDOMrepresentation(fin);
				fin.close();
				content.delete();

				if(!result) {
					error_string = "Error parsing and loading analyses";
					analyses = null;
					return false;
				}

			} catch(java.lang.OutOfMemoryError memory_error) {
				System.err.println("Failed to create document in memory!");
				error_string = "Out of memory while loading document";
				memory_error.printStackTrace();
				analyses = null;
				return false;
			} catch(IOException ioe) {
				error_string = "IO error encountered";
				ioe.printStackTrace();
				analyses = null;
				return false;
			}
		}

		// Text --------------------------------------------------
		in = openStream(new File(filename));
		content = getDecompressedEntry(TextTags.TEXT_FILENAME,in);
		try {
			in.close();
		} catch(IOException ioe) {
			error_string = "IO error encountered";
			ioe.printStackTrace();
		}

		// It's ok if the text.xml file is not present.  Do not fret...
		if(content != null) {

			// However, once we're here, cause an error condition if the analysis
			// is msessed up			

			texts = new TextDatabase();
			try {
				FileInputStream fin = new FileInputStream(content);
				boolean result = texts.loadDOMrepresentation(fin);
				fin.close();
				content.delete();

				if(!result) {
					error_string = "Error parsing and loading text windows";
					analyses = null;
					return false;
				}

			} catch(java.lang.OutOfMemoryError memory_error) {
				System.err.println("Failed to create document in memory!");
				error_string = "Out of memory while loading document";
				memory_error.printStackTrace();
				analyses = null;
				return false;
			} catch(IOException ioe) {
				error_string = "IO error encountered";
				ioe.printStackTrace();
				analyses = null;
				return false;
			}
		}
		
		//	Plots -------------------------------------------------
		in = openStream(new File(filename));
		content = getDecompressedEntry(PlotTags.PLOT_FILENAME,in);
		try {
			in.close();
		} catch(IOException ioe) {
			error_string = "IO error encountered";
			ioe.printStackTrace();
		}

		// It's ok if the plots.xml file is not present.  Do not fret...
		if(content != null) {

			// However, once we're here, cause an error condition if the plots
			// are msessed up			

			plots = new PlotDatabase();
			try {
				FileInputStream fin = new FileInputStream(content);
				boolean result = plots.loadDOMrepresentation(fin);
				fin.close();
				content.delete();

				if(!result) {
					error_string = "Error parsing and loading plot windows";
					analyses = null;
					return false;
				}

			} catch(java.lang.OutOfMemoryError memory_error) {
				System.err.println("Failed to create document in memory!");
				error_string = "Out of memory while loading document";
				memory_error.printStackTrace();
				analyses = null;
				return false;
			} catch(IOException ioe) {
				error_string = "IO error encountered";
				ioe.printStackTrace();
				analyses = null;
				return false;
			}
		}

		// ODS Charts ----------------------------------------------		
		if(document.isAllowODSCharts()) {
			String[] chart_names = document.getChartWorksheets();
			String[] chart_locations = document.getChartObjectIDs();
			if(chart_names != null && chart_locations != null) {

				charts = new ODSChartDatabase[Math.min(chart_names.length, chart_locations.length)];

				for(int i=0;i<Math.min(chart_names.length, chart_locations.length);i++) {

					// Check on the chart_locations
					if(chart_locations[i].charAt(0) == '.' && chart_locations[i].charAt(1) == '/') {
						chart_locations[i] = chart_locations[i].substring(2);
					}

					in = openStream(new File(filename));
					content = getDecompressedEntry(chart_locations[i]+"/"+ODSChartTags.CHT_FILENAME,in);
					try {
						in.close();
					} catch(IOException ioe) {
						error_string = "IO error encountered";
						ioe.printStackTrace();
					}

					// Not exactly ok if there is no content, but ignore for now
					if(content != null) {
						charts[i] = new ODSChartDatabase();
						charts[i].setWindowTitle(chart_names[i]);

						try {
							FileInputStream fin = new FileInputStream(content);
							boolean result = charts[i].loadDOMrepresentation(fin);
							fin.close();
							content.delete();

							if(!result) {
								error_string = "Error parsing and loading text windows";
								analyses = null;
								return false;
							}

						} catch(java.lang.OutOfMemoryError memory_error) {
							System.err.println("Failed to create document (ChartDatabase) in memory!");
							error_string = "Out of memory while loading document (ChartDatabase)";
							memory_error.printStackTrace();
							analyses = null;
							return false;
						} catch(IOException ioe) {
							error_string = "IO error encountered (ChartDatabase)";
							ioe.printStackTrace();
							analyses = null;
							return false;
						}
					}
				}
			 } 
			
		} // End ODS Chart handling


		return true;
	}

	public String[] getTables() {
		
		if(using_quickload) {
			String[] temp = new String[2];
			temp[0] = ODDatabase.VARIABLE_TABLE;
			temp[1] = ODDatabase.CONSTANT_TABLE;
			return temp;
		} else if(document != null) {
			return document.getContentTableNames();
		} else {
			return null;
		}
	}
	
	public ZipEntry getEntry(String name, ZipInputStream in) {
		boolean found = true;
		
		ZipEntry current = null;
		try {
			current = in.getNextEntry();
			while(current.getName().compareTo(name) != 0) {
				in.closeEntry();
				current = in.getNextEntry();
				if(current == null) {
					found = false;
					break;
				}
			}
		} catch(IOException ioe) {
			error_string = "IO error encountered";
			return null;
		}
		
		if(found)
			return current;
		else
			return null;
	}
	
	private File getDecompressedEntry(String name, ZipInputStream in) {
		
		File tmpFile = null;
		byte[] buf = new byte[1024];
		
		ZipEntry current = getEntry(name,in);
		if(current == null) return null;
		
		try {
			tmpFile = File.createTempFile("easy-loadXML", null);
			
			FileOutputStream out = new FileOutputStream(tmpFile);
			
			int n;
			while ((n = in.read(buf, 0, 1024)) > -1)
				out.write(buf, 0, n);
			
//			synchronized (in) {
//				synchronized (out) {
//					int one_byte = in.read();
//					while (in.available() > 0) {
//						out.write(one_byte);
//						one_byte = in.read();
//					}
//				}
//			}
			
			in.closeEntry();
		} catch(IOException ioe) {
			error_string = "IO error encountered";
			if(tmpFile != null) tmpFile.delete();
			return null;
		}
		
		return tmpFile;
	}
	
	private StatTableModel LoadQuickData(File quick_file) {
		StatTableModel return_model = null;
		BufferedReader input;
		try {
			input = new BufferedReader(new FileReader(quick_file));
		} catch (FileNotFoundException fnfe) {
			return null;
		}
		
		String[] headers = null;
		try {
			headers = input.readLine().split(" ");
		} catch (IOException ioe) {
			return null;
		}
		
		if(headers == null) return null;

		Object[] contents = ImportExport.LoadText(input,1,1,",",0);
		
		if(headers[0].compareTo(SaveWorksheet.QUICK_PANEL)==0) {
			return_model = ImportExport.CreatePanelModel((ArrayList)contents[0],(String[])contents[1]);
		} else if(headers[0].compareTo(SaveWorksheet.QUICK_DATE)==0) {
			int date_column = new Integer(headers[1]).intValue();
			int date_type = DateObject.getTypeInt(headers[2]);
			DateObject start = ParseDate.ParseTextToDate(headers[3],date_type);
			DateObject end = ParseDate.ParseTextToDate(headers[4],date_type);
			
			return_model = ImportExport.CreateTimeSeriesModel(start,end);
			return_model = ImportExport.ImportTimeSeriesData(return_model,(ArrayList)contents[0],date_column,(String[])contents[1]);
		}
	
		return return_model;
	}
	
	private void LoadQuickConstants(File quick_file,StatTableModel model) {
		ImportExport.LoadConstants(quick_file,model);
	}
	
	private boolean loadQuick(String worksheet) {
		boolean return_val = true;
		
		ZipInputStream in = openStream(new File(filename));
		if(in == null) return false;
		
		if(worksheet.compareTo(ODDatabase.VARIABLE_TABLE)==0) {
			File tmpFile = getDecompressedEntry(SaveWorksheet.QUICK_DATA_FILE,in);
			if(tmpFile != null) {
				
				loaded_model = LoadQuickData(tmpFile);
				if(loaded_model == null) {
					error_string = "Error loading data";
					return_val = false;
				}
					
				tmpFile.delete();
				
			} else {
				error_string = "Error loading data";
				return_val = false;
			}
		} else if(worksheet.compareTo(ODDatabase.CONSTANT_TABLE)==0) {
			File tmpFile = getDecompressedEntry(SaveWorksheet.QUICK_CONST_FILE,in);
			if(tmpFile != null && loaded_model != null) {
				LoadQuickConstants(tmpFile,loaded_model);
				tmpFile.delete();
			} 
		}
		
		try {
			in.close();
		} catch(IOException ioe) {
			
		}
		return return_val;
	}
	
	
	private ZipInputStream openStream(File filename) {
		try {
			return new ZipInputStream(new FileInputStream(filename));
		} catch(FileNotFoundException fnfe) {
			return null;
		}
	}
	
	public boolean buildAnalyses() {
		if(analyses == null) return false;
		
		for(int i=0;i<analyses.getAnalysisCount();i++) {
			AnalysisIO.BuildAnalysis(analyses.getAnalysis(i), parent);
		}
		
		return true;
	}
	
	public boolean buildTexts() {
		if(texts == null) return false;
		
		for(int i=0;i<texts.getTextWindowCount();i++) {
			
			String content_type = texts.getContentType(i);
			String name = texts.getName(i);
			String contents = texts.getContents(i);
			boolean editable = texts.getEditable(i);
			if(name == null || contents == null) continue;
			
			java.awt.Dimension size = texts.getSize(i);
			InfoWindow temp = null;
			if(size == null)
				temp = (InfoWindow)parent.openWindow(new InfoWindow(name,contents,editable,content_type));
			else
				temp = (InfoWindow)parent.openWindow(new InfoWindow(name,contents,editable,size,content_type));
			
			try {
				temp.setIcon(true);
			} catch(PropertyVetoException pve) {
				System.err.println("Can't iconify: "+name);
				pve.printStackTrace();
			}
		}
		
		return true;
	}
	
	public boolean buildPlots() {
		if(plots==null) return false;
		
		for(int i=0;i<plots.getPlotCount();i++) {
			PlotIO.BuildPlot(plots.getPlot(i), parent);
		}
		
		return true;
	}
	
	public boolean buildCharts() {
		
		if(!allow_ODS_charts) 
			return buildPlots();
		
		if(charts == null) return false;
		
		for(int i=0;i<charts.length;i++) {
			if(charts[i] == null) continue;
			
			PlotDialog plot = charts[i].createPlot(parent);

			try {
				plot.setIcon(true);
			} catch(PropertyVetoException pve) {
				System.err.println("Can't iconify: "+plot.getTitle());
				pve.printStackTrace();
			}
		}
		
		return true;
	}
	
	public void allowODSCharts(boolean allow) {
		allow_ODS_charts = allow;
	}
	
	public boolean isAllowODSCharts() {
		return allow_ODS_charts;
	}
	
}
