/*
 * $Id: CompareDocumentsForm.java,v 1.7 2006/08/30 21:08:15 marbobo Exp $
 *
 * Copyright (c) Siemens AG 2008 
 *           (c) Universität Paderborn 2008 
 * Licensed under EUPL 1.1. 
 *
 * $Log: CompareDocumentsForm.java,v $
 * Revision 1.7  2006/08/30 21:08:15  marbobo
 * + Rückkehr zur Projekt- und Funktionssicht korrigiert
 *
 * Revision 1.6  2006/03/23 11:45:32  marbobo
 * + Bugfix
 *
 * Revision 1.5  2006/02/02 11:59:15  korowa
 * *** empty log message ***
 *
 * Revision 1.4  2006/02/02 11:58:42  korowa
 * Möglichkeit zwieschen Veränderungen vorwärts und rückwärts zu springen
 *
 * Revision 1.3  2006/01/27 13:46:46  korowa
 * *** empty log message ***
 *
 * Revision 1.2  2006/01/26 12:45:00  korowa
 * *** empty log message ***
 *
 * Revision 1.1  2006/01/20 12:44:07  korowa
 * Ermöglicht ein Vergleich von verschiedenen Versionen eines Dokumentes
 *
 * Revision 1.5  2004/01/29 02:35:35  stuart
 * Test for out of bounds exception in UnifiedPrint.print_hunk.
 * Add setOutput() to DiffPrint.Base.
 *
 * Revision 1.4  2003/04/22  01:50:47  stuart
 * add Unified format diff
 *
 * Revision 1.3  2003/04/22  01:00:32  stuart
 * added context diff format
 *
 * Revision 1.2  2000/03/02  16:59:54  stuart
 * add GPL
 *
 */
package vkc.view.form;

import java.io.*;
import java.util.ArrayList;
import java.util.Vector;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionMapping;

import vkc.converter.DocConverter;
import vkc.db.entities.VKCAdditionalDocumentFormat;
import vkc.db.entities.VKCDocument;
import vkc.db.entities.VKCEntity;
import vkc.db.serializer.VKCDocumentSerializer;
import vkc.exception.DBException;
import vkc.misc.VKCLogManager;
import vkc.server.archive.DataStoreException;
import vkc.util.VKCInputStream;
import vkc.view.PageId;
import vkc.view.bean.Diff;
import vkc.view.bean.Diff.change;

interface UnaryPredicate {
	boolean execute(Object obj);
}

