/*
 *  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 com.approximatrix.draco.application.ChildDialog;
import com.approximatrix.draco.main.ui.MainWindow;
import com.approximatrix.draco.application.ChildFrameRegistrar;
import com.approximatrix.draco.datamodel.DateObject;
import com.approximatrix.draco.datamodel.StatTableModel;
import com.approximatrix.draco.io.ImportExport;
import com.approximatrix.draco.plot.ui.PlotDialog;

import java.io.*;
import java.util.zip.*;
import java.net.URL;

import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;

public class SaveWorksheet {
	
	private static final int QUICK_LOAD_NEEDED = 250;
	public static final String QUICK_DATA_FILE = "easy-quickload.csv";
	public static final String QUICK_CONST_FILE = "easy-quickload-constants.csv";
	public static final String QUICK_PANEL = "panel";
	public static final String QUICK_DATE = "date";
	
	private String filename = null;
	private String error_string = null;
	
	private boolean quicksave_only = false;
	private boolean allow_quicksave = true;
	private boolean allow_analyses = false;
	private boolean allow_ods_charts = false; // They really don't work right anyway
	
	public SaveWorksheet(String filename) {
		this.filename = filename;
	}
	
	public String getError() {
		return error_string;
	}
	
	public void setQuicksaveOnly(boolean quick) {
		quicksave_only = quick;
	}
	
	public void setAllowQuicksave(boolean quick) {
		allow_quicksave = quick;
	}
	
	public void setAllowAnalyses(boolean analyses) {
		allow_analyses = analyses;
	}
	
	public boolean isQuicksaveOnly(boolean quick) {
		return quicksave_only;
	}

	public boolean saveData(MainWindow parent) {
		
		boolean return_val = true;
		StatTableModel model = parent.getWindowTableModel();
		
		// Create the zip file
		ZipOutputStream out = null;
		try {
			out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(filename)));
		} catch (FileNotFoundException fnfe) {
			fnfe.printStackTrace();
			error_string = "Unable to create output file";
			return false;
		}
		try {
			try {
				// Need to write meta.xml, manifest.xml, and mimetype
				WriteResource("meta.xml", "/com/approximatrix/draco/io/XMLio/resource/meta.xml",out);
				WriteResource("META-INF/manifest.xml", "/com/approximatrix/draco/io/XMLio/resource/manifest.xml",out);
				WriteResource("styles.xml", "/com/approximatrix/draco/io/XMLio/resource/styles.xml",out);
				WriteResource("settings.xml", "/com/approximatrix/draco/io/XMLio/resource/settings.xml",out);
				
				// Great, now need to write the contents
				ZipEntry zipEntry = new ZipEntry("content.xml");
				out.putNextEntry(zipEntry);
				
				File tmpFile = CreateData(parent);
				if(tmpFile != null) {
					try {
						InputStream in = new FileInputStream(tmpFile);
						WriteStream(in,out);
						in.close();
					} catch (IOException e) {
						e.printStackTrace();
						out.closeEntry();
						return_val = false;
					}
					tmpFile.delete();
				} else {
					return_val = false;
				}
				out.closeEntry();
				
				if(allow_quicksave && (model.getRowCount() > QUICK_LOAD_NEEDED || quicksave_only)) {
					return_val = WriteAllQuickData(model,out);
				}
				
				// Write analyses work
				zipEntry = new ZipEntry(AnalysisTags.ANALYSIS_FILENAME);
				out.putNextEntry(zipEntry);
				tmpFile = CreateAnalyses(parent);
				if(tmpFile != null) {
					try {
						InputStream in = new FileInputStream(tmpFile);
						WriteStream(in,out);
						in.close();
					} catch (IOException e) {
						e.printStackTrace();
						out.closeEntry();
						return_val = false;
					}
					tmpFile.delete();
				} else {
					return_val = false;
				}
				out.closeEntry();
				
				// Write text work
				zipEntry = new ZipEntry(TextTags.TEXT_FILENAME);
				out.putNextEntry(zipEntry);
				tmpFile = CreateTexts(parent);
				if(tmpFile != null) {
					try {
						InputStream in = new FileInputStream(tmpFile);
						WriteStream(in,out);
						in.close();
					} catch (IOException e) {
						e.printStackTrace();
						out.closeEntry();
						return_val = false;
					}
					tmpFile.delete();
				} else {
					return_val = false;
				}
				out.closeEntry();
				
				// Write ODS graphs work
				//PlotDialog[] plots = (PlotDialog[])manager.getWindowsByClass(PlotDialog.class);
				Object[] plots = parent.getWindowsByClass(PlotDialog.class);
				if(plots != null && allow_ods_charts) {
					for(int i=0; i<plots.length; i++) {
						String folder = new String("Object "+Integer.toString(i));
						WriteResource(folder+"/styles.xml", "/com/approximatrix/draco/io/XMLio/resource/styles.xml",out);

						zipEntry = new ZipEntry(folder+"/"+ODSChartTags.CHT_FILENAME);
						out.putNextEntry(zipEntry);
						tmpFile = CreateChart((PlotDialog)plots[i]);
						if(tmpFile != null) {
							try {
								InputStream in = new FileInputStream(tmpFile);
								WriteStream(in,out);
								in.close();
							} catch (IOException e) {
								e.printStackTrace();
								out.closeEntry();
								return_val = false;
							}
							tmpFile.delete();
						} else {
							return_val = false;
						}
						out.closeEntry();
					}
				}
				
				// Write custom plots to file
				zipEntry = new ZipEntry(PlotTags.PLOT_FILENAME);
				out.putNextEntry(zipEntry);
				tmpFile = CreatePlots(parent);
				if(tmpFile != null) {
					try {
						InputStream in = new FileInputStream(tmpFile);
						WriteStream(in,out);
						in.close();
					} catch (IOException e) {
						e.printStackTrace();
						out.closeEntry();
						return_val = false;
					}
					tmpFile.delete();
				} else {
					return_val = false;
				}
				out.closeEntry();
				
			} finally {
				out.close();
			}
		} catch(IOException ioe) {
			ioe.printStackTrace();
			error_string = "IO error encountered";
			return_val = false;
		}
        return return_val;
	}
	
	private boolean WriteResource(String entry, String resource_path, ZipOutputStream out) {
		ZipEntry zipEntry = new ZipEntry(entry);
		try {
			out.putNextEntry(zipEntry);
			
			URL resource = getClass().getResource(resource_path);
			try {
				InputStream in = resource.openStream();
				WriteStream(in,out);
			} catch (IOException e) {
				e.printStackTrace();
				out.closeEntry();
				error_string = "IO error encountered";
				return false;
			}
			out.closeEntry();
		} catch(IOException ioe) {
			ioe.printStackTrace();
			error_string = "IO error encountered";
			return false;
		}
        
        return true;
		
	}
	
	private boolean WriteStream(InputStream in, OutputStream out) {
		byte[] buffer = new byte[256];
		synchronized (in) {
			synchronized (out) {
				try {
					while (true) {
						int bytesRead = in.read(buffer);
						if (bytesRead == -1) break;
						out.write(buffer, 0, bytesRead);
					}
				} catch(IOException ioe) {
					ioe.printStackTrace();
					error_string = "IO error encountered";
					return false;
				}
			}
		}
		return true;
	}
	
	private boolean WriteAllQuickData(StatTableModel model,ZipOutputStream out) {
		boolean return_val = true;
		
		File quick_file = CreateQuickData(model);
		ZipEntry zipQEntry = null;
		zipQEntry = new ZipEntry(QUICK_DATA_FILE);
		try {
			out.putNextEntry(zipQEntry);
			InputStream in = new FileInputStream(quick_file);
			WriteStream(in,out);
			in.close();
			out.closeEntry();
		} catch (IOException e) {
			e.printStackTrace();
			return_val = false;
		}
		quick_file.delete();
		
		quick_file = CreateQuickConstants(model);
		if(quick_file != null) {
			zipQEntry = new ZipEntry(QUICK_CONST_FILE);
			try {
				out.putNextEntry(zipQEntry);
				InputStream in = new FileInputStream(quick_file);
				WriteStream(in,out);
				in.close();
				out.closeEntry();
			} catch (IOException e) {
				e.printStackTrace();
				return_val = false;
			}
		}
		
		return return_val;
	}
	
	private File CreateQuickConstants(StatTableModel model) {
		File tmpFile = null;
		try {
			tmpFile = File.createTempFile("easy-saveQUICKCONST", null);
			
			if(!ImportExport.SaveConstants(tmpFile,model)) {
				tmpFile.delete();
				return null;
			}
			
		} catch(IOException ioe) {
			if(tmpFile != null)	tmpFile.delete();
			error_string = "IO error encountered";
			return null;
		}
		return tmpFile;
	}
	
	private File CreateQuickData(StatTableModel model) {
		File tmpFile = null;
		try {
			tmpFile = File.createTempFile("easy-saveQUICK", null);
			
			PrintWriter out_stream = new PrintWriter(new FileWriter(tmpFile));
    		int date_col = model.getDateColumn();
    		if(date_col == -1) {
    			out_stream.println(QUICK_PANEL);
    		} else {
    			out_stream.print(QUICK_DATE+" ");
    			out_stream.print(date_col);
    			out_stream.print(" ");
    			out_stream.print(DateObject.getTypeString(model.getDateType())+" ");
    			
    			out_stream.print(model.getValueAt(0,date_col).toString());
    			out_stream.print(" ");
    			out_stream.print(model.getValueAt(model.getRowCount()-1,date_col).toString());
    			
    			out_stream.println();
    		}
			
			if(!ImportExport.SaveText(out_stream,model)) 
				return null;
			
			out_stream.close();
			
		} catch(IOException ioe) {
			tmpFile.delete();
			error_string = "IO error encountered";
			return null;
		}
		return tmpFile;
	}
	
	private File CreateAnalyses(ChildFrameRegistrar parent) {
		File tmpFile = null; 
		AnalysisDatabase ad = new AnalysisDatabase(parent);
		try {
			tmpFile = File.createTempFile("easy-saveXML", null);
			Writer filewriter = new OutputStreamWriter(new FileOutputStream(tmpFile), "UTF8");
			try {
				DOMSource source = new DOMSource(ad.getDOMrepresentation());
				StreamResult result = new StreamResult(filewriter);
				try {
					Transformer trans = TransformerFactory.newInstance().newTransformer();
					trans.setOutputProperty(OutputKeys.INDENT, "yes");
					trans.transform(source, result);
				} catch(TransformerConfigurationException tce) {
					tce.printStackTrace();
					filewriter.close();
					return tmpFile;
				} catch(TransformerException te) {
					te.printStackTrace();
					filewriter.close();
					return tmpFile;
				}
			} catch(java.lang.OutOfMemoryError memory_error) {
				//System.err.println("Failed to create document in memory!");
				//memory_error.printStackTrace();
				error_string = "Out of memory while generating analysis document";
				filewriter.close();
				return null;
			} finally {
				filewriter.close();
			}
			
		} catch(IOException ioe) {
			tmpFile.delete();
			error_string = "IO error encountered";
			return null;
		}
		return tmpFile;
	}
	
	private File CreateChart(PlotDialog plot) {
		File tmpFile = null; 
		ODSChartDatabase cd = new ODSChartDatabase(plot.getPlotter());
		try {
			tmpFile = File.createTempFile("easy-saveXML", null);
			Writer filewriter = new OutputStreamWriter(new FileOutputStream(tmpFile), "UTF8");
			try {
				DOMSource source = new DOMSource(cd.getDOMrepresentation());
				StreamResult result = new StreamResult(filewriter);
				try {
					Transformer trans = TransformerFactory.newInstance().newTransformer();
					trans.setOutputProperty(OutputKeys.INDENT, "yes");
					trans.transform(source, result);
				} catch(TransformerConfigurationException tce) {
					tce.printStackTrace();
					filewriter.close();
					return tmpFile;
				} catch(TransformerException te) {
					te.printStackTrace();
					filewriter.close();
					return tmpFile;
				} catch(NullPointerException npe) {
					error_string = "Creating DOMSource failed while saving graphs";
					return null;
				}
				
			} catch(java.lang.OutOfMemoryError memory_error) {
				//System.err.println("Failed to create document in memory!");
				//memory_error.printStackTrace();
				error_string = "Out of memory while generating graphs";
				filewriter.close();
				return null;
			} finally {
				filewriter.close();
			}
			
		} catch(IOException ioe) {
			tmpFile.delete();
			error_string = "IO error encountered";
			return null;
		}
		return tmpFile;
	}
	
	private File CreateTexts(ChildFrameRegistrar parent) {
		File tmpFile = null; 
		TextDatabase td = new TextDatabase(parent);
		try {
			tmpFile = File.createTempFile("easy-saveXML", null);
			Writer filewriter = new OutputStreamWriter(new FileOutputStream(tmpFile), "UTF8");
			try {
				DOMSource source = new DOMSource(td.getDOMrepresentation());
				StreamResult result = new StreamResult(filewriter);
				try {
					Transformer trans = TransformerFactory.newInstance().newTransformer();
					trans.setOutputProperty(OutputKeys.INDENT, "yes");
					trans.transform(source, result);
				} catch(TransformerConfigurationException tce) {
					tce.printStackTrace();
					filewriter.close();
					return tmpFile;
				} catch(TransformerException te) {
					te.printStackTrace();
					filewriter.close();
					return tmpFile;
				}
			} catch(java.lang.OutOfMemoryError memory_error) {
				//System.err.println("Failed to create document in memory!");
				//memory_error.printStackTrace();
				error_string = "Out of memory while generating texts document";
				filewriter.close();
				return null;
			} finally {
				filewriter.close();
			}
			
		} catch(IOException ioe) {
			tmpFile.delete();
			error_string = "IO error encountered";
			return null;
		}
		return tmpFile;
	}
	
	private File CreatePlots(ChildFrameRegistrar parent) {
		File tmpFile = null; 
		PlotDatabase pd = new PlotDatabase(parent);
		try {
			tmpFile = File.createTempFile("easy-saveXML", null);
			Writer filewriter = new OutputStreamWriter(new FileOutputStream(tmpFile), "UTF8");
			try {
				DOMSource source = new DOMSource(pd.getDOMrepresentation());
				StreamResult result = new StreamResult(filewriter);
				try {
					Transformer trans = TransformerFactory.newInstance().newTransformer();
					trans.setOutputProperty(OutputKeys.INDENT, "yes");
					trans.transform(source, result);
				} catch(TransformerConfigurationException tce) {
					tce.printStackTrace();
					filewriter.close();
					return tmpFile;
				} catch(TransformerException te) {
					te.printStackTrace();
					filewriter.close();
					return tmpFile;
				}
			} catch(java.lang.OutOfMemoryError memory_error) {
				//System.err.println("Failed to create document in memory!");
				//memory_error.printStackTrace();
				error_string = "Out of memory while generating plots document";
				filewriter.close();
				return null;
			} finally {
				filewriter.close();
			}
			
		} catch(IOException ioe) {
			tmpFile.delete();
			error_string = "IO error encountered";
			return null;
		}
		return tmpFile;
	}
	
	private File CreateData(MainWindow parent) {
		File tmpFile = null; 
		ODDatabase od = new ODDatabase(parent);
		
		// Block saving problematic ODS charts
		od.allowODSCharts(false);
		
		try {
			tmpFile = File.createTempFile("easy-saveXML", null);
			Writer filewriter = new OutputStreamWriter(new FileOutputStream(tmpFile), "UTF8");
			try {
				DOMSource source = new DOMSource(od.getDOMrepresentation(!quicksave_only || !allow_quicksave));
				StreamResult result = new StreamResult(filewriter);
				try {
					Transformer trans = TransformerFactory.newInstance().newTransformer();
					trans.setOutputProperty(OutputKeys.INDENT, "yes");
					trans.transform(source, result);
				} catch(TransformerConfigurationException tce) {
					tce.printStackTrace();
					filewriter.close();
					return tmpFile;
				} catch(TransformerException te) {
					te.printStackTrace();
					filewriter.close();
					return tmpFile;
				}
			} catch(java.lang.OutOfMemoryError memory_error) {
				//System.err.println("Failed to create document in memory!");
				//memory_error.printStackTrace();
				error_string = "Out of memory while generating ods (data) document";
				filewriter.close();
				return null;
			} finally {
				filewriter.close();
			}
			
		} catch(IOException ioe) {
			tmpFile.delete();
			error_string = "IO error encountered";
			return null;
		}
		return tmpFile;
	}
	
}
