package cnp.ew.lightweight;

import java.awt.*;
import java.applet.*;
import java.util.*;
import cnp.ew.util.*;
import cnp.ew.text.*;
import cnp.ew.misc.*;

public class CpLcPanel extends Panel implements CpTimerUser
{
    CpLightweightComponent lc;

    boolean haveFocus;
    CpLightweightComponent gotsFocus, wantsFocus;
    Hashtable componentToLcDict;


    CpLightweightComponent mouseTarget;

    Rectangle damagedRect;
    int enableDamageRepairStack;
    boolean processingSystemKey;

    CpLightweightComponent poppedUpLc=null;

    // Ken added for flying tips
    Point lastMouseLocation = new Point(0, 0);
    CpFlyingTipLc tipTextLc=null;
    Dimension flyingTipOffset = new Dimension(0, 14);
    long lastPoppedDown = System.currentTimeMillis();

    static final int flyingTipTextPopUpInterval=800;
    static final int flyingTipTextImmediatePopUpInterval=200;

    public CpLcPanel()
    {
        super();
        damagedRect = new Rectangle(0,0,0,0);
        haveFocus = false;
        mouseTarget = lc;
        enableDamageRepairStack = 0;
        setLayout(null);
    }

    public void setLc(CpLightweightComponent newLc)
    {
        lc = newLc;
        if (lc != null) {
            lc.setComponent(this);
        }
    }

    public CpLightweightComponent getLc()
    {
        return lc;
    }

    // Cleverly (dangerously?) added so that we can say "add(lc)" to a component
    public void add(CpLightweightComponent lcChild)
    {
        if (lc == null) {
            setLc(new CpPanelLc());
        }
        lc.add(lcChild);
    }

    synchronized public void update(Graphics g)
    {
        paint(g);
    }

    synchronized public void paint(Graphics g)
    {
       //System.out.println("*******PAINT: " + g.getClipRect());
        lc.paintIn(this, null, g.getClipRect(), new Point(0, 0));

        if (poppedUpLc != null) {
            poppedUpLc.paintIn(this, null, g.getClipRect(), new Point(0, 0));
        }
    }

    synchronized public void damageGlobal(Rectangle r)
    {
        // We assume that when we show again, a full repaint
        // will occur.

        //System.out.println("[[ damaging globalRect: " + r);
        if (!isShowing()) {
            return;
        }

        // Add this rectangle to the damagedRect
        if ((damagedRect.width == 0) && (damagedRect.height == 0)) {
            damagedRect = new Rectangle(r.x, r.y, r.width, r.height);
        } else {
            damagedRect = damagedRect.union(r);
        }
        //System.out.println("]] finished damaging globalRect: " + r);
    }

    synchronized public void enableDamageRepair(boolean newEnableDamageRepair)
    {
        if (newEnableDamageRepair) {
            enableDamageRepairStack--;
            if (enableDamageRepairStack < 0) {
                System.out.println("ERROR:  enable damage repair without matching disable");
            }
        } else {
            enableDamageRepairStack++;
        }
    }

    public boolean isDamageRepairEnabled()
    {
        return enableDamageRepairStack <= 0;
    }

    synchronized public void repairDamage()
    {
        if (isValid() && enableDamageRepairStack <= 0) {
            //System.out.println("******REPAIR: " + damagedRect);
            executeRepairDamage();
        }
    }

    public void executeRepairDamage()
    {
        // TBD: Don't really understand this.  Evidentally, damagedRect is modified in paintIn, so copy it first.

        Rectangle r = new Rectangle(damagedRect.x, damagedRect.y, damagedRect.width, damagedRect.height);
        lc.paintIn(this, null, damagedRect, new Point(0, 0));

        if (poppedUpLc != null) {
            poppedUpLc.paintIn(this, null, r, new Point(0, 0));
        }

        clearDamagedRect();
    }

    synchronized public void clearDamagedRect()
    {
        damagedRect.x = 0;
        damagedRect.y = 0;
        damagedRect.width = 0;
        damagedRect.height = 0;
    }

    public void lcRequestFocus(CpLightweightComponent anLc)
    {
        if (haveFocus) {
        	if (gotsFocus != anLc) {
        	        CpLightweightComponent oldGotsFocus = gotsFocus;
        	        gotsFocus = anLc;
                	oldGotsFocus.lostFocus();
                	gotsFocus.gotFocus();
        	}
        } else {
            wantsFocus = anLc;
            requestFocus();
        }
    }

