/*
 * Filename: Gui.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 August 6, 2002, 1:22 PM
 */

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

import javax.swing.table.*;
import edu.columbia.print.ninja.core.service.*;
import edu.columbia.print.ninja.prix.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.text.html.HTMLDocument;
import javax.print.attribute.PrintRequestAttributeSet;
import edu.columbia.print.ninja.core.event.PrintQueueEvent;
import net.jini.core.event.*;

import javax.swing.border.*;
import java.io.*;
import java.nio.*;
import java.awt.*;
import net.jini.print.service.PrintServiceEvent;
import edu.columbia.print.ninja.core.authenticate.AuthStub;
import java.rmi.server.RemoteObject;
import net.jini.core.event.RemoteEventListener;
import javax.swing.*;
import java.awt.event.*;
import edu.columbia.print.ninja.core.service.RequestQueue;
import java.net.*;
import java.util.*;
import javax.print.attribute.standard.*;
import net.jini.print.job.*;
import java.nio.channels.*;
import javax.security.auth.callback.*;
import net.jini.core.lease.*;
import net.jini.lease.*;
import net.jini.core.event.*;
import net.jini.core.entry.*;
import net.jini.core.lookup.ServiceItem;
import com.millipad.util.*;
import com.millipad.teahouse.TPane;
import java.io.*;
import org.xml.sax.*;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;

/**
 *
 * @author  joe
 */
