/*******************************************************************************
* Copyright (c)                                                                *
*           2005   Tristan Allwood,                                            *
*           2005   Daniel Burke,                                               *
*           2005   Marc Hull,                                                  *
*           2005   Ekaterina Itskova,                                          *
*           2005   Steve Zymler                                                *
*                                                                              *
* Permission is hereby granted, free of charge, to any person obtaining a      *
* copy of this software and associated documentation files (the                *
* "Software"), to deal in the Software without restriction, including          *
* without limitation the rights to use, copy, modify, merge, publish,          *
* distribute, sublicense, and/or sell copies of the Software, and to           *
* permit persons to whom the Software is furnished to do so, subject to        *
* the following conditions:                                                    *
*                                                                              *
* The above copyright notice and this permission notice shall be included      *
* in all copies or substantial portions of the Software.                       *
*                                                                              *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS      *
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF                   *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.       *
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY         *
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,         *
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE            *
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                       *
*******************************************************************************/
/*
 * Created on 21-Dec-2004
 *
 */
package uk.ac.ic.doc.kevlar.gui.internal.widgets.textbox;

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;

import uk.ac.ic.doc.kevlar.gui.internal.AMouseListenerObject;
import uk.ac.ic.doc.kevlar.gui.internal.caches.ColorCache;
import uk.ac.ic.doc.kevlar.gui.internal.caches.FontCache;
import uk.ac.ic.doc.kevlar.gui.internal.caches.Utils;
import uk.ac.ic.doc.kevlar.gui.internal.keyboard.KeyboardManager;
import uk.ac.ic.doc.kevlar.gui.internal.keyboard.states.TextBoxState;
import uk.ac.ic.doc.kevlar.gui.internal.widgets.IDeselectListener;
import uk.ac.ic.doc.kevlar.gui.internal.widgets.ISelectListener;

/**
 * @author Marc
 **/
public class TextBox extends AMouseListenerObject implements ITextBox, ClipboardOwner {

	private static final String EDIT_BOX_SKIN = "editbox";
	private static final String TRANSPARENT_EDIT_BOX_SKIN = "transparenteditbox";
	    
	protected Caret caret;
    protected String text;
    protected String visibleText;
    protected int caretPos;
    protected int visibleStart;
    protected GC evilHack;
    protected Font font;
    protected boolean selected;
    protected boolean selecting;
    protected int selectionStart;
    protected int selectionEnd;
    protected boolean editable;
    protected IModifyListener modifyListener;
    protected ISelectListener selectListener;
    protected IDeselectListener deselectListener;
    
    public TextBox() {
        
        super();

		font = FontCache.get("Courier New", 10, SWT.NORMAL);
        evilHack = new GC(new Image(Display.getDefault(), 1, 1));
        evilHack.setFont(font);
        
        caret = new Caret();
        caret.setSize(new Point(1, 10));
        caret.permanentlyHide();
        addChild(caret);
        
        setText("");
        selected = false;
        selecting = false;
        editable = true;
        modifyListener = null;
        deselectListener = null;
        
    }
    
    public void setModifyListener(IModifyListener l) {
        
        modifyListener = l;
        
    }
    
    public void setSelectListener(ISelectListener l) {
        
        selectListener = l;
        
    }
    
    public void setDeselectListener(IDeselectListener l) {
        
        deselectListener = l;
        
    }
    
    public void setEditable(boolean editable) {
        
        this.editable = editable;
        
    }
    
    public void setText(String text) {
        
        this.text = text;
        
        selectionStart = 0;
        selectionEnd = 0;
        selecting = false;
        
        setCaretPos(caretPos);
        updateVisibleText();
        redraw();
        
        if (modifyListener != null) {
            
            modifyListener.onModify(this, text);
            
        }
        
    }
    
    public String getText() {
        
        return text;
        
    }
    
    public void setCaretPos(int pos) {
        
        if (pos > text.length()) {
            
            caretPos = text.length();
            
        } else if (pos < 0) {
            
            caretPos = 0;
            
        } else {
        
            caretPos = pos;
            
        }
        
        if (selecting) {
            
            selectionEnd = caretPos;
            
        } else if (selectionStart != selectionEnd || selectionStart != 0) {
            
            selectionStart = 0;
            selectionEnd = 0;
            redraw();
            
        }
        
        if (caretPos < visibleStart+2) {
            
            setVisibleStart(caretPos-2);
            
        } else if (caretPos > visibleStart+numberOfVisibleCharacters()) {
            
            setVisibleStart(caretPos-numberOfVisibleCharacters()+2);
            
        }
        
        caret.setPosition(new Point(getPixelPos(caretPos-visibleStart), 3));
        
    }
    
    public void setVisibleStart(int pos) {
        
        if (pos < 0) {
            
            visibleStart = 0;
            
        } else if (pos > text.length()) {
            
            visibleStart = text.length();
            
        } else {
            
            visibleStart = pos;
            
        }
        
        updateVisibleText();
        
    }
    
