/*
 * Filename: StateStack.java
 *
 * Author: Joe Wang
 *
 * Copyright (c) 2003-2005 
 * The Trustees of Columbia University in the City of New York
 * Columbia University Information Technology
 *
 * This file is part of NINJa.
 *
 * NINJa 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 2 of the License, or
 * (at your option) any later version.
 *
 * NINJa 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with NINJa; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * Created on September 25, 2003, 11:38 AM
 */

package edu.columbia.print.ninja.prix.display;

import java.awt.*;
import java.util.*;
import javax.swing.*;
import com.millipad.util.*;

/**
 *
 * @author  joe
 */
public class StateStack implements Runnable {

    private Map states;
    private Vector pushes, pops;
    private BlockingStack stack, resultStack;
    
    /** Creates a new instance of StateStack */
    public StateStack() {
        states = new HashMap();
        pushes = new Vector();
        pops = new Vector();
        stack = new BlockingStack();
        resultStack = new BlockingStack();
        new Thread(this).start();
    }
    
    public void addState(Object state, JComponent focus, JComponent[] shows, JComponent[] hides, Object[] origin) {
        states.put(state, new State(focus, shows, hides, origin));
    }
    
    public synchronized Object currentState() {
        return pushes.get(0);
    }

    private boolean pushIn(Object id) {
        State state = (State )states.get(id);
        if(state==null) return false;
        int pushCount = countStates(id, pushes);
        int popCount = countStates(id, pops);
        if(pushCount<popCount) {    //pop
            pops.remove(id);
            return true;
        }
        //see if acceptable from this state
        Object top = null;
        if(pushes.size()>0) top = pushes.get(0);
        if(top!=null && !state.canChange(top)) {
            return false;
        }
        pushes.add(0, id);
        if(top!=null) {
            State ts = (State )states.get(top);
            try {
                for(;!ts.isComplete();) Thread.sleep(100);
            }
            catch(Exception e) {}
        }
        state.change();
        System.out.println(top+" > "+id);
        return true;
    }
    
    private void popIn(Object id) {
        Object top = null;
        if(pushes.size()>0) top = pushes.get(0);
        for(int i=0; i<pushes.size(); i++) {
            if(!id.equals(pushes.get(i))) continue;
            pushes.remove(i);
            if(i!=0) return;
            if(pushes.size()==0) return;
            State state = (State )states.get(pushes.get(0));
        if(top!=null) {
            State ts = (State )states.get(top);
            try {
                for(;!ts.isComplete();) Thread.sleep(100);
            }
            catch(Exception e) {}
        }
            state.change();
            System.out.println(pushes.get(0)+" < "+id);
            return;
        }
//        pops.add(0, id);
    }

    int countStates(Object id, Vector stack) {
        Vector tmp = new Vector(stack);
        int before = tmp.size();
        Vector tmp2 = new Vector();
        tmp2.add(id);
        tmp.removeAll(tmp2);
        return before-tmp.size();
    }

    public synchronized boolean push(Object in) {
        stack.push(new Object[] {new Integer(1), in});
        boolean[] result = (boolean[] )resultStack.pop();
        return result[0];
    }
    
    public synchronized void pop(Object in) {
        stack.push(new Object[] {new Integer(-1), in});
    }

    public void run() {
        Integer pushI = new Integer(1);
        for(;;) {
            Object[] obj = (Object[] )stack.pop();
            if(pushI.equals(obj[0])) {
                boolean result = pushIn(obj[1]);
                resultStack.push(new boolean[] {result});
            }
            else popIn(obj[1]);
        }
    }
    
    class State implements java.awt.event.FocusListener {
        Set shows, hides, origins;
        JComponent focus;
        boolean gotFocus;
        State(JComponent focus, JComponent[] shows, JComponent[] hides, Object[] origins) {
            this.shows = new HashSet();
            this.hides = new HashSet();
            this.origins = new HashSet();
            this.focus = focus;
            if(shows!=null) for(int i=shows.length-1; i>=0; i--) this.shows.add(shows[i]);
            if(hides!=null) for(int i=hides.length-1; i>=0; i--) this.hides.add(hides[i]);
            if(origins!=null) for(int i=origins.length-1; i>=0; i--) this.origins.add(origins[i]);
            focus.addFocusListener(this);
        }
        boolean canChange(Object current) {
            return origins.contains(current);
        }
        synchronized void change() {
            for(Iterator i=hides.iterator(); i.hasNext();) {
                JComponent c = (JComponent )i.next();
                c.setVisible(false);
            }
            for(Iterator i=shows.iterator(); i.hasNext();) {
                JComponent c = (JComponent )i.next();
                c.setVisible(true);
            }
            focus.setVisible(true);
            gotFocus = false;
            focus.requestFocus();
        }

        public synchronized boolean isComplete() {return gotFocus;}
        public synchronized void focusGained(java.awt.event.FocusEvent e) {
            gotFocus = true;
        }
        
        public void focusLost(java.awt.event.FocusEvent e) {
        }
        
    }
}