    public boolean gotFocus(Event evt, Object what)
    {
        // TBD: hack to deal with bad browser behavior.  We assume we always have the focus if we're in an applet.

        if (getParent() instanceof CpLcApplet && haveFocus) {
            return true;
        }

        haveFocus = true;

        if (gotsFocus != null) { System.out.println("Warning: gotFocus: illegal state."); }
        if (wantsFocus != null) {
            gotsFocus = wantsFocus;
            wantsFocus = null;
        } else {
            CpLightweightComponent c = lc.firstChildWantingFocus();
            if (c != null) {
                gotsFocus = c;
            } else {
                gotsFocus = lc;
            }
        }
       //System.out.println("GOT FOCUS:  set to: " + gotsFocus);
        return gotsFocus.gotFocus();
    }

    public boolean lostFocus(Event evt, Object what)
    {
        // TBD: hack to deal with bad browser behavior.  We assume we always have the focus if we're in an applet.
        if (getParent() instanceof CpLcApplet) {
            return true;
        }
        haveFocus = false;

        if (gotsFocus != null) {
            gotsFocus.lostFocus();
        }

        wantsFocus = gotsFocus;
        gotsFocus = null;
        return false;
    }

    public Dimension preferredSize()
    {
        return lc.preferredSize();
    }

    public void layout()
    {
        if (lc != null) {
            lc.reshape(0, 0, size().width, size().height);
        }
    }

    ////////////// USE THE FOCUS WIDGET,  AND FORE MOUSE, KEEP THE MOSUEWIDGET

// KEN ADDED: Changed to support bubbling up of keyboard events.
    public boolean keyUp(Event e, int key)
    {
        if (processingSystemKey) {
            return true;
        }

        CpLightweightComponent currentLc = gotsFocus;
        Vector lcChain = new Vector(3);
        while (currentLc != null) {
            lcChain.addElement(currentLc);
            currentLc = currentLc.getParent();
        }
        int chainLength = lcChain.size();
        for (int i = chainLength - 1 ; i >= 0; i--) {
            currentLc = (CpLightweightComponent)lcChain.elementAt(i);
            if (currentLc.parentKeyUp(e, e.key)) {
                return true;
            }
        }

        for (int i=0; i < chainLength; i++){
            currentLc = (CpLightweightComponent)lcChain.elementAt(i);
            if (currentLc.keyUp(e, e.key)) {
                return true;
            }
        }
        return false;
    }

// KEN ADDED: Changed to support bubbling up of keyboard events.
    public boolean keyDown(Event e, int key)
    {
        processingSystemKey = false;
        // TBD: control tab never seems to happen.
        if (key == 9 && (gotsFocus == null || !gotsFocus.wantsTab() || e.controlDown())) { // tab
            if (e.shiftDown()) {
                shiftFocusBackward();
            } else {
                shiftFocusForward();
            }
            processingSystemKey = true;
            return true;
        }

        if (key == 10 && (gotsFocus == null || !gotsFocus.wantsCr())) { // cr
            processingSystemKey = true;
            pushDefaultButton();
            return true;
        }


        if (key == 27 && (gotsFocus == null || !gotsFocus.wantsEscape())) { // escape
            processingSystemKey = true;
            pushCancelButton();
            return true;
        }

        if (key == Event.RIGHT && (gotsFocus == null || !gotsFocus.wantsLeftAndRightArrows())) {
            shiftFocusForwardWithinGroup();
            return true;
        }

        if (key == Event.LEFT && (gotsFocus == null || !gotsFocus.wantsLeftAndRightArrows())) {
            shiftFocusBackwardWithinGroup();
            return true;
        }


        CpLightweightComponent currentLc = gotsFocus;
        Vector lcChain = new Vector(3);
        while (currentLc != null) {
            lcChain.addElement(currentLc);
            currentLc = currentLc.getParent();
        }
        int chainLength = lcChain.size();
        for (int i = chainLength - 1 ; i >= 0; i--) {
            currentLc = (CpLightweightComponent)lcChain.elementAt(i);
            if (currentLc.parentKeyDown(e, e.key)) {
                return true;
            }
        }

        for (int i=0; i < chainLength; i++){
            currentLc = (CpLightweightComponent)lcChain.elementAt(i);
            if (currentLc.keyDown(e, e.key)) {
                return true;
            }
        }

        // No one handled them, see if we have a system key match.
        if (key == 9) { // tab
            if (e.shiftDown()) {
                shiftFocusBackward();
            } else {
                shiftFocusForward();
            }
            processingSystemKey = true;
            return true;
        }

        if (key == 10) { // cr
            processingSystemKey = true;
            pushDefaultButton();
            return true;
        }


        if (key == 27) { // escape
            processingSystemKey = true;
            pushCancelButton();
            return true;
        }

        return false;
    }

    public boolean mouseDrag(Event e, int x, int y)
    {
        lastMouseLocation.x = x;
        lastMouseLocation.y = y;

        if (mouseTarget != null) {
            return mouseTarget.mouseDrag(e, x - mouseTarget.locationGlobal().x, y - mouseTarget.locationGlobal().y);
        }
        return false;
    }

