## Copyright (C) 2009,2010,2011,2012 Philip Nienhuis <prnienhuis at users.sf.net>
##
## 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/>.

## -*- texinfo -*-
## @deftypefn {Function File} [@var{xls}] = xlsclose (@var{xls})
## @deftypefnx {Function File} [@var{xls}] = xlsclose (@var{xls}, @var{filename})
## @deftypefnx {Function File} [@var{xls}] = xlsclose (@var{xls}, "FORCE")
## Close the Excel spreadsheet pointed to in struct @var{xls}, if needed
## write the file to disk. Based on information contained in @var{xls},
## xlsclose will determine if the file should be written to disk.
##
## If no errors occured during writing, the xls file pointer struct will be
## reset and -if COM interface was used- ActiveX/Excel will be closed.
## However if errors occurred, the file pinter will be untouched so you can
## clean up before a next try with xlsclose().
## Be warned that until xlsopen is called again with the same @var{xls} pointer
## struct, hidden Excel or Java applications with associated (possibly large)
## memory chunks are kept in memory, taking up resources.
## If (string) argument "FORCE" is supplied, the file pointer will be reset 
## regardless, whether the possibly modified file has been saved successfully
## or not. Hidden Excel (COM) or OpenOffice.org (UNO) invocations may live on,
## possibly even impeding proper shutdown of Octave.
##
## @var{filename} can be used to write changed spreadsheet files to
## an other file than opened with xlsopen(); unfortunately this doesn't work
## with JXL (JExcelAPI) interface.
##
## You need MS-Excel (95 - 2010), and/or the Java package => 1.2.8 plus Apache
## POI > 3.5 and/or JExcelAPI and/or OpenXLS and/or OpenOffice.org or clones
## installed on your computer + proper javaclasspath set, to make this
## function work at all.
##
## @var{xls} must be a valid pointer struct made by xlsopen() in the same
## octave session.
##
## Examples:
##
## @example
##   xls1 = xlsclose (xls1);
##   (Close spreadsheet file pointed to in pointer struct xls1; xls1 is reset)
## @end example
##
## @seealso {xlsopen, xlsread, xlswrite, xls2oct, oct2xls, xlsfinfo}
##
## @end deftypefn

## Author: Philip Nienhuis
## Created: 2009-11-29
## Updates: 
## 2010-01-03 (checked OOXML support)
## 2010-08-25 See also: xlsopen (instead of xlsclose)
## 2010-10-20 Improved tracking of file changes and need to write it to disk
## 2010-10-27 Various changes to catch errors when writing to disk;
##     "      Added input arg "keepxls" for use with xlswrite.m to save the
##     "      untouched file ptr struct in case of errors rather than wipe it
## 2010-11-12 Replaced 'keepxls' by new filename arg; catch write errors and
##            always keep file pointer in case of write errors
## 2011-03-26 Added OpenXLS support
## 2011-05-18 Added experimental UNO support, incl. saving newly created files
## 2011-09-08 Bug fix in check for filename input arg
## 2012-01-26 Fixed "seealso" help string

function [ xls ] = xlsclose (xls, varargs)

	force = 0;

	if (nargin > 1)
		for ii=2:nargin
			if (strcmp (lower (varargin{ii}), "force"))
				# Close .ods anyway even if write errors occur
				force = 1;
			elseif (~isempty (strfind (tolower (varargin{ii}), '.')))
				# Apparently a file name
				if (xls.changed == 0 || xls.changed > 2)
					printf ("File %s wasn't changed, new filename ignored.", xls.filename);
				elseif (strcmp (xls.xtype, 'JXL'))
					error ("JXL doesn't support changing filename, new filename ignored.");
				elseif ~((strcmp (xls.xtype, 'COM') || strcmp (xls.xtype, 'UNO')) && isempty (strfind ( lower (filename), '.xls')))
					# Excel/ActiveX && OOo (UNO bridge) will write any valid filetype; POI/JXL/OXS need .xls[x]
					error ('.xls or .xlsx extension lacking in filename %s', filename);
				else
					### For multi-user environments, uncomment below AND relevant stanza in xlsopen
					# In case of COM, be sure to first close the open workbook
					#if (strcmp (xls.xtype, 'COM'))
					#	 xls.app.Application.DisplayAlerts = 0;
					#	 xls.workbook.close();
					#	 xls.app.Application.DisplayAlerts = 0;
					#endif
					if (strcmp (xls.xtype, 'UNO'))
						# If needed, turn filename into URL
						if (~isempty (strmatch ("file:///", filename)) || ~isempty (strmatch ("http:///", filename))...
							|| ~isempty (strmatch ("ftp:///", filename)) || ~isempty (strmatch ("www:///", filename)))
							# Seems in proper shape for OOo (at first sight)
						else
							# Transform into URL form
							fname = canonicalize_file_name (strsplit (filename, filesep){end});
							# On Windows, change backslash file separator into forward slash
							if (strcmp (filesep, "\\"))
								tmp = strsplit (fname, filesep);
								flen = numel (tmp);
								tmp(2:2:2*flen) = tmp;
								tmp(1:2:2*flen) = '/';
								filename = [ 'file://' tmp{:} ];
							endif
						endif
					endif
          # Preprocessing / -checking ready. Assign filename arg to file ptr struct
					xls.filename = filename;
				endif
			endif
		endfor
	endif

	if (strcmp (xls.xtype, 'COM'))
		# If file has been changed, write it out to disk.
		#
		# Note: COM / VB supports other Excel file formats as FileFormatNum:
		# 4 = .wks - Lotus 1-2-3 / Microsoft Works
		# 6 = .csv
		# -4158 = .txt 
		# 36 = .prn
		# 50 = .xlsb - xlExcel12 (Excel Binary Workbook in 2007 with or without macro's)
		# 51 = .xlsx - xlOpenXMLWorkbook (without macro's in 2007)
		# 52 = .xlsm - xlOpenXMLWorkbookMacroEnabled (with or without macro's in 2007)
		# 56 = .xls  - xlExcel8 (97-2003 format in Excel 2007)
		# (see Excel Help, VB reference, Enumerations, xlFileType)
		
		# xls.changed = 0: no changes: just close;
		#               1: existing file with changes: save, close.
		#               2: new file with data added: save, close
		#               3: new file, no added added (empty): close & delete on disk

		xls.app.Application.DisplayAlerts = 0;
		try
			if (xls.changed > 0 && xls.changed < 3)
				if (xls.changed == 2)
					# Probably a newly created, or renamed, Excel file
					printf ("Saving file %s ...\n", xls.filename);
					xls.workbook.SaveAs (canonicalize_file_name (xls.filename));
				elseif (xls.changed == 1)
					# Just updated existing Excel file
					xls.workbook.Save ();
				endif
				xls.changed = 0;
				xls.workbook.Close (canonicalize_file_name (xls.filename));
			endif
			xls.app.Quit ();
			delete (xls.workbook);	# This statement actually closes the workbook
			delete (xls.app);	 	# This statement actually closes down Excel
		catch
			xls.app.Application.DisplayAlerts = 1;
		end_try_catch
		
	elseif (strcmp (xls.xtype, 'POI'))
		if (xls.changed > 0 && xls.changed < 3)
			try
				xlsout = java_new ("java.io.FileOutputStream", xls.filename);
				bufout = java_new ("java.io.BufferedOutputStream", xlsout);
				if (xls.changed == 2) printf ("Saving file %s...\n", xls.filename); endif
				xls.workbook.write (bufout);
				bufout.flush ();
				bufout.close ();
				xlsout.close ();
				xls.changed = 0;
			catch
