/*******************************************************************************
 * Copyright (c) 2007, 2011 Wind River Systems, Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * Michael Scharf (Wind River) - initial implementation
 * Michael Scharf (Wing River) - [211659] Add field assist to terminal input field
 * Michael Scharf (Wing River) - [196447] The optional terminal input line should be resizeable
 * Martin Oberhuber (Wind River) - [168197] Fix Terminal for CDC-1.1/Foundation-1.1
 * Michael Scharf (Wing River) - [236458] Fix 168197 lost the last entry
 * Anton Leherbauer (Wind River) - [220971] The optional terminal input line has redraw problems when resizing
 *******************************************************************************/
package org.eclipse.tm.internal.terminal.control;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.fieldassist.TextContentAdapter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter;



/**
 * Manages the Command History for the command line input
 * of the terminal control.
 * <li>
 * <ul>Navigate with ARROW_UP,ARROW_DOWN,PAGE_UP,PAGE_DOWN
 * <ul>ESC to cancel history editing
 * <ul>History can be edited (by moving up and edit) but changes are
 * not persistent (like in bash).
 * <ul>If the same command is entered multiple times in a row,
 * only one entry is kept in the history.
 * </li>
 *
 */
public class CommandInputFieldWithHistory implements ICommandInputField {
	private class FieldAssist implements IContentProposalProvider {

		public IContentProposal[] getProposals(String contents, int position) {
			String prefix=contents.substring(0, position);
			List result=new ArrayList();
			// show an entry only once
			Set seen=new HashSet();
			for (Iterator iterator = fHistory.iterator(); iterator.hasNext();) {
				String history = (String) iterator.next();
				if(history.startsWith(prefix) && !seen.contains(history)) {
					// the content is the rest of the history item
					String content=history.substring(prefix.length());
					result.add(new Proposal(content,history));
					// don't add this proposal again
					seen.add(history);
				}
			}
			return (IContentProposal[]) result.toArray(new IContentProposal[result.size()]);
		}

	}
	private static class Proposal implements IContentProposal {

		private final String fContent;
		private final String fLabel;
		Proposal(String content, String label) {
			fContent= content;
			fLabel= label;
		}
		public String getContent() {
			return fContent;
		}

		public String getLabel() {
			return fLabel;
		}

		public String getDescription() {
			return null;
		}

		public int getCursorPosition() {
			return fContent.length();
		}
	}

	final List fHistory=new ArrayList();
	/**
	 * Keeps a modifiable history while in history editing mode
	 */
	List fEditedHistory;
	/**
	 * The current position in the edit history
	 */
	private int fEditHistoryPos=0;
	/**
	 * The limit of the history.
	 */
	private final int fMaxSize;
	/**
	 * The input text field.
	 */
	private Text fInputField;
	private Sash fSash;
	private Composite fPanel;
	public CommandInputFieldWithHistory(int maxHistorySize) {
		fMaxSize=maxHistorySize;
	}
	/**
	 * Add a line to the history.
	 * @param line The line to be added to the history.
	 */
	protected void pushLine(String line) {
		endHistoryMode();
		// anything to remember?
		if(line==null || line.trim().length()==0)
			return;
		fHistory.add(0,line);
		// ignore if the same as last
		if(fHistory.size()>1 && line.equals(fHistory.get(1)))
			fHistory.remove(0);
		// limit the history size.
		if(fHistory.size()>=fMaxSize)
			fHistory.remove(fHistory.size()-1);
	}
	/**
	 * Sets the history
	 * @param history or null
	 */
	public void setHistory(String history) {
		endHistoryMode();
		fHistory.clear();
		if(history==null)
			return;
		// add history entries separated by '\n'
		// fHistory.addAll(Arrays.asList(history.split("\n"))); //$NON-NLS-1$
		//<J2ME CDC-1.1 Foundation-1.1 variant>
		StringTokenizer tok=new StringTokenizer(history,"\n"); //$NON-NLS-1$
		while(tok.hasMoreElements())
			fHistory.add(tok.nextElement());
		//</J2ME CDC-1.1 Foundation-1.1 variant>
	}
	/**
	 * @return the current content of the history buffer and new line separated list
	 */
	public String getHistory() {
		StringBuffer buff=new StringBuffer();
		boolean sep=false;
		for (Iterator iterator = fHistory.iterator(); iterator.hasNext();) {
			String line=(String) iterator.next();
			if(line.length()>0) {
				if(sep)
					buff.append("\n"); //$NON-NLS-1$
				else
					sep=true;
				buff.append(line);
			}
		}
		return buff.toString();
	}
	/**
	 * @param currLine Line of text to be moved in history
	 * @param count (+1 or -1) for forward and backward movement. -1 goes back
	 * @return the new string to be displayed in the command line or null,
	 * if the limit is reached.
	 */
	public String move(String currLine, int count) {
		if(!inHistoryMode()) {
			fEditedHistory=new ArrayList(fHistory.size()+1);
			fEditedHistory.add(currLine);
			fEditedHistory.addAll(fHistory);
			fEditHistoryPos=0;
		}
		fEditedHistory.set(fEditHistoryPos,currLine);
		if(fEditHistoryPos+count>=fEditedHistory.size())
			return null;
		if(fEditHistoryPos+count<0)
			return null;
		fEditHistoryPos+=count;
		return (String) fEditedHistory.get(fEditHistoryPos);
	}
	private boolean inHistoryMode() {
		return fEditedHistory!=null;
	}