/**
 * A simple framework for printing change lists produced by <code>Diff</code>.
 * 
 * @see bmsi.util.Diff
 * @author Stuart D. Gathman Copyright (C) 2000 Business Management Systems,
 *         Inc.
 *         <p>
 *         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 1, or (at your option)
 *         any later version.
 *         <p>
 *         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.
 *         <p>
 *         You should have received a copy of the GNU General Public License
 *         along with this program; if not, write to the Free Software
 *         Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
public final class CompareDocumentsForm extends BasicActionForm {
	/**
	 * A Base class for printing edit scripts produced by Diff. This class
	 * divides the change list into "hunks", and calls <code>print_hunk</code>
	 * for each hunk. Various utility methods are provided as well.
	 */

	private Integer categoryId = VKCEntity.ID_UNDEFINED;

	private Integer documentId = null;

	private Integer selectedId = null;

	private Integer sourcePageId = PageId.CATEGORY_VIEW;

	private Integer targetId = null;

	private Integer subTargetId = null;

	private String[] selectedDocuments = new String[0];

	private String[] newText = new String[0];

	private String[] oldText = new String[0];

	private int counter = 0;

	private ArrayList outOld = new ArrayList();

	private ArrayList outNew = new ArrayList();

	private String oldChange = "";
	
	private String newChange = "";

	public void reset(ActionMapping mapping, HttpServletRequest request) {
		super.reset(mapping, request);

		categoryId = VKCEntity.ID_UNDEFINED;

		documentId = null;

		selectedId = null;

		sourcePageId = PageId.CATEGORY_VIEW;

		targetId = null;

		subTargetId = null;

		selectedDocuments = new String[0];

		newText = new String[0];

		oldText = new String[0];

		counter = 0;

		outOld = new ArrayList();

		outNew = new ArrayList();

		oldChange = "";
		
		newChange = "";
	}

	public abstract class Base {
		protected PrintWriter outfile;

		protected Base(Object[] a, Object[] b) {
			file0 = a;
			file1 = b;
		}

		/**
		 * Set to ignore certain kinds of lines when printing an edit script.
		 * For example, ignoring blank lines or comments.
		 */
		protected UnaryPredicate ignore = null;

		/**
		 * Set to the lines of the files being compared.
		 */
		protected Object[] file0, file1;

		/**
		 * Script wird in verschiedene Veränderungen unterteilt und analysiert
		 * analyze_hunk();
		 */
		public void print_script(change script) {
			change next = script;

			while (next != null) {
				change t, end;

				/* Find a set of changes that belong together. */
				t = next;
				end = hunkfun(next);

				/*
				 * Disconnect them from the rest of the changes, making them a
				 * hunk, and remember the rest for next iteration.
				 */
				next = end.link;
				end.link = null;

				/* Print this hunk. */
				analyze_hunk(t);

				/* Reconnect the script so it will all be freed properly. */
				end.link = next;
			}
			/*
			 * Falls keine Änderungen mehr vorhanden aber der Text noch nicht zu
			 * Ende ist, schreibe den Rest in output
			 */
			if (counter < oldText.length)
				analyze_hunk(next);
			else if (!oldChange.equals("")) {
				outOld.add(oldChange);
				outNew.add(newChange);
				newChange = "";
				oldChange = "";
			}

		}

		/**
		 * Called with the tail of the script and returns the last link that
		 * belongs together with the start of the tail.
		 */

		protected Diff.change hunkfun(change hunk) {
			return hunk;
		}

		/**
		 * Hier werden die Veränderungen analysiert und entsprechend in die
		 * Ausgansdateien reingeschrieben.
		 * 
		 */

		protected void analyze_hunk(Diff.change hunk) {
			int f0 = 0, l0 = 0, f1 = 0, l1 = 0;
			if (hunk != null) {
				f0 = hunk.line0;
				f1 = hunk.line1;
				l0 = hunk.line0 + hunk.deleted - 1;
				l1 = hunk.line1 + hunk.inserted - 1;
			} else { // Keine Änderungen(mehr), den Rest des Textes
				// rausschreiben
				f0 = oldText.length;
				hunk = new change(0, 0, 0, 0, null);
			}

			if (counter < f0) {
				if (!oldChange.equals("")) {
					outOld.add(oldChange);
					outNew.add(newChange);
					newChange = "";
					oldChange = "";
				}
				String block = "";
				for (; counter < f0; counter += 2) {
					if (!oldText[counter].equals(""))
						block += oldText[counter] + "\n";
				}
				if (!block.equals("")) {
					outNew.add(block);
					outOld.add(block);
				}

			}

			if (hunk.deleted != 0) {
				String oldblock = "";
				for (; counter <= l0; counter += 2) {
					if (oldText[counter].equals(""))
						counter++;
					oldblock += oldText[counter] + "\n";
				}

				if (hunk.inserted == 0) {
					outNew.add(newChange);
					outOld.add(oldblock);
				} else {
					oldChange += oldblock;
				}
			}
			if (hunk.inserted != 0) {
				String newblock = "";
				for (int ind = f1; ind <= l1; ind += 2) {
					if (newText[ind].equals(""))
						ind++;
					newblock += newText[ind] + "\n";
				}
				if (hunk.deleted == 0) {
					outOld.add(oldChange);
					outNew.add(newblock);
				} else
					newChange += newblock;
			}
		}

	}

	/**
	 * Print a change list in the standard diff format.
	 * 
	 */
	public class NormalPrint extends Base {

		public NormalPrint(Object[] a, Object[] b) {
			super(a, b);
		}

		/**
		 * Print a hunk of a normal diff. This is a contiguous portion of a
		 * complete edit script, describing changes in consecutive lines.
		 */

		protected void print_hunk(change hunk) {

			/* Determine range of line numbers involved in each file. */
			analyze_hunk(hunk);
		}
	}

	/**
	 * Read a text file into an array of String. This provides basic diff
	 * functionality. A more advanced diff utility will use specialized objects
	 * to represent the text lines, with options to, for example, convert
	 * sequences of whitespace to a single space for comparison purposes.
	 * 
	 * @throws DataStoreException
	 */
	static String[] slurp(VKCDocument file) throws IOException,
			DataStoreException {
		Object[] temp = (file.getAdditionalDocumentFormats().toArray());
		VKCAdditionalDocumentFormat tdfFormat = null;
		for (int i = 0; i < temp.length; i++) {
			if (((VKCAdditionalDocumentFormat) temp[i]).getDocTypeId()
					.intValue() == 10) {
				tdfFormat = (VKCAdditionalDocumentFormat) temp[i];
			}
		}
		if (tdfFormat == null)
			return new String[0];// sollte eigentlich nie vorkommen bei
		// laufendem Betrieb;
		VKCInputStream tdfStream = (VKCInputStream) tdfFormat.getInputStream();
		DocConverter converter = DocConverter.createConverter(tdfStream,
				tdfFormat.getDocTypeId().intValue());
		converter.convert(tdfStream, null);
		// DataInputStream rdr = new DataInputStream(converter.getAsciiStream()
		// );

		BufferedReader rdr = new BufferedReader(new InputStreamReader(converter
				.getAsciiStream(), "utf-8"));
		Vector s = new Vector();
		for (;;) {
			String line = rdr.readLine();
			if (line == null)
				break;
			s.addElement(line);
		}
		String[] a = new String[s.size()];
		s.copyInto(a);
		return a;
	}

	public ActionErrors validate(ActionMapping mapping,
			HttpServletRequest request) {
		ActionErrors errors = new ActionErrors();

		if (request.getParameter("initialize") != null) {
			try {
				setNewText();
			} catch (Exception e) {
				VKCLogManager.errorLog(this, "", e.getMessage(), e);
				errors.add("error", new ActionError("error.general", e
						.getMessage()));
			}
		}

		return errors;
	}

	void setNewText() throws DBException, IOException, DataStoreException {

		VKCDocument document1 = VKCDocumentSerializer.retrieveDocument(Integer
				.valueOf(selectedDocuments[0]));
		VKCDocument document2 = VKCDocumentSerializer.retrieveDocument(Integer
				.valueOf(selectedDocuments[1]));

		if (document1.getPublished().after(document2.getPublished())) {

			newText = slurp(document1);
			oldText = slurp(document2);

		} else {
			oldText = slurp(document1);
			newText = slurp(document2);
		}
		Diff d = new Diff(oldText, newText);
		Diff.change script = d.diff_2(false);
		Base p;
		p = new NormalPrint(newText, oldText);
		if (script != null) {
			p.print_script(script);
		} else
			p.analyze_hunk(null);
	}

	public Integer getCategoryId() {
		return categoryId;
	}

	public void setCategoryId(Integer categoryId) {
		this.categoryId = categoryId;
	}

	public Integer getDocumentId() {
		return documentId;
	}

	public void setDocumentId(Integer documentId) {
		this.documentId = documentId;
	}

	public String[] getNewText() {
		return newText;
	}

	public String[] getOldText() {
		return oldText;
	}

	public void setOldText(String[] oldText) {
		this.oldText = oldText;
	}

	public Integer getSelectedId() {
		return selectedId;
	}

	public boolean getSameBlock(int block) {
		String s1 = null;
		String s2 = null;
		if (outOld.size() > block && outNew.size() > block) {
			s1 = (String) outOld.get(block);
			s2 = (String) outNew.get(block);
		}
		if (s1 != null && s2 != null) {
			return s1.equals(s2);
		}

		return false;
	}

	public void setSelectedId(Integer selectedId) {
		this.selectedId = selectedId;
	}

	public Integer getSourcePageId() {
		return sourcePageId;
	}

	public void setSourcePageId(Integer sourcePageId) {
		this.sourcePageId = sourcePageId;
	}

	public Integer getSubTargetId() {
		return subTargetId;
	}

	public void setSubTargetId(Integer subTargetId) {
		this.subTargetId = subTargetId;
	}

	public Integer getTargetId() {
		return targetId;
	}

	public void setTargetId(Integer targetId) {
		this.targetId = targetId;
	}

	public String getOutNew(int i) {
		return (String) outNew.get(i);
	}

	public void setOutNew(ArrayList outNew) {
		this.outNew = outNew;
	}

	public ArrayList getOutOld() {
		return outOld;
	}

	public void setOutOld(ArrayList outOld) {
		this.outOld = outOld;
	}

	public String[] getSelectedDocuments() {
		return selectedDocuments;
	}

	public void setSelectedDocuments(String[] selectedDocuments) {
		this.selectedDocuments = selectedDocuments;
	}
}