    public void updateVisibleText() {
        
        visibleText = text.substring(visibleStart);
        
    }
    
    public int numberOfVisibleCharacters() {
        
        return Utils.capText(evilHack, text, area.width-10, "...");
        
    }
    
    public void updateDimensions() {
        
        super.updateDimensions();
        
        setCaretPos(caretPos);
        updateVisibleText();
        
    }
    
    public void insert(char c) {
        
        if (editable) {
        
            if (selectionStart != selectionEnd) {
            
                delete();
            
            }
        
            setText(text.substring(0,caretPos)+c+text.substring(caretPos));
            setCaretPos(caretPos+1);
            updateVisibleText();
            redraw();
            
        }
        
    }
    
    public void insert(String s) {
        
        if (editable) {
        
            if (selectionStart != selectionEnd) {
            
                delete();
            
            }
        
            setText(text.substring(0,caretPos)+s+text.substring(caretPos));
            setCaretPos(caretPos+s.length());
            updateVisibleText();
            redraw();
            
        }
        
    }
    
    public void moveCaretLeft() {
        
        setCaretPos(caretPos-1);
        redraw();
        
    }
    
    public void moveCaretRight() {
        
        setCaretPos(caretPos+1);
        redraw();
        
    }
    
    public void moveCaretWordLeft() {
        
        int start = caretPos-2;
        
        while (start > 0 && text.charAt(start) == ' ') {
            
            start--;
            
        } 

        int newPos = text.lastIndexOf(' ', start);
        
        setCaretPos(newPos+1);
        redraw();
        
    }
    
    public void moveCaretWordRight() {
        
        int newPos = text.indexOf(' ', caretPos);
        
        if (newPos < 0) {
            
            setCaretPos(text.length());
            
        } else {
            
            while (newPos < text.length() && text.charAt(newPos) == ' ') {
                
                newPos++;
                
            }
            
            setCaretPos(newPos);
            
        }
        
        redraw();
        
    }
    
    public void backSpace() {
        
        if (editable) {
        
            if (selectionStart != selectionEnd) {
            
                delete();
            
            } else if (caretPos > 0) {
            
                setCaretPos(caretPos-1);
                delete();
            
            }
            
        }
        
    }
    
    public void delete() {
        
        if (editable) {
        
            if (selectionStart != selectionEnd) {
                
                selecting = false;
                int temp;
                
                if (selectionStart < selectionEnd) {
                    
                    temp = selectionStart;
                    setText(text.substring(0, selectionStart)+text.substring(selectionEnd));
                    
                } else {
                    
                    temp = selectionEnd;
                    setText(text.substring(0, selectionEnd)+text.substring(selectionStart));
                    
                }
                
                setCaretPos(temp);
                selectionStart = 0;
                selectionEnd = 0;
                
            } else if (caretPos < text.length()) {
                
                setText(text.substring(0, caretPos)+text.substring(caretPos+1));
                
            }
            
        }
        
    }
    
    public void home() {
        
        setCaretPos(0);
        redraw();
        
    }
    
    public void end() {
        
        setCaretPos(text.length());
        redraw();
        
    }
    
    public void cut() {
        
        if (selectionStart != selectionEnd) {
        
            copy();
            delete();
            
        }
        
    }
    
    public void copy() {
        
        if (selectionStart != selectionEnd) {
            
            Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
            
            if (selectionStart < selectionEnd) {
                
                c.setContents(new StringSelection(text.substring(selectionStart, selectionEnd)), this);
                
            } else {
                
                c.setContents(new StringSelection(text.substring(selectionEnd, selectionStart)), this);
                
            }
                            
        }
        
    }
    
    public void paste() {
        
        try {
        
            Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
            
            if (c.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
            
                String contents = (String)c.getData(DataFlavor.stringFlavor);
                contents = contents.replaceAll("(\n|\r|\u0085|\u2028|\u2029)", "");
                
                if (contents.length() > 500) {
                    
                    contents = contents.substring(0, 500);
                    
                }
                
                insert(contents);
                
            }
            
        } catch(UnsupportedFlavorException e) {
            
            System.err.println("Unsupported flavor: "+e);
            
        } catch (IOException e) {
            
            System.err.println("IO Exception: "+e);
            
        }
        
    }
    
    public void startSelect() {
        
        if (!selecting) {
        
            selecting = true;
            
            if (selectionStart == selectionEnd && selectionStart == 0) {
            
                selectionStart = caretPos;
                selectionEnd = caretPos;
                
            }
            
        }
        
    }
    
    public void endSelect() {
        
        selecting = false;
        
    }
    
