package org.objectstyle.wolips.componenteditor.actions;

import java.io.StringWriter;
import java.util.List;
import java.util.Map;

import org.eclipse.jface.action.IAction;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewerExtension;
import org.eclipse.jface.text.Region;
import org.eclipse.ui.actions.ActionDelegate;
import org.objectstyle.wolips.bindings.api.Binding;
import org.objectstyle.wolips.bindings.wod.SimpleWodBinding;
import org.objectstyle.wolips.bindings.wod.SimpleWodElement;
import org.objectstyle.wolips.componenteditor.ComponenteditorPlugin;
import org.objectstyle.wolips.templateeditor.TemplateEditor;
import org.objectstyle.wolips.wodclipse.editor.WodEditor;

/**
 * <P>
 * This is the superclass of the actions that insert new components into the
 * component. Most of the guts of it are in the superclass here with the
 * configuration of the actions in the subclasses.
 * </P>
 * 
 * @author apl
 * 
 */

public abstract class InsertHtmlAndWodAction extends AbstractTemplateAction {
	protected InsertComponentSpecification _componentSpecification;

	public void setComponentSpecification(InsertComponentSpecification componentSpecification) {
		_componentSpecification = componentSpecification;
	}

	/**
	 * <P>
	 * This method should return the required bindings that the component must
	 * have and so we may as well shove these in at the same time.
	 * </P>
	 */
	protected abstract List<Binding> getRequiredBindings(String componentName);

	/**
	 * Returns the InsertComponentSpecification for this component. This may
	 * open a dialog, so it should only be called from the run method.
	 * 
	 * @return the InsertComponentSpecification for this action
	 */
	protected abstract InsertComponentSpecification getComponentSpecification();

	protected String getIndentText(IDocument doc, int offset) throws BadLocationException {
		IRegion indentRegion = getIndentRegion(doc, offset);
		String indentText = doc.get(indentRegion.getOffset(), indentRegion.getLength());
		return indentText;
	}

	protected IRegion getIndentRegion(IDocument doc, int offset) throws BadLocationException {
		IRegion lineRegion = doc.getLineInformationOfOffset(offset);
		int lineStartOffset = lineRegion.getOffset();
		int lineLength = lineRegion.getLength();
		int lineEndOffset = lineStartOffset + lineLength;
		int indentOffset = -1;
		for (int i = lineStartOffset; indentOffset == -1 && i < lineEndOffset; i++) {
			char ch = doc.getChar(i);
			if (!Character.isWhitespace(ch)) {
				indentOffset = i;
			}
		}
		Region indentRegion;
		if (indentOffset == -1) {
			indentRegion = new Region(lineStartOffset, 0);
		} else {
			indentRegion = new Region(lineStartOffset, indentOffset - lineStartOffset);
		}
		return indentRegion;
	}

	protected boolean isLineEmpty(IDocument doc, int offset) throws BadLocationException {
		IRegion lineRegion = doc.getLineInformationOfOffset(offset);
		int lineStartOffset = lineRegion.getOffset();
		int lineLength = lineRegion.getLength();
		int lineEndOffset = lineStartOffset + lineLength;
		boolean isLineEmpty = true;
		for (int i = lineStartOffset; isLineEmpty && i < lineEndOffset; i++) {
			char ch = doc.getChar(i);
			if (!Character.isWhitespace(ch)) {
				isLineEmpty = false;
			}
		}
		return isLineEmpty;
	}

	/**
	 * @see ActionDelegate#run(IAction)
	 */
	public void run(IAction action) {
		insert();
	}