    public boolean mouseUp(Event e, int x, int y)
    {
        lastMouseLocation.x = x;
        lastMouseLocation.y = y;

        if (mouseTarget != null) {
            return mouseTarget.mouseUp(e, x - mouseTarget.locationGlobal().x, y - mouseTarget.locationGlobal().y);
        }
        return false;
    }

    void attemptToGiveFocus(CpLightweightComponent targetLc)
    {
        while (targetLc != null) {
            if (targetLc.usesFocus()) {
                lcRequestFocus(targetLc);
                return;
            }
            targetLc = targetLc.getParent();
        }
    }

    public boolean mouseDown(Event e, int x, int y)
    {
        lastMouseLocation.x = x;
        lastMouseLocation.y = y;
        killFlyingTipTextTimer();
        if (poppedUpLc != null && mouseTarget.getRootLc() != poppedUpLc) {
            popDownPoppedUpLc();
        }

        if (mouseTarget != null) {
            attemptToGiveFocus(mouseTarget);
            CpLightweightComponent currentLc = mouseTarget;
            Vector lcChain = new Vector(3);
            while (currentLc != null) {
                lcChain.addElement(currentLc);
                currentLc = currentLc.getParent();
            }
            int chainLength = lcChain.size();
            for (int i = chainLength - 1 ; i >= 0; i--) {
                currentLc = (CpLightweightComponent)lcChain.elementAt(i);
                if (currentLc.parentMouseDown(e, x - mouseTarget.locationGlobal().x, y - mouseTarget.locationGlobal().y)) {
                    return true;
                }
            }

            for (int i=0; i < chainLength; i++){
                currentLc = (CpLightweightComponent)lcChain.elementAt(i);
                if (currentLc.mouseDown(e, x - mouseTarget.locationGlobal().x, y - mouseTarget.locationGlobal().y)) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean mouseMove(Event e, int x, int y)
    {
        CpLightweightComponent temp = null;

        lastMouseLocation.x = x;
        lastMouseLocation.y = y;

    // Make sure only non tooltip text popups can get mouse input.
        if (poppedUpLc != null && tipTextLc == null) {
            temp = poppedUpLc.locateGlobal(x, y);
        }
        if (temp == null) {
            temp = lc.locateGlobal(x, y);
        }


        if (temp != mouseTarget) {
            popDownFlyingTipText();
            if (mouseTarget != null) {
                mouseTarget.mouseExit(e, x - mouseTarget.locationGlobal().x, y - mouseTarget.locationGlobal().y);
                mouseTarget.notifyObservers(CpEvent.HELP_TEXT_CHANGED, "");
            }
            mouseTarget = temp;
            if (mouseTarget != null) {
                mouseTarget.mouseEnter(e, x - mouseTarget.locationGlobal().x, y - mouseTarget.locationGlobal().y);
                String helpText = mouseTarget.getHelpText(mouseTarget.mouseLocation());
                if (helpText != null) {
                    mouseTarget.notifyObservers(CpEvent.HELP_TEXT_CHANGED, helpText);
                }
            }
        }

        startFlyingTipTextTimer();

        if (mouseTarget != null) {
            return mouseTarget.mouseMove(e, x - mouseTarget.locationGlobal().x, y - mouseTarget.locationGlobal().y);
        }
        return false;
    }

    public boolean mouseExit(Event e, int x, int y)
    {

        popDownPoppedUpLc();

        if (mouseTarget != null) {
            mouseTarget.mouseExit(e, x - mouseTarget.locationGlobal().x, y - mouseTarget.locationGlobal().y);
            mouseTarget.notifyObservers(CpEvent.HELP_TEXT_CHANGED, "");
            killFlyingTipTextTimer();
        }
        return false;
    }

    public void popUpLcAt(CpLightweightComponent newPopup, Rectangle globalRect)
    {
        if (poppedUpLc != null) {
            popDownPoppedUpLc();
        }

        poppedUpLc = newPopup;
        newPopup.setComponent(this);
        newPopup.enableDamageRepair(false);
        newPopup.reshape(globalRect.x, globalRect.y, globalRect.width, globalRect.height);
        newPopup.enableDamageRepair(true);
        damageGlobal(globalRect);
        repairDamage();

    }

    synchronized public void popDownPoppedUpLc()
    {
        if (poppedUpLc != null) {
            CpLightweightComponent temp = poppedUpLc;
            poppedUpLc = null;
            damageGlobal(temp.boundsGlobal());
            repairDamage();
            temp.notifyObservers(CpEvent.LC_POPPED_DOWN);
        }
        if (tipTextLc != null) {
            tipTextLc = null;
            lastPoppedDown = System.currentTimeMillis();
        }
    }

    void startFlyingTipTextTimer()
    {
        killFlyingTipTextTimer();
        if (mouseTarget != null) {
            CpTimer.getDefaultTimer().register(this, currentTipTextInterval());
        }
    }

    void killFlyingTipTextTimer()
    {
        CpTimer.getDefaultTimer().unregister(this);
    }

    public long timerEvent(int timerType) {
        popUpFlyingTipText();
        return 0;
    }

    int currentTipTextInterval()
    {
        if (System.currentTimeMillis() < lastPoppedDown + 5000) {
            return flyingTipTextImmediatePopUpInterval;
        } else {
            return flyingTipTextPopUpInterval;
        }
    }

    public Point mouseLocation()
    {
        return lastMouseLocation;
    }

    public void setFlyingTipOffset(Dimension tipOffset)
    {
        flyingTipOffset = tipOffset;
    }

    String getFlyingTipText()
    {
        String tipText = mouseTarget.getFlyingTipText();
        CpLightweightComponent curLc = mouseTarget;
        while (tipText == null && curLc != null) {
            curLc = curLc.getParent();
            if (curLc != null) {
                tipText = curLc.getFlyingTipText();
            }
        }
        return tipText;
    }


    public void popUpFlyingTipText()
    {
        // If another popup is up that isn't a tipText, don't pop up the flying tip.
        if (nonFlyingTipPoppedUp() || !haveFocus) {
            return;
        }

        popDownPoppedUpLc();

        String tipText = getFlyingTipText();
        if (tipText == null) {
            return;
        }

        tipTextLc = new CpFlyingTipLc(tipText);
        tipTextLc.setDrawOffscreen(true);
        Dimension preferredSize = tipTextLc.preferredSize();
        Dimension size = size();
        int popUpMargin = 8;
        Rectangle rect = new Rectangle(lastMouseLocation.x + flyingTipOffset.width, lastMouseLocation.y + flyingTipOffset.height, preferredSize.width, preferredSize.height);

        // if possible, put it below the mouse, aligned to the left

        if (rect.x + preferredSize.width + popUpMargin > size.width) {
            rect.x = size.width - preferredSize.width - popUpMargin;
        }

        if (rect.y + preferredSize.height + popUpMargin > size.height) {
            rect.y = lastMouseLocation.y - preferredSize.height - popUpMargin;
        }
        popUpLcAt(tipTextLc, rect);
    }

    public void popDownFlyingTipText()
    {
        if (tipTextLc != null) {
            popDownPoppedUpLc();
        }
    }

    boolean nonFlyingTipPoppedUp()
    {
        return tipTextLc == null && poppedUpLc != null;
    }

    public boolean hasFocus(CpLightweightComponent anLc)
    {
        return haveFocus && gotsFocus == anLc;
    }

    public void setCursor(int cursorId)
    {
        getFrame().setCursor(cursorId);
    }

    public Insets insets()
    {
        if (lc != null) {
            return lc.insets();
        }
        return super.insets();
    }


    public void addRealComponent(Component c, CpLightweightComponent lc)
    {
        add(c);
        if (componentToLcDict == null) {
            componentToLcDict = new Hashtable();
        }
        componentToLcDict.put(c, lc);
    }

    public void removeRealComponent(Component c)
    {
        remove(c);
        componentToLcDict.remove(c);
        if (componentToLcDict.size() == 0) {
            componentToLcDict = null;
        }
    }


    public boolean handleEvent(Event e)
    {
        boolean result = super.handleEvent(e);
        if (e.target != this && componentToLcDict != null) {
            CpComponentLc componentLc = (CpComponentLc)componentToLcDict.get(e.target);
            componentLc.handleEvent(e);
        }
        return result;
    }

    public synchronized Graphics getGraphics()
    {
        return super.getGraphics();
    }

    public Frame getFrame()
    {
        Container parent = getParent();

        while (parent != null) {
            if (parent instanceof Frame) {
                return (Frame)parent;
            }
            parent = parent.getParent();
        }
        return null;
    }

    void shiftFocusForward()
    {
        if (gotsFocus != null) {
            gotsFocus.shiftFocusForward();
        }
    }

    void shiftFocusForwardWithinGroup()
    {
        if (gotsFocus != null) {
            gotsFocus.shiftFocusForwardWithinGroup();
        }
    }

    void shiftFocusBackward()
    {
        if (gotsFocus != null) {
            gotsFocus.shiftFocusBackward();
        }
    }


    void shiftFocusBackwardWithinGroup()
    {
        if (gotsFocus != null) {
            gotsFocus.shiftFocusBackwardWithinGroup();
        }
    }


    void pushDefaultButton()
    {
        if (lc != null) {
            lc.pushDefaultButton();
        }
    }

    void pushCancelButton()
    {
        if (lc != null) {
            lc.pushCancelButton();
        }
    }
}