#				xlsout.close ();
			end_try_catch
		endif

	elseif (strcmp (xls.xtype, 'JXL'))
		if (xls.changed > 0 && xls.changed < 3)
			try
				if (xls.changed == 2) printf ("Saving file %s...\n", xls.filename); endif
				xls.workbook.write ();
				xls.workbook.close ();
				if (xls.changed == 3)
					# Upon entering write mode, JExcelAPI always makes a disk file
					# Incomplete new files (no data added) had better be deleted.
					xls.workbook.close ();
					delete (xls.filename); 
				endif
				xls.changed = 0;
			catch
			end_try_catch
		endif

	elseif (strcmp (xls.xtype, 'OXS'))
		if (xls.changed > 0 && xls.changed < 3)
			try
				xlsout = java_new ("java.io.FileOutputStream", xls.filename);
				bufout = java_new ("java.io.BufferedOutputStream", xlsout);
				if (xls.changed == 2) printf ("Saving file %s...\n", xls.filename); endif
				xls.workbook.writeBytes (bufout);
				xls.workbook.close ();
				bufout.flush ();
				bufout.close ();
				xlsout.close ();
				xls.changed = 0;
			catch
#				xlsout.close ();
			end_try_catch
		else
			xls.workbook.close ();
		endif

	elseif (strcmp (xls.xtype, 'UNO'))
		# Java & UNO bridge
		try
			if (xls.changed && xls.changed < 3)
				# Workaround:
				unotmp = java_new ('com.sun.star.uno.Type', 'com.sun.star.frame.XModel');
				xModel = xls.workbook.queryInterface (unotmp);
				unotmp = java_new ('com.sun.star.uno.Type', 'com.sun.star.util.XModifiable');
				xModified = xModel.queryInterface (unotmp);
				if (xModified.isModified ())
					unotmp = java_new ('com.sun.star.uno.Type', 'com.sun.star.frame.XStorable');	# isReadonly() ?  	
					xStore = xls.app.xComp.queryInterface (unotmp);
					if (xls.changed == 2)
						# Some trickery as Octave Java cannot create non-numeric arrays
						lProps = javaArray ('com.sun.star.beans.PropertyValue', 1);
						lProp = java_new ('com.sun.star.beans.PropertyValue', "Overwrite", 0, true, []);
						lProps(1) = lProp;
						# OK, store file
						xStore.storeAsURL (xls.filename, lProps);
					else
						xStore.store ();
					endif
				endif
			endif
			xls.changed = -1;		# Needed for check on properly shutting down OOo
			# Workaround:
			unotmp = java_new ('com.sun.star.uno.Type', 'com.sun.star.frame.XModel');
			xModel = xls.app.xComp.queryInterface (unotmp);
			unotmp = java_new ('com.sun.star.uno.Type', 'com.sun.star.util.XCloseable');
			xClosbl = xModel.queryInterface (unotmp);
			xClosbl.close (true);
			unotmp = java_new ('com.sun.star.uno.Type', 'com.sun.star.frame.XDesktop');
			xDesk = xls.app.aLoader.queryInterface (unotmp);
			xDesk.terminate();
			xls.changed = 0;
		catch
			if (force)
				# Force closing OOo
				unotmp = java_new ('com.sun.star.uno.Type', 'com.sun.star.frame.XDesktop');
				xDesk = xls.app.aLoader.queryInterface (unotmp);
				xDesk.terminate();
			else
				warning ("Error closing xls pointer (UNO)");
			endif
			return
		end_try_catch

#	elseif   <other interfaces here>
		
	endif

	if (xls.changed && xls.changed < 3)
		warning (sprintf ("File %s could not be saved. Read-only or in use elsewhere?\nFile pointer preserved.", xls.filename));
		if (force)
			xls = [];
		endif
	else
		xls = [];
	endif

endfunction