	public void insert() {
		TemplateEditor te = getTemplateEditor();
		WodEditor we = getWodEditor();
		InsertComponentSpecification ics = getComponentSpecification();
		System.out.println("InsertHtmlAndWodAction.insert: " + te + ", " + we + ", " + ics);
		if (te != null && we != null && ics != null) {
			SimpleWodElement wodElement = new SimpleWodElement(ics.getComponentInstanceName(), ics.getComponentName());
			List<Binding> bindings = ics.getRequiredBindings();
			if (bindings != null) {
				for (Binding binding : bindings) {
					wodElement.addBinding(new SimpleWodBinding(null, binding.getName(), "", true));
				}
			}

			// If the component name is blank, then this is an HTML tag
			if (ics.getComponentName() == null || ics.getComponentName().length() == 0) {
				wodElement.setTagName(ics.getTagName());
			}

			SimpleWodElement htmlElement;
			if (ics.isInline()) {
				htmlElement = wodElement;
			} else {
				htmlElement = new SimpleWodElement("", "");
				htmlElement.setTagName(ics.getTagName());
			}

			Map<String, String> htmlAttributes = ics.getHtmlAttributes();
			if (htmlAttributes != null) {
				for (Map.Entry<String, String> htmlAttribute : htmlAttributes.entrySet()) {
					htmlElement.addBinding(new SimpleWodBinding(null, htmlAttribute.getKey(), htmlAttribute.getValue(), true));
				}
			}

			IDocument teDoc = te.getHtmlEditDocument();
			IDocument weDoc = we.getWodEditDocument();
			ITextSelection teDocTSel = (ITextSelection) te.getSourceEditor().getSelectionProvider().getSelection();

			// insert the WebObjects component into the template portion.
			try {
				ITextViewerExtension teExt = (ITextViewerExtension) te.getSourceEditor().getViewer();
				teExt.getRewriteTarget().beginCompoundChange();
				try {
					int selectionStartOffset = teDocTSel.getOffset();
					int selectionEndOffset = teDocTSel.getOffset() + teDocTSel.getLength();

					if (ics.isComponentContent()) {
						int selectionStartLine = teDocTSel.getStartLine();
						int selectionEndLine = teDocTSel.getEndLine();

						StringWriter startTagWriter = new StringWriter();
						htmlElement.writeInlineFormat(startTagWriter, "", true, true, false, false, "$", "");
						String startTag = startTagWriter.toString();

						StringWriter endTagWriter = new StringWriter();
						htmlElement.writeInlineFormat(endTagWriter, "", true, false, false, true, "$", "");
						String endTag = endTagWriter.toString();

						String indentText = getIndentText(teDoc, selectionStartOffset);
						IRegion startLineRegion = teDoc.getLineInformationOfOffset(selectionStartOffset);
						IRegion endLineRegion = teDoc.getLineInformationOfOffset(selectionEndOffset);

						// MS: If the selection starts within the indent
						// area, then you're actually selecting
						// from the beginning of the line, not splitting an
						// existing line of HTML.
						int selectionLineStartOffset = (selectionStartOffset - startLineRegion.getOffset());
						boolean selectionStartedInIndent = (indentText.length() >= selectionLineStartOffset);

						if (selectionStartLine == selectionEndLine) {
							if (selectionEndOffset == endLineRegion.getOffset() && selectionEndOffset > 0) {
								teDoc.replace(selectionEndOffset - 1, 0, endTag);
							} else {
								teDoc.replace(selectionEndOffset, 0, endTag);
							}

							if (selectionStartedInIndent) {
								if (indentText.length() == 0) {
									int offset = Math.max(startLineRegion.getOffset() - 1, 0);
									teDoc.replace(offset, 0, startTag);
								} else {
									teDoc.replace(startLineRegion.getOffset() + indentText.length(), 0, startTag);
								}
							} else {
								teDoc.replace(selectionStartOffset, 0, startTag);
							}
						} else {
							int indentEndOffset;
							String lastLineIndentText = getIndentText(teDoc, selectionEndOffset);
							int selectionLineEndOffset = (selectionEndOffset - endLineRegion.getOffset());
							if (lastLineIndentText.length() >= selectionLineEndOffset) {
								String endText = indentText + endTag + "\n";
								teDoc.replace(endLineRegion.getOffset(), 0, endText);
								indentEndOffset = 1;
							} else {
								String endText = "\n" + indentText + endTag + "\n" + indentText;
								teDoc.replace(selectionEndOffset, 0, endText);
								indentEndOffset = 2;
							}

							int indentStartOffset;
							if (selectionStartedInIndent) {
								indentStartOffset = 1;
								String startText = startTag + "\n" + indentText;
								teDoc.replace(startLineRegion.getOffset() + indentText.length(), 0, startText);
							} else {
								indentStartOffset = 2;
								String startText = "\n" + indentText + startTag + "\n" + indentText;
								teDoc.replace(selectionStartOffset, 0, startText);
							}
							for (int line = selectionStartLine + indentStartOffset; line <= selectionEndLine + indentEndOffset; line++) {
								int lineOffset = teDoc.getLineOffset(line);
								teDoc.replace(lineOffset, 0, "\t");
							}
						}
					} else {
						StringWriter startTagWriter = new StringWriter();
						htmlElement.writeInlineFormat(startTagWriter, null, true, true, false, true, "$", "");
						String tag = startTagWriter.toString();
						teDoc.replace(selectionStartOffset, 0, tag);
					}
				} finally {
					teExt.getRewriteTarget().endCompoundChange();
				}

				// insert the WebObjects component into the bindings
				// portion.
				if (!ics.isInline()) {
					int firstBindingValueOffset = -1;

					int offset = weDoc.getLength();
					StringWriter wodElementWriter = new StringWriter();
					if (offset > 0) {
						wodElementWriter.write("\n");
					}
					wodElement.writeWodFormat(wodElementWriter, true);
					wodElementWriter.flush();
					String wodElementStr = wodElementWriter.toString();

					weDoc.replace(offset, 0, wodElementStr);

					if (-1 != firstBindingValueOffset) {
						we.selectAndReveal(offset + firstBindingValueOffset, 0);
					} else {
						we.selectAndReveal(offset, wodElementStr.length());
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
				ComponenteditorPlugin.getDefault().log(e);
			}
		}
	}
}