public class Gui extends javax.swing.JPanel
implements Serializable, Runnable, RemoteEventListener, LeaseListener, javax.swing.event.ListSelectionListener,WebServer.Callback {
    RemoteEventListener myListener;
    transient StateStack stateStack;
    Rectangle topBounds;

    /** Creates new form Gui */
    public Gui() throws Exception {
        initComponents();
    }
    
    public Gui(Object obj) throws Exception {
        if(obj==null) return;
        timer = new java.util.Timer(true);
        try {
            getAccessibleContext().setAccessibleName("remote");
            ServiceItem item = (ServiceItem )obj;
            service = (Station )item.service;
            initComponents();
            init();
        }
        catch(Exception e) {
            e.printStackTrace();
        }
    }

    public void registerListeners() throws Exception {
        try {
            for(int i=jobTable.getRowCount()-1; i>=0; i--) {
                Object[] tmp = (Object[] )jobTable.getValueAt(i, 0);
                garbage.push(new Object[] {tmp[0], new Integer(0)});
            }
        }
        catch(Exception e) {
            e.printStackTrace();
        }
        reg = new EventRegistration[2];
        reg[0] = queue.addEventListener(null, new edu.columbia.print.ninja.core.event.PrintQueueEvent[0], myListener, null, Lease.FOREVER);
        reg[1] = queue.addEventListener(null, new javax.print.attribute.PrintServiceAttribute[0], myListener, null, Lease.FOREVER);
    }

    void init() throws Exception {
        try {java.rmi.server.UnicastRemoteObject.exportObject(this);}
        catch(Exception e) {System.out.println(e);}
        isLocal = service.isLocal((java.rmi.Remote )this);
        if(isLocal) {
            System.out.println("local");
            queue = service.getQueue(true);
            myListener = (RemoteEventListener )this;
            //start webserver
            Stack threadPool = new Stack();
            for(int i=10; i>0; i--) threadPool.push(new java.util.Timer());
            webServer = new WebServer(this, threadPool);
        }
        else {
            System.out.println("remote");
            queue = service.getQueue(false);
            myListener = (RemoteEventListener )java.rmi.server.RemoteObject.toStub(this);
        }
        registerListeners();
        setSequenceNumber(reg[0].getSequenceNumber());
        stateStack = new StateStack();
        stateStack.addState(REBOOT_STATE, rebootPanel, null, null, new Object[] {SELECT_STATE});
        stateStack.addState(SELECT_STATE, jobTable, new JComponent[] {}, new JComponent[] {rebootPanel,authPanel,errorPanel,statusPanel}, new Object[] {});
        stateStack.addState(VERIFY_STATE, authPanel, null, new JComponent[] {password,user,errorPanel}, new Object[] {SELECT_STATE});
        stateStack.addState(AUTH_STATE, user, new JComponent[] {password,authPanel}, new JComponent[] {statusPanel}, new Object[] {VERIFY_STATE});
        stateStack.addState(LOGIN_ERROR_STATE, errorPanel, null, new JComponent[] {authPanel}, new Object[] {VERIFY_STATE});
        stateStack.addState(PRINT_STATE, statusPanel, null, new JComponent[] {authPanel,errorPanel}, new Object[] {SELECT_STATE,VERIFY_STATE,AUTH_STATE,LOGIN_ERROR_STATE,SELECT_STATE});
        pjSeqNos = new HashMap();

        garbage = new BlockingQueue();
        garbageCollector = new GarbageCollector();
        new Thread(garbageCollector).start();
        authRenewMgr = new LeaseRenewalManager();
        statusRenewMgr = new LeaseRenewalManager();
        jobTable.getSelectionModel().addListSelectionListener(this);
        new Thread(this).start();
    }

    public void start() {
        pageRefresh = new PageRefresh();
        timer.schedule(pageRefresh, 0L);
        stateStack.push(SELECT_STATE);
    }

    public EventRegistration[] getEventRegistrations() {
        return reg;
    }
    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    private void initComponents() {
        setLayout(new java.awt.BorderLayout());
        try {
            mainPane = new TPane();
            authPanel = new TPane();
            rebootPanel = new TPane();
            errorPanel = new TPane();
            statusPanel = new TPane();
        }
        catch(Exception e) {}
        user = new javax.swing.JTextField();
        password = new javax.swing.JPasswordField();
        progress = new javax.swing.JProgressBar();
        status = new javax.swing.JLabel();
        jobName = new javax.swing.JLabel();
        uni = new javax.swing.JLabel();
        printerPicture = new javax.swing.JLabel();
        authError = new javax.swing.JLabel();
        authLabel = new javax.swing.JLabel();
        jobTable = new javax.swing.JTable();
        jobTable.setName("table");
        jobTable.requestFocus(true);

        final JTable inactiveTable = new javax.swing.JTable(0, 1);
        jobTable.setModel(new javax.swing.table.DefaultTableModel(
            new Object [0][],
            new String [] {"", "", "", "", ""}
        ) {
            public boolean isCellEditable(int rowIndex, int columnIndex) {
                return false;
            }
        });
        jobTable.setVerifyInputWhenFocusTarget(false);
        jobTable.getColumnModel().getColumn(0).setCellRenderer( new javax.swing.table.TableCellRenderer() {     public java.awt.Component getTableCellRendererComponent(javax.swing.JTable t, Object v, boolean s, boolean f, int r, int c) { 	return jobTable.getDefaultRenderer(java.lang.Object.class).getTableCellRendererComponent(t, (r+1)+"", s, f, r, c);   } } );
        TableCellRenderer tcRenderer = new javax.swing.table.TableCellRenderer() {
            public java.awt.Component getTableCellRendererComponent(javax.swing.JTable t, Object v, boolean s, boolean f, int r, int c) {
                Object[] vect = (Object[] )jobTable.getModel().getValueAt(r, 0);
                if(vect==null) return null;
                java.awt.Component comp;
                if(((Long )vect[1]).longValue()==PrintQueueEvent.REQUEST_ADDING) {
                    comp = jobTable.getDefaultRenderer(java.lang.Object.class).getTableCellRendererComponent(inactiveTable, v, s, f, 0, 0);
                }
                else comp = jobTable.getDefaultRenderer(java.lang.Object.class).getTableCellRendererComponent(t, v, s, f, r, c);
                return comp;
            }
        };
        jobTable.getColumnModel().getColumn(3).setCellRenderer(tcRenderer);
        for(ActionMap am = jobTable.getActionMap(); am!=null; am=am.getParent()) {
            am.remove("selectNextRowCell");
            if(am.get("selectNextRowCell")==null) break;
        }
        jobTable.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                jobTableKeyPressed(evt);
            }
        });

        printerPicture.setIcon(new javax.swing.ImageIcon(getClass().getResource("/edu/columbia/print/ninja/prix/display/ecioffln.gif")));
        statusPanel.setVisible(false);
        statusPanel.add(progress).setName("progress");
        statusPanel.add(status).setName("status");
        statusPanel.add(jobName).setName("job");
        statusPanel.add(uni).setName("uni");
        statusPanel.add(printerPicture).setName("statusImage");

        errorPanel.setVisible(false);
        errorPanel.add(authError).setName("error");

        authPanel.setFocusCycleRoot(true);
        authPanel.setVisible(false);
        authPanel.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                authPanelKeyPressed(evt);
            }
        });
        user.setVerifyInputWhenFocusTarget(false);
        user.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                userKeyPressed(evt);
            }
        });
        password.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                passwordKeyPressed(evt);
            }
        });
        authPanel.add(user).setName("user");
        authPanel.add(password).setName("password");
        authPanel.add(authLabel).setName("title");

        rebootPanel.setFocusCycleRoot(true);
        rebootPanel.setVisible(false);
        rebootPanel.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                rebootPanelKeyPressed(evt);
            }
        });

        mainPane.add(inactiveTable).setName("unavailable table");
        mainPane.add(new JScrollPane(jobTable)).setName("scroll");
        mainPane.add(authPanel, javax.swing.JLayeredPane.MODAL_LAYER);
        mainPane.add(errorPanel, javax.swing.JLayeredPane.MODAL_LAYER);
        mainPane.add(statusPanel, javax.swing.JLayeredPane.MODAL_LAYER);
        mainPane.add(rebootPanel, javax.swing.JLayeredPane.POPUP_LAYER);
        try {
            mainPane.add(
                new javax.swing.JLabel(java.net.InetAddress.getLocalHost().getHostName())
            ).setName("hostname");
        }
        catch(Exception e) {
            mainPane.add(new javax.swing.JLabel("NINJa")).setName("hostname");
        }

        add(mainPane, java.awt.BorderLayout.CENTER);


    }
    
    private void authPanelKeyPressed(java.awt.event.KeyEvent evt) {
        switch(evt.getKeyCode()) {
            case KeyEvent.VK_ESCAPE:
                cancelAuth(null);
                break;
        }
    }
    
    private void rebootPanelKeyPressed(java.awt.event.KeyEvent evt) {
        switch(evt.getKeyCode()) {
            case KeyEvent.VK_ESCAPE:
                stateStack.pop(REBOOT_STATE);
                break;
            case KeyEvent.VK_ENTER:
                System.exit(-1);
        }
    }
    
    private void passwordKeyPressed(java.awt.event.KeyEvent evt) {
        userKeyPressed(evt);
    }
    
    private void userKeyPressed(java.awt.event.KeyEvent evt) {
        // Add your handling code here:
        switch(evt.getKeyCode()) {
            case KeyEvent.VK_ESCAPE:
                cancelAuth(null);
                break;
            case KeyEvent.VK_ENTER:
                stateStack.pop(AUTH_STATE);
                synchronized(passwordSync) {
                    collectingInput = true;
                    passwordSync.notify();
                }
                break;
        }
    }
    
    private void jobTableKeyPressed(java.awt.event.KeyEvent evt) {
        Object[] v = null;
        switch(evt.getKeyCode()) {
            case KeyEvent.VK_ENTER:
                int[] rows = jobTable.getSelectedRows();
                PrintRequest firstReq = null;
                RequestingUserName requester = null;
                //remove non matching items
                for(int i=0; i<rows.length; i++) {
                    v = (Object[] )jobTable.getValueAt(rows[i], 0);
                    if(((Long )v[1] ).longValue()!=PrintQueueEvent.REQUEST_ADDED) continue;
                    PrintRequest req = (PrintRequest )v[0];
                    RequestingUserName name = null;
                    try {
                        String userName = jobTable.getValueAt(rows[i], 1)+"";
                        userName += "@"+jobTable.getValueAt(rows[i], 2);
                        name = new RequestingUserName(userName, null);
                        req.getAttributes().add(name);
                    }
                    catch(Exception e) {e.printStackTrace();}
                    if(firstReq==null) {
                        firstReq = req;
                        requester = name;
                        continue;
                    }
//                    System.out.println(requester+" "+name);
                    if(requester==null && name==null) continue;
                    if(requester==null && rows[i]>0) {
                        jobTable.getSelectionModel().removeSelectionInterval(rows[i], rows[i]);
                        continue;
                    }
                    if(!requester.equals(name) && rows[i]>0) jobTable.getSelectionModel().removeSelectionInterval(rows[i], rows[i]);
                }
                rows = jobTable.getSelectedRows();
                if(rows==null || rows.length==0) break;
                requests = new PrintRequest[rows.length];
                for(int i=0; i<rows.length; i++) {
                    v = (Object[] )jobTable.getValueAt(rows[i], 0);
                    if(((Long )v[1] ).longValue()!=PrintQueueEvent.REQUEST_ADDED) continue;
                    requests[i] = (PrintRequest )v[0];
                }
                synchronized(passwordSync) {
                    collectingInput = false;
                    passwordSync.notify();
                }
                synchronized(this) {notify();}
                break;
            case KeyEvent.VK_F1:
                stateStack.push(REBOOT_STATE);
                break;
        }
    }
    
    private synchronized void cancelAuth(Lease lease) {
        //        if(!authPanel.isVisible()) return;
        synchronized(passwordSync) {
            collectingInput = false;
            passwordSync.notify();
            if(lease==null) lease = authLease;
            if(lease!=null) {
                try {
                    authRenewMgr.cancel(lease);
                    System.out.println(user.getText()+" canceled auth");
                }
                catch(Exception e) {
                    System.out.println("cancelAuth:"+e);
                }

            }
        }
            stateStack.pop(AUTH_STATE);
            stateStack.pop(VERIFY_STATE);
    }

    String getJobName(JobName file, long type) {
        String secureName = (file==null) ? "" : file.toString();
        int index;
        index = secureName.indexOf("http://");
        if(index>=0) {
            //lpr translates ? of url params into _
            int lastIndex = secureName.indexOf("_", index);
            if(lastIndex>=0) secureName = secureName.substring(0, lastIndex);
        }
        index = secureName.indexOf("ftp://");
        if(index>=0) {
            //lpr translates ? of url params into _
            int lastIndex = secureName.indexOf("@", index);
            if(lastIndex>=0) secureName = secureName.substring(lastIndex);
        }
        if(type==PrintQueueEvent.REQUEST_ADDING) secureName = "Processing... "+secureName;
        return secureName;
    }

    synchronized void addRequest(PrintQueueEvent event, long type) throws java.rmi.RemoteException, java.rmi.NoSuchObjectException {
        EventRegistration reg = null;
        for(int i=jobTable.getRowCount()-1; i>=0; i--) {
            Object[] tmp = (Object[] )jobTable.getValueAt(i, 0);
            if(tmp==null) continue;
            if(tmp[0].equals(event.getPrintRequest())) {
                if(((Long )tmp[1]).longValue()==PrintQueueEvent.REQUEST_ADDED) return;
                tmp[1] = new Long(type);
                PrintRequestAttributeSet prattr = (event.getPrintRequest()).getAttributes();
                jobTable.setValueAt(getJobName((JobName )prattr.get(JobName.class), type), i, 3);
                return;
            }
        }
        long expiration = 0L;
        try {
            expiration = queue.getRequestExpiration(event.getPrintRequest());
        }
        catch(java.rmi.RemoteException rexc) {
            PrintRequestAttributeSet prattr = (event.getPrintRequest()).getAttributes();
            System.out.println(prattr.get(JobName.class)+" | "+rexc.getMessage());
        }
        expiration = expiration-System.currentTimeMillis()-10*1000;
//        if(expiration<=0) return;
        if(isLocal) timer.schedule(new Deleter(event.getPrintRequest()), expiration);
        
        PrintRequestAttributeSet prattr = (event.getPrintRequest()).getAttributes();
        RequestingUserName user = null;
        JobName file = null;
        String secureName = null;
        JobMediaSheets sheets = null;;
        String host=null;
        if(prattr!=null) {
            user = (RequestingUserName )prattr.get(RequestingUserName.class);
            file = (JobName )prattr.get(JobName.class);
            secureName = getJobName(file, type);
            sheets = (JobMediaSheets )prattr.get(JobMediaSheets.class);
        }
        if(user!=null) {
            //parse user
            StringTokenizer t = new StringTokenizer(user.getValue(), "@");
            user = new RequestingUserName(t.nextToken(), null);
            //parse host
            if(t.hasMoreTokens()) host = t.nextToken();
        }
        ((DefaultTableModel )jobTable.getModel()).addRow(new Object[] {
            new Object[] {event.getPrintRequest(), new Long(type)},
            user, host, secureName, sheets, null, null
        });
        int row = jobTable.getSelectedRow();
        if(row<0) row=0;
        jobTable.getSelectionModel().clearSelection();
        jobTable.getSelectionModel().setSelectionInterval(row,row);
        pqSeqNo = event.getSequenceNumber();
    }
    
    synchronized void removeRequest(PrintQueueEvent event) {
        //cancel the registration lease
        //find row
        garbage.push(new Object[] {event.getPrintRequest(), new Integer(0)});
        pqSeqNo = event.getSequenceNumber();
    }
    
    void removeRow(int i) {
        ((DefaultTableModel )jobTable.getModel()).removeRow(i);
        if(jobTable.getSelectedRow()>=0) return;
        if(jobTable.getRowCount()<0) return;
        if(jobTable.getRowCount()<=i) jobTable.getSelectionModel().setSelectionInterval(jobTable.getRowCount()-1,jobTable.getRowCount()-1);
        else jobTable.getSelectionModel().setSelectionInterval(i,i);
    }
    
    
    public synchronized void completeJob(PrintRequest handback, String text, String gif, long hold, boolean remove) throws Exception {
        if(isSticky) return;
        System.out.println("complete:("+new Date()+") "+text);
        if(!remove) isSticky = true;
        printerPicture.setIcon(new javax.swing.ImageIcon(getClass().getResource("/edu/columbia/print/ninja/prix/display/"+gif)));
        progress.setString("");
        advanceProgress(100);
        if(remove) {
            garbage.push(new Object[] {handback, new Integer(0)});
            queue.requestRemove(
            new PrintRequest[] {handback},
            1000
            );
        }
        status.setText(text);
        Thread.sleep(hold);
        if(!remove) isSticky = false;
        lastJobTime = System.currentTimeMillis();
        stateStack.pop(PRINT_STATE);
        stateStack.pop(VERIFY_STATE);
    }
    
    synchronized void advanceProgress(int value) {
        if(progress.getValue()>=value) return;
        try {
            for(int k=progress.getValue()+1; k<value; k++) {
                progress.setValue(k);
                Thread.sleep(10);
            }
        }
        catch(Exception e) {}
        progress.setValue(value);
    }
    
    public void notify(net.jini.core.event.RemoteEvent evt) throws net.jini.core.event.UnknownEventException, java.rmi.RemoteException {
        if(evt instanceof PrintQueueEvent) {
            PrintQueueEvent event = (PrintQueueEvent )evt;
            if((event.getSequenceNumber()!=pqSeqNo) && (event.getSequenceNumber()!=pqSeqNo+1)) {
                System.out.println("missing sequences "+pqSeqNo+" "+event.getSequenceNumber());
                pqSeqNo = event.getSequenceNumber();
            }
            //job_added
            switch((int )event.getID()) {
                case (int )PrintQueueEvent.REQUEST_ADDED:
                case (int )PrintQueueEvent.REQUEST_ADDING:
                    addRequest(event, event.getID());
                    break;
                case (int )PrintQueueEvent.REQUEST_REMOVED:
                    removeRequest(event);
            }
            return;
        }
        if(evt instanceof PrintJobEvent) {
//            if(state[0]!=PRINT_STATE) return;
            stateStack.push(PRINT_STATE);
            Object handback = null;
            try {
                handback = evt.getRegistrationObject().get();
                if(!handback.equals(currentHandback)) {
                    currentHandback = handback;
                    printerPicture.setIcon(new javax.swing.ImageIcon(getClass().getResource("/edu/columbia/print/ninja/prix/display/eci_ok.gif")));
                    status.setText(" ");
                    status.setForeground(Color.black);
                    progress.setValue(0);
                    progress.setString("");
                }
                for(int i=jobTable.getRowCount()-1; i>=0; i--) {
                    Object[] tmp = (Object[] )jobTable.getValueAt(i, 0);
                    if(tmp==null) continue;
                    if(!tmp[0].equals(handback)) continue;
                    //                                        advanceProgress(10);
                    //					if(progress.getValue()==0) progressprogress.setValue(10);
                    PrintJobEvent event = (PrintJobEvent )evt;
                    javax.print.attribute.Attribute[] attrs = null;
                    attrs = event.getAttributes().toArray();
                    for(int j=attrs.length-1; j>=0; j--) {
                        if(attrs[j] instanceof JobState) {
                            if(JobState.COMPLETED.equals(attrs[j])) {
                                status.setForeground(Color.black);
                                completeJob((PrintRequest )handback, "complete", "eci_ok.gif", 700, true);
                                return;
                            }
                            else if(JobState.ABORTED.equals(attrs[j])) {
                                status.setForeground(Color.red);
                                String msg, icon;
                                if(event.getAttributes().containsValue(JobStateReason.DOCUMENT_FORMAT_ERROR)) {
                                    msg = "Reboot the HP printer and try again";
                                    icon = "ecirboot.gif";
                                }
                                else {
                                    msg = "Printing Cancelled";
                                    icon = "ecigener.gif";
                                }
                                completeJob((PrintRequest )handback, msg, icon, 15*1000, false);
                                return;
                            }
                            else if(!JobState.PROCESSING_STOPPED.equals(attrs[j])) {
                                status.setForeground(Color.black);
                                status.setText("processing");
                            }
                            else if(!JobState.PENDING_HELD.equals(attrs[j]) && !JobState.PROCESSING_STOPPED.equals(attrs[j])) {
                                progress.setString("");
                                //                                                                advanceProgress(50);
                            }
                            else if(JobState.PENDING_HELD.equals(attrs[j])) {
                                status.setForeground(Color.black);
                            }
                            else if(JobState.PROCESSING_STOPPED.equals(attrs[j])) {
                                status.setForeground(Color.red);
                                status.setText("processing stopped");
                            }
                            else {
                                status.setForeground(Color.black);
System.out.println("attr "+attrs[j]);
                                printerPicture.setIcon(new javax.swing.ImageIcon(getClass().getResource("/edu/columbia/print/ninja/prix/display/eci_ok.gif")));
                            }
                            continue;
                        }
                        if(attrs[j] instanceof javax.print.attribute.standard.JobOriginatingUserName) {
                            //							jobTable.setValueAt(attrs[j], i, 1);
                            //							jobTable.setValueAt(null, i, 2);
                            uni.setText(attrs[j]+"");
                            continue;
                        }
                        if(attrs[j] instanceof JobMediaSheets) {
                            //							jobTable.setValueAt(attrs[j], i, 4);
                            continue;
                        }
                        if(attrs[j] instanceof JobMediaSheetsCompleted) {
                            //							jobTable.setValueAt(new JobMediaSheets(((JobMediaSheetsCompleted )attrs[j]).getValue()), i, 4);
                            continue;
                        }
                        if(attrs[j] instanceof JobKOctetsProcessed) {
                            JobKOctets octets = (JobKOctets )event.getAttributes().get(JobKOctets.class);
                            if(octets==null) continue;
                            try {
                                advanceProgress(((JobKOctetsProcessed )attrs[j]).getValue()*70/octets.getValue()+20);
                                progress.setString(((JobKOctetsProcessed )attrs[j]).getValue()+"/"+octets.getValue()+" Kb");
                                if(((JobKOctetsProcessed )attrs[j]).getValue()==octets.getValue()) {
                                    progress.setString("");
                                }
                                //								jobTable.setValueAt(pbar, i, 6);
                            }
                            catch(Exception e) {
                                e.printStackTrace();
                            }
                            continue;
                        }
                        if(attrs[j] instanceof JobStateReasons) {
                            JobStateReasons reasons;
                            reasons = (JobStateReasons )attrs[j];
                            if(reasons!=null && reasons.size()>0) {
                                if(reasons.contains(JobStateReason.RESOURCES_ARE_NOT_READY)) advanceProgress(5);
                                if(reasons.contains(JobStateReason.JOB_QUEUED)) advanceProgress(10);
                                if(reasons.contains(JobStateReason.JOB_OUTGOING)) advanceProgress(15);
                                if(reasons.contains(JobStateReason.JOB_PRINTING)) advanceProgress(20);
                                if(reasons.contains(JobStateReason.PRINTER_STOPPED)) advanceProgress(90);
                                if(reasons.contains(JobStateReason.JOB_COMPLETED_SUCCESSFULLY)) advanceProgress(95);
                                if(reasons.contains(JobStateReason.ABORTED_BY_SYSTEM)) advanceProgress(95);
                                if(reasons.contains(JobStateReason.JOB_COMPLETED_WITH_ERRORS)) advanceProgress(95);
                                if(reasons.contains(JobStateReason.JOB_COMPLETED_WITH_WARNINGS)) advanceProgress(95);
                                //100% pcd deducted
                                if(reasons.size()==1 && reasons.contains(JobStateReason.JOB_COMPLETED_SUCCESSFULLY)) {
                                    continue;
                                }
                                status.setForeground(Color.red);
                                //status.setText(reasons+"");
                                //System.out.println(reasons+"");
                            }
                            continue;
                        }
                        if(attrs[j] instanceof JobKOctets) continue;
                        System.out.println("job attr: "+attrs[j].getClass());
                    }
                    break;
                }
            }
            catch(Exception e) {
                e.printStackTrace();
            }
            return;
        }
        if(evt instanceof PrintServiceEvent) {
            try {
                PrintServiceEvent event = (PrintServiceEvent )evt;
                javax.print.attribute.PrintServiceAttributeSet set = event.getAttributes();
javax.print.attribute.Attribute[] a=set.toArray();
for(int i=a.length-1; i>=0; i--) {
System.out.println("ps evt "+a[i]);
}
                PrinterStateReasons reasons;
                reasons = (PrinterStateReasons )set.get(PrinterStateReasons.class);
                java.util.Set errors = (reasons==null) ? null : reasons.printerStateReasonSet(javax.print.attribute.standard.Severity.ERROR);
                if(errors!=null && errors.size()>0 && !isSticky) {
                    status.setForeground(Color.red);
                    status.setText("Printer is Malfunctioning");//+reasons+"");
                    String image = "ecioffln.gif";
                    Set hwErrors = reasons.printerStateReasonSet(Severity.ERROR);
                    if(hwErrors.contains(PrinterStateReason.MEDIA_EMPTY)) image = "ecit4emp.gif";
                    //                                        if(hwErrors.contains(PrinterStateReason.MEDIA_NEEDED)) image = "ecit4emp.gif";
                    if(hwErrors.contains(PrinterStateReason.TONER_LOW)) image = "ecitoner.gif";
                    if(hwErrors.contains(PrinterStateReason.TONER_EMPTY)) image = "ecitoner.gif";
                    if(hwErrors.contains(PrinterStateReason.COVER_OPEN)) image = "ecidropn.gif";
                    if(hwErrors.contains(PrinterStateReason.DOOR_OPEN)) image = "ecidropn.gif";
                    if(hwErrors.contains(PrinterStateReason.MEDIA_JAM)) image = "ecipajam.gif";
                    if(hwErrors.contains(PrinterStateReason.TIMED_OUT)) image = "ecioffln.gif";
                    if(hwErrors.contains(PrinterStateReason.CONNECTING_TO_DEVICE)) image = "ecicable.gif";
                    printerPicture.setIcon(new javax.swing.ImageIcon(getClass().getResource("/edu/columbia/print/ninja/prix/display/"+image)));
                }
//                else if(PrinterState.IDLE.equals(set.get(PrinterState.class))) {
//                    completeJob(null, "complete", "eci_ok.gif", 700, false);
//                }
                else {
                    status.setForeground(Color.black);
                }
            }
            catch(Exception e) {
                System.out.println("error in ps attributeUpdate "+e+" "+evt);
            }
            return;
        }
    }
    
    public void run() {
        for(;;) {
            synchronized(this) {
                try {wait();}
                catch(Exception e) {continue;}
            }
            authLabel.setText("Verifying Information...");
            if(!stateStack.push(VERIFY_STATE)) continue;
            CallbackHandler cbh = new CallbackHandler(requests[0]);
            //call the PcdAuth here
            
            PrintRequestAttributeSet set = null;
            try {set=((edu.columbia.print.ninja.core.service.RemoteDocPrintRequest )requests[0]).getAttributes();}
            catch(Exception e) {}
            if(set==null) set = new javax.print.attribute.HashPrintRequestAttributeSet();
            for(int i=jobTable.getRowCount()-1; i>=0; i--) {
                Object[] tmp = (Object[] )jobTable.getValueAt(i, 0);
                if(tmp==null) continue;
                if(!tmp[0].equals(requests[0])) continue;
                String user = jobTable.getValueAt(i, 1)+"";
                System.out.println("user="+user);
                if(user!=null) set.add(new RequestingUserName(user, null));
            }
            try {
                ///request.setAttributes sometimes throws a no such object exception
                ///that doesn't allow people to select their jobs
                ///need to read up on weak references and rmi gc
                //				request.setAttributes(set);
                long time = -System.currentTimeMillis();
                AuthStub auth = null;
                auth = queue.requestStart(requests, null, Lease.ANY);
                time += System.currentTimeMillis();
                authLease = auth.getLease();
                authRenewMgr.renewFor(auth.getLease(), Lease.FOREVER, this);
                //keep track of the registration
//                auth.getPrintService().addEventListener(null, null, myListener, null, 0L);
                auth.setCallbackHandler(cbh);
                int result = 0;
                try {result = auth.authenticate();}
                catch(Exception e) {
                    e.printStackTrace();
                }
                switch(result) {
                    case AuthStub.AUTHENTICATE_SUCCESSFUL:
                        try {
                            statusRenewMgr.cancel(psLease);
//                            statusRenewMgr.cancel(pjLease);
                        }
                        catch(Exception e) {}
                        printerPicture.setIcon(new javax.swing.ImageIcon(getClass().getResource("/edu/columbia/print/ninja/prix/display/eci_ok.gif")));
                        status.setText(" ");
                        progress.setValue(0);
                        progress.setString("");
                        EventRegistration reg;
                        authRenewMgr.cancel(auth.getLease());
                        for(int i=0; i<requests.length; i++) {
                            reg = requests[i].addEventListener(
                                false, false, null, null, false, myListener,
                                null, 60*1000L);
    //                        pjLease = reg.getLease();
    //                        statusRenewMgr.renewFor(pjLease, Lease.FOREVER, this);
                        }
                        stateStack.push(PRINT_STATE);
                        //                                    cancelAuth(auth.getLease());
                        break;
                    case AuthStub.PREAUTHENTICATE_FAILED:
                        stateStack.push(LOGIN_ERROR_STATE);
                        Thread.sleep(5*1000);
                        stateStack.pop(LOGIN_ERROR_STATE);
                        cancelAuth(auth.getLease());
                        System.out.println("fail");
                        break;
                    case AuthStub.USER_CANCELED_REQUEST:
                        cancelAuth(auth.getLease());
                        break;
                    case AuthStub.PREAUTHENTICATE_TIMEOUT:
                        cancelAuth(auth.getLease());
                        System.out.println("timeout "+(System.currentTimeMillis()-auth.getLease().getExpiration()));
                        break;
                }
            }
            catch(java.rmi.NoSuchObjectException e) {
                e.printStackTrace();
            }
            catch(LeaseDeniedException e) {
                garbage.push(new Object[] {requests[0], new Integer(0)});
            }
            catch (Exception e) {
                e.printStackTrace();
            }
            stateStack.pop(VERIFY_STATE);
        }
    }
   
    public void notify(final net.jini.lease.LeaseRenewalEvent event) {
        if(event.getSource().equals(authRenewMgr)) {
            timer.schedule(new TimerTask() {
                public void run() {
                    synchronized(passwordSync) {
                        passwordSync.notify();
                    }
                    cancelAuth(event.getLease());
                }
            }, 0);
            return;
        }
    }
    
    public void setSequenceNumber(long pqSeqNo) {this.pqSeqNo=pqSeqNo;}
    
    public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
    }
    
    class CallbackHandler implements javax.security.auth.callback.CallbackHandler {
        
        Object value;
        
        char[] pass = null;
        
        CallbackHandler(Object value) {
            this.value = value;
        }
        
        public void handle(javax.security.auth.callback.Callback[] cbs) throws IOException, UnsupportedCallbackException {
            //System.out.println(0);
            long time = -System.currentTimeMillis();
            uni.setText("");
            status.setText("");
            for(int i=jobTable.getRowCount()-1; i>=0; i--) {
                Object[] tmp = (Object[] )jobTable.getValueAt(i, 0);
                if(tmp==null) continue;
                if(!tmp[0].equals(value)) continue;
                //					authPanel.requestFocus(false);
                for(int j=cbs.length-1; j>=0; j--) {
                    if(cbs[j] instanceof NameCallback) {
                        authLabel.setText(jobTable.getValueAt(i, 3)+"");
//                        authPanel.repaint();
                        jobName.setText(jobTable.getValueAt(i, 3)+"");
                        user.setEnabled(true);
                        password.setEnabled(true);
                        NameCallback ncb = (NameCallback )cbs[j];
                        password.setText("");
                        password.selectAll();
                        String name = ncb.getName();
                        if(name==null) {
                            user.setText(jobTable.getValueAt(i, 1)+"");
                            authPanel.setFocusTraversalKeys(
                            KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
                            null
                            );
                            authPanel.setFocusTraversalKeys(
                            KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
                            null
                            );
                            user.setEditable(true);
                            user.selectAll();
                            //no usage information from pcd
                            if(!stateStack.push(AUTH_STATE)) {
                                cancelAuth(null);
                            }
                        }
                        else {
                            user.setText(name);
                            authPanel.setFocusTraversalKeys(
                            KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
                            Collections.EMPTY_SET
                            );
                            authPanel.setFocusTraversalKeys(
                            KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
                            Collections.EMPTY_SET
                            );
                            user.setEditable(false);
                            if(!stateStack.push(AUTH_STATE)) {
                                cancelAuth(null);
                            }
                            password.requestFocus();
                        }
                        NameCallback cb = (NameCallback )cbs[j];
                        System.out.println("get passwd");
                        pass = getLogin(cb);
                        System.out.println("got passwd");
                    }
/*                    if(cbs[j] instanceof ConfirmationCallback) {
                        synchronized(jobTable) {jobTable.notify();}
                    }
 */                    if(cbs[j] instanceof PasswordCallback) {
                         PasswordCallback cb = (PasswordCallback )cbs[j];
                         cb.setPassword(pass);
                     }
                    if(cbs[j] instanceof TextOutputCallback) {
                        TextOutputCallback cb = (TextOutputCallback )cbs[j];
                        authError.setText(cb.getMessage());
                        System.out.println("text output: "+cb.getMessage());
                        errorPanel.setVisible(true);
                    }
                    if(cbs[j] instanceof ChoiceCallback) {
                        ChoiceCallback cb = (ChoiceCallback )cbs[j];
                        String[] choices = cb.getChoices();
                    }
                }
                return;
            }
            throw new UnsupportedCallbackException(cbs[0], "Couldn't locate request");
        }
        
    }
    
    public void refreshLAF() {
        try {
            if(pageRefresh!=null) pageRefresh.cancel();
            pageRefresh = new PageRefresh();
            timer.schedule(pageRefresh, 0L);
        }
        catch(Exception e) {e.printStackTrace();}
    }

    char[] getLogin(NameCallback cb) throws UnsupportedCallbackException {
        try {
            synchronized(passwordSync) {
//            for(;;) {
//                if(!user.hasFocus() || !password.isVisible()) break;
                passwordSync.wait();
//            }
            }
        }
        catch(Exception e) {}
        if(!collectingInput) throw new UnsupportedCallbackException(cb);
        user.setEnabled(false);
    password.setEnabled(false);
        errorPanel.setVisible(false);
        authLabel.setText("Authenticating...");
        cb.setName(user.getText());
        return password.getPassword();
    }

    class Shutdown extends Thread {
        
        public void run() {
//            if(webServer!=null) webServer.stop();
        }
        
    }
    
       public void writeText(PrintWriter os) throws Exception {
            if(mainPane==null) return;
            if(authPanel==null) return;
            if(statusPanel==null) return;
            if(status==null) return;
            if(jobTable==null) return;
            if(uni==null) return;
            if(jobName==null) return;
            if(progress==null) return;
            String buffer = mainPane.getText();
            int index = buffer.indexOf("<requests>");
            if(index<0) return;
            os.println(buffer.substring(0, index));
            os.println("<font color=\"#000000\">");
            os.println("<table border=1 width=100%>");
            if(authPanel.isVisible()) {
                os.println("<tr><td align=center><table>");
                os.println("<tr><td colspan=2 align=left><font size=+1>Authenticating</font></td></tr>");
                os.println("<tr><td align=left>Job Name:</td><td>"+authLabel.getText()+"</td></tr>");
                os.println("</table></td></tr>");
            }
            if(statusPanel.isVisible()) {
                os.println("<tr><td align=center><table>");
                os.println("<tr><td colspan=2 align=left><font size=+1>Print Status</font></td></tr>");
                os.println("<tr><td align=left>UNI:</td><td>"+uni.getText()+"</td></tr>");
                os.println("<tr><td align=left>Job Name:</td><td>"+jobName.getText()+"</td></tr>");
                if(status.getForeground().equals(Color.red)) {
                    os.println("<tr><td align=center colspan=2><font size=+1 color=red>"+status.getText()+"</font></td></tr>");
                }
                else {
                    os.println("<tr><td align=center colspan=2><font size=+1 color=black>"+status.getText()+"</font></td></tr>");
                }
                os.println("<tr><td align=center colspan=2>"+progress.getValue()+"%</td></tr>");
                os.println("</table></td></tr>");
            }
            os.println("<tr><td>");
            os.println("<table width=100% border=0>");
            os.println("<tr><td colspan="+jobTable.getColumnCount()+">");
            os.println("<strong>Displaying the most recent of "+jobTable.getRowCount()+" jobs</strong>");
            if(lastJobTime>0) {
                os.println("(last job successfully completed on "+new Date(lastJobTime)+")");
            }
            os.println("<br>last reboot on "+lastReboot);
            os.println("</td></tr>");
            os.println("<tr>");
            for(int i=0; i<jobTable.getColumnCount(); i++) {
                os.println("<td><strong>"+jobTable.getColumnName(i)+"</strong></td>");
            }
            os.println("</tr>");
            for(int i=jobTable.getRowCount()-15; i<jobTable.getRowCount(); i++) {
                if(i<0) continue;
                os.println("<tr>");
                for(int j=0; j<jobTable.getColumnCount(); j++) {
                    String value = "";
                    try {
                        Object tmp = jobTable.getValueAt(i,j);
                        if(tmp instanceof JProgressBar) {
                            tmp = ((JProgressBar )tmp).getString();
                        }
                        else if(tmp instanceof Object[]) {
                            tmp = (i+1)+"";
                        }
                        if(tmp!=null) value = tmp+"";
                    }
                    catch(Exception e) {e.printStackTrace();}
                    os.println("<td>"+value+"</td>");
                }
                os.println("</tr>");
            }
            
            os.println("</table>");
            os.println("</td></tr></table>");
            os.println("</font>");
            os.println(buffer.substring(buffer.indexOf("</requests>", index)+13));
            os.flush();
        }
        
    
    class GarbageCollector implements Runnable {
        public synchronized void run() {
            Object marker = null;
            for(;;) {
                Object[] req = (Object[] )garbage.pop();
                boolean removed = false;
                try {
                    if(req.equals(marker)) wait(10000);
                    if(marker==null) marker = req;
                    for(int i=jobTable.getRowCount()-1; i>=0; i--) {
                        Object[] tmp = (Object[] )jobTable.getValueAt(i, 0);
                        if(!req[0].equals(tmp[0])) continue;
//                System.out.println(i+" remove "+tmp+" size="+garbage.size());
                        removeRow(i);
                        removed = true;
                        break;
                    }
                }
                catch(Exception e) {
                    e.printStackTrace();
                }
                if(!removed) {
                    Integer count = (Integer )req[1];
                    count = new Integer(count.intValue()+1);
                    req[1] = count;
                    if(count.intValue()<10) garbage.push(req);
                    else removed = true;
                }
                if(removed && req.equals(marker)) marker=null;
            }
        }
    }
    class PageRefresh extends TimerTask {
        public void run() {
            try {
                authPanel.setBounds(getTopLevelAncestor().getBounds());
                rebootPanel.setBounds(getTopLevelAncestor().getBounds());
                errorPanel.setBounds(getTopLevelAncestor().getBounds());
                statusPanel.setBounds(getTopLevelAncestor().getBounds());
                mainPane.setPage(new URL(service.getProperty("laf.base")+"/"+service.getProperty("laf.main.page")));
                authPanel.setPage(new URL(service.getProperty("laf.base")+"/"+service.getProperty("laf.auth.page")));
                rebootPanel.setPage(new URL(service.getProperty("laf.base")+"/"+service.getProperty("laf.reboot.page")));
                errorPanel.setPage(new URL(service.getProperty("laf.base")+"/"+service.getProperty("laf.error.page")));
                statusPanel.setPage(new URL(service.getProperty("laf.base")+"/"+service.getProperty("laf.status.page")));
                mainPane.invalidate();
                authPanel.invalidate();
                rebootPanel.invalidate();
                errorPanel.invalidate();
                statusPanel.invalidate();
            }
            catch(Exception e) {e.printStackTrace();}
            try {
                pageRefresh = new PageRefresh();
                timer.schedule(pageRefresh, Long.parseLong(service.getProperty("laf.refresh")));
            }
            catch(Exception e) {e.printStackTrace();}
        }
    }

    class Deleter extends TimerTask {
        transient PrintRequest req;
        Deleter(PrintRequest req) {this.req=req;}
        public void run() {
            garbage.push(new Object[] {req, new Integer(0)});
        }
    }
    
    private static final Integer SELECT_STATE = new Integer(1);
    private static final Integer VERIFY_STATE = new Integer(2);
    private static final Integer AUTH_STATE = new Integer(3);
    private static final Integer LOGIN_ERROR_STATE = new Integer(4);
    private static final Integer PRINT_STATE = new Integer(5);
    private static final Integer REBOOT_STATE = new Integer(6);

    private boolean isSticky = false, collectingInput;
    private transient Object me=this, passwordSync=new Object();
    private transient Lease authLease, psLease, pjLease;
    private transient RequestQueue queue;
    private transient PrintRequest[] requests;
    private transient Thread authThread;
    private long lastJobTime = -1;
    private volatile long pqSeqNo;
    private transient Map pjSeqNos;
    private Date lastReboot = new Date();
    private String name;
    private transient WebServer webServer;
    private transient LeaseRenewalManager authRenewMgr, statusRenewMgr;
    private transient java.util.Timer timer;
    private transient Station service;
    private transient EventRegistration[] reg;
    private transient BlockingQueue garbage;
    private boolean isLocal;
    private transient GarbageCollector garbageCollector;
    private transient Object currentHandback;
    
    private TPane mainPane, authPanel, rebootPanel, errorPanel, statusPanel;
    private javax.swing.JLabel authError;
    private javax.swing.JLabel authLabel;
    private javax.swing.JLabel jobName;
    private javax.swing.JTable jobTable;
    private javax.swing.JPasswordField password;
    private javax.swing.JLabel printerPicture;
    private javax.swing.JProgressBar progress;
    private javax.swing.JLabel status;
    private javax.swing.JLabel uni;
    private javax.swing.JTextField user;
    private PageRefresh pageRefresh;
    
}