    public void setCaretPos(Point position) {

        int currentPos = 0;
        int newPos = currentPos;
        
        while (true) {
            
            int currentPixelPos = getPixelPos(currentPos);
            
            if (position.x < currentPixelPos && currentPos > 0) {
                
                newPos = currentPos - 1;
                
            } else if (position.x > currentPixelPos && currentPos < visibleText.length()) {
                
                newPos = currentPos + 1;
                
            } else {
                
                break;
                
            }
            
            int newPixelPos = getPixelPos(newPos);
            
            if (position.x > currentPixelPos && position.x < newPixelPos) {
                
                if (newPixelPos - position.x < position.x - currentPixelPos) {
                    
                    currentPos = newPos;
                    
                }
                
                break;
                
            } else if (position.x > newPixelPos && position.x < currentPixelPos) {
                
                if (position.x-newPixelPos < currentPixelPos-position.x) {
                    
                    currentPos = newPos;
                    
                }
                
                break;
                
            } else {
                
                currentPos = newPos;
                
            }
            
        }
        
        setCaretPos(visibleStart+currentPos);
        redraw();
                
    }
    
    public boolean capturesMouse() {
        
        return true;
        
    }
        
    public void mousePress(Point position, int button) {
        
        super.mousePress(position, button);
        
        if (!selected) {
        
            KeyboardManager.getSingleton().setState(new TextBoxState(this));
            
        }
        
        selecting = true;
        setCaretPos(position);
        selectionStart = caretPos;
        getMouseFocus();
        
    }
    
    public void mouseMoving(Point position) {
        
        super.mouseMoving(position);
        
        if (isMouseDown()) {
        
            setCaretPos(position);
            
        }
        
    }
    
    public void mouseRelease(Point position, int button) {
        
        super.mouseRelease(position, button);
        selecting = false;
        releaseMouseFocus();
        
    }
    
    private int getPixelPos(int textPos) {
        
        if (textPos >= 0 && textPos <= numberOfVisibleCharacters()) {
            
            return evilHack.textExtent(visibleText.substring(0, textPos)).x+7;
            
        } else if (textPos > numberOfVisibleCharacters()) {
            
            return evilHack.textExtent(visibleText.substring(0, numberOfVisibleCharacters())).x+7;
            
        }
        
        return 7;
        
    }
    
    public void select() {
        
        selected = true;
        caret.permanentlyShow();
        redraw();
        
        if (selectListener != null) {
            
            selectListener.onSelect(this);
            
        }
        
    }
    
    public void deselect() {
        
        selected = false;
        selecting = false;
        selectionStart = 0;
        selectionEnd = 0;
        caret.permanentlyHide();
        redraw();
        
        if (deselectListener != null) {
            
            deselectListener.onDeselect(this);
            
        }
        
    }
    
    public void drawBefore(GC gc, Rectangle redrawArea) {
        
        if (selected) {
        
            Utils.drawBox(gc, new Rectangle(0-redrawArea.x, 0-redrawArea.y, getSize().x, getSize().y), EDIT_BOX_SKIN);
            
        } else {
            
            Utils.drawBox(gc, new Rectangle(0-redrawArea.x, 0-redrawArea.y, getSize().x, getSize().y), TRANSPARENT_EDIT_BOX_SKIN);
            
        }
		
		gc.setFont(font);
		Utils.drawText(gc, visibleText, new Rectangle(7-redrawArea.x, -redrawArea.y, area.width-10, area.height-4), Utils.ALIGN_LEFT);
		
		if (selectionStart != selectionEnd) {
		    
		    int selectionPixelStart;
		    
		    if (selectionStart < visibleStart) {
		        
		        selectionPixelStart = 7;
		        
		    } else if (selectionStart > visibleStart+numberOfVisibleCharacters()) {
		        
		        selectionPixelStart = getPixelPos(visibleStart+numberOfVisibleCharacters());
		        
		    } else {
		        
		        selectionPixelStart = getPixelPos(selectionStart-visibleStart);
		        
		    }
		    
		    int selectionPixelEnd;
		    
		    if (selectionEnd < visibleStart) {
		        
		        selectionPixelEnd = 7;
		        
		    } else if (selectionEnd > visibleStart+numberOfVisibleCharacters()) {
		        
		        selectionPixelEnd = getPixelPos(visibleStart+numberOfVisibleCharacters());
		        
		    } else {
		        
		        selectionPixelEnd = getPixelPos(selectionEnd-visibleStart);
		        
		    }
		    
			gc.setXORMode(true);
			gc.setForeground(ColorCache.get(255, 255, 255));
			gc.setBackground(ColorCache.get(255, 255, 255));
			gc.fillRectangle(selectionPixelStart, 2, selectionPixelEnd-selectionPixelStart, 12);
			gc.setXORMode(false);
					    
		}
		
/*		Point p = gc.textExtent(visibleText.substring(0, caretPos-visibleStart));
		gc.drawLine(7+p.x-redrawArea.x, -redrawArea.y, 7+p.x-redrawArea.x, p.y-redrawArea.y); */
        
    }

    public void lostOwnership(Clipboard arg0, Transferable arg1) {
        
    }
    
}