	/**
	 * Exit the history movements and go to position 0;
	 * @return the string to be shown in the command line
	 */
	protected String escape() {
		if(!inHistoryMode())
			return null;
		String line= (String) fEditedHistory.get(0);
		endHistoryMode();
		return line;
	}
	/**
	 * End history editing
	 */
	private void endHistoryMode() {
		fEditedHistory=null;
		fEditHistoryPos=0;
	}
	public void createControl(final Composite parent,final ITerminalViewControl terminal) {
//		fSash = new Sash(parent,SWT.HORIZONTAL|SWT.SMOOTH);
		fSash = new Sash(parent,SWT.HORIZONTAL);
		final GridData gd_sash = new GridData(SWT.FILL, SWT.CENTER, true, false);
		gd_sash.heightHint=5;
		fSash.setLayoutData(gd_sash);
		fSash.addListener (SWT.Selection, new Listener () {
			public void handleEvent (Event e) {
				if (e.detail == SWT.DRAG) {
					// don't redraw during drag, it causes paint errors - bug 220971
					return;
				}
				// no idea why this is needed
				GridData gdata = (GridData) fInputField.getLayoutData();
				Rectangle sashRect = fSash.getBounds ();
				Rectangle containerRect = parent.getClientArea ();

				int h=fInputField.getLineHeight();
				// make sure the input filed height is a multiple of the line height
				gdata.heightHint = Math.max(((containerRect.height-e.y-sashRect.height)/h)*h,h);
				// do not show less then one line
				e.y=Math.min(e.y,containerRect.height-h);
				fInputField.setLayoutData(gdata);
				parent.layout();
				// else the content assist icon will be replicated
				parent.redraw();
			}
		});
		fPanel = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.marginWidth = 0; layout.marginHeight = 0; layout.marginTop = 0; layout.marginBottom = 2;
		fPanel.setLayout(layout);
		fPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		fInputField=new Text(fPanel, SWT.MULTI|SWT.BORDER|SWT.WRAP|SWT.V_SCROLL);
		GridData data=new GridData(SWT.FILL, SWT.FILL, true, false);
		boolean installDecoration=true;
		if(installDecoration) {
			// The ContentAssistCommandAdapter says: "The client is responsible for
			// ensuring that adequate space is reserved for the decoration."
			// TODO: what is the "adequate space"???
			data.horizontalIndent=6;
		}
		fInputField.setLayoutData(data);
		fInputField.setFont(terminal.getFont());
		// Register field assist *before* the key listener.
		// Else the ENTER key is sent *first* to the input field
		// and then to the field assist popup.
		// (https://bugs.eclipse.org/bugs/show_bug.cgi?id=211659)
		new ContentAssistCommandAdapter(
				fInputField,
				new TextContentAdapter(),
				new FieldAssist(),
				null,
				null,
				installDecoration);
		fInputField.addKeyListener(new KeyListener(){
			public void keyPressed(KeyEvent e) {
				// if the field assist has handled the key already then
				// ignore it (https://bugs.eclipse.org/bugs/show_bug.cgi?id=211659)
				if(!e.doit)
					return;
				if(e.keyCode=='\n' || e.keyCode=='\r') {
					e.doit=false;
					String line=fInputField.getText();
					if(!terminal.pasteString(line+"\n")) //$NON-NLS-1$
						return;
					pushLine(line);
					setCommand("");//$NON-NLS-1$
				} else if(e.keyCode==SWT.ARROW_UP || e.keyCode==SWT.PAGE_UP) {
					e.doit=false;
					setCommand(move(fInputField.getText(),1));
				} else if(e.keyCode==SWT.ARROW_DOWN || e.keyCode==SWT.PAGE_DOWN) {
					e.doit=false;
					setCommand(move(fInputField.getText(),-1));
				} else if(e.keyCode==SWT.ESC) {
					e.doit=false;
					setCommand(escape());
				}
			}
			private void setCommand(String line) {
				if(line==null)
					return;
				fInputField.setText(line);
				fInputField.setSelection(fInputField.getCharCount());
			}
			public void keyReleased(KeyEvent e) {
			}
		});
	}
	public void setFont(Font font) {
		fInputField.setFont(font);
		fInputField.getParent().layout(true);
	}
	public void dispose() {
		fSash.dispose();
		fSash=null;
		fPanel.dispose();
		fPanel=null;
		fInputField.dispose();
		fInputField=null;
	}
}