/*
 * Filename: LprPrinter.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
 *
 */

package edu.columbia.print.ninja.prix.service.lpr;

import com.millipad.util.*;
import edu.columbia.print.ninja.core.service.*;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import javax.print.*;
import javax.print.event.*;
import javax.print.attribute.*;
import javax.print.attribute.standard.*;

/**
 * LprPrinter is a wrapper for a PrintService which is based on the LPR protocol.
 * The lpr printer provides additional functionality which is either broken or
 * missing in j2se 1.4.  This class functions with LPRng and will not work
 * with other implementations due to the reliance on the output of the lpq command.
 * @author Joe Wang <jw168@columbia.edu>
 * @since 1.0
 * @see <a href="http://www.lprng.com">LPRng</a>
 */
public class LprPrinter implements PrintService {
    PrintService service, duplex;
    LpqReader reader;
    Set listeners;
    PrintServiceAttributeSet oldPSSet;
    
    public LprPrinter(PrintService service, PrintService duplex) throws Exception {
        this.service = service;
        this.duplex = duplex;
        oldPSSet = new HashPrintServiceAttributeSet();
        reader = new LpqReader(service.getName());
        listeners = new HashSet();
    }
    
    private static void debug(Object o) {Debug.print(LprPrinter.class, o);}
    
    /**
     * return the same name as the PrintService with which the LprPrinter was contructed.
     * @return the name of this PrintService
     * @see javax.print.PrintService#getName
     */
    public String getName() {return service.getName();}
    
    public void addPrintServiceAttributeListener(PrintServiceAttributeListener l) {
        listeners.add(l);
    }
    
    public javax.print.DocPrintJob createPrintJob() {
        return new Job(service.createPrintJob(), (duplex==null)?null:duplex.createPrintJob(), this);
    }
    
    public javax.print.attribute.PrintServiceAttributeSet getAttributes() {
        try {
            return reader.getPrinterAttributes();
        }
        catch(Exception e) {}
        return null;
    }
    
    public javax.print.attribute.PrintServiceAttribute getAttribute(Class clazz) {
        return null;
    }
    
    public Object getDefaultAttributeValue(Class clazz) {
        return null;
    }
    
    public javax.print.ServiceUIFactory getServiceUIFactory() {
        return null;
    }
    
    public Class[] getSupportedAttributeCategories() {
        return null;
    }
    
    public Object getSupportedAttributeValues(Class clazz, javax.print.DocFlavor docFlavor, javax.print.attribute.AttributeSet attributeSet) {
        return null;
    }
    
    public javax.print.DocFlavor[] getSupportedDocFlavors() {
        return null;
    }
    
    public javax.print.attribute.AttributeSet getUnsupportedAttributes(javax.print.DocFlavor docFlavor, javax.print.attribute.AttributeSet attributeSet) {
        return null;
    }
    
    public boolean isAttributeCategorySupported(Class clazz) {
        return false;
    }
    
    public boolean isAttributeValueSupported(javax.print.attribute.Attribute attribute, javax.print.DocFlavor docFlavor, javax.print.attribute.AttributeSet attributeSet) {
        return false;
    }
    
    public boolean isDocFlavorSupported(javax.print.DocFlavor docFlavor) {
        return false;
    }
    
    public void removePrintServiceAttributeListener(javax.print.event.PrintServiceAttributeListener printServiceAttributeListener) {
    }
    
    class Job implements DocPrintJob {
        Set listeners, attrListeners;
        LprPrinter ps;
        DocPrintJob parent, duplex;
        PrintRequestAttributeSet reqSet;
        Doc doc;
        PrintJobAttributeSet set;
        PrintJobAttributeSet oldPJSet;
        
        Job(DocPrintJob parent, DocPrintJob duplex, LprPrinter ps) {
            this.parent = parent;
            this.duplex = duplex;
            this.ps = ps;
            oldPJSet = new HashPrintJobAttributeSet();
            listeners = new HashSet();
            attrListeners = new HashSet();
            set = new HashPrintJobAttributeSet();
            set.add(JobState.PENDING_HELD);
        }
        public void addPrintJobAttributeListener(PrintJobAttributeListener l,
        PrintJobAttributeSet set) {
            attrListeners.add(new Object[] {l, set});
        }
        public void addPrintJobListener(PrintJobListener l) {
            listeners.add(l);
        }
        public PrintJobAttributeSet getAttributes() {
            try {
                if(doc==null) return null;
                set.add(new JobOriginatingUserName("Unknown", null));
                //determine simplex/duplex
                boolean isDuplex = false;
                Sides sides = null;
                if(doc!=null) sides = (Sides )doc.getAttributes().get(Sides.class);
                if(sides!=null && !sides.equals(Sides.ONE_SIDED)) isDuplex = true;
                set.addAll(reader.getJobAttributes(isDuplex));
                return set;
                
            }
            catch(Exception e) {e.printStackTrace();}
            return null;
        }
        public PrintService getPrintService() {return ps;}
        
        public void print(Doc doc, PrintRequestAttributeSet set) {
            this.doc = doc;
            PrintJobAttributeSet status = new HashPrintJobAttributeSet();
            try {
                DocAttributeSet dupAttrs = new HashDocAttributeSet();
                DocAttributeSet docAttrs = doc.getAttributes();
                dupAttrs.addAll(docAttrs);

                PrintRequestAttributeSet reqAttrs = new HashPrintRequestAttributeSet();
                reqAttrs.addAll(set);
                reqAttrs.remove(Copies.class);
                reqSet = set;

                RequestingUserName user = (RequestingUserName )reqAttrs.get(RequestingUserName.class);
                JobMediaSheets sheets = (JobMediaSheets )reqAttrs.get(JobMediaSheets.class);
                String value = "";
                if(sheets==null) value += "?";
                else {
                    value += sheets.getValue();
                    if(sheets.getValue()==1) value += " sheet";
                    else value += " sheets";
                }
                debug(value);
                reqAttrs.add(new JobName(value, null));
            
                InputStream is = doc.getStreamForBytes();
                if(dupAttrs.containsKey(Compression.class)) {
                    is = new InflaterInputStream(is);
                    dupAttrs.remove(Compression.class);
                }
                Sides sides = (Sides )set.get(Sides.class);
                Doc lprDoc = new SimpleDoc(is, doc.getDocFlavor(), dupAttrs);
                                /*
                                if(sides!=null && !sides.equals(Sides.ONE_SIDED)) duplex.print(lprDoc, set);
                                 */
                parent.print(lprDoc, reqAttrs);
                
                for(Iterator i=attrListeners.iterator(); i.hasNext();) {
                    Object[] vals = (Object[] )i.next();
                    PrintJobAttributeListener l = (PrintJobAttributeListener )vals[0];
                    PrintJobAttributeSet s = (PrintJobAttributeSet )vals[1];
                    if(s!=null && !s.containsKey(JobState.class)) continue;
                    status.add(JobState.PROCESSING);
                    l.attributeUpdate(new PrintJobAttributeEvent(this, status));
                }
                Thread.sleep(3*1000);
                String subject = (user==null) ? null : user.getValue();
                waitForFinish(subject);
            }
            catch(Exception e) {
                e.printStackTrace();
                debug("Aborting job");
                status.clear();
                status.add(JobState.ABORTED);
                for(Iterator i=attrListeners.iterator(); i.hasNext();) {
                    Object[] vals = (Object[] )i.next();
                    PrintJobAttributeListener l = (PrintJobAttributeListener )vals[0];
                    try {
                        l.attributeUpdate(new PrintJobAttributeEvent(this, status));
                    }
                    catch(Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }
            //remove all jobs
            if(status.containsValue(JobState.ABORTED)) {
                try {
                    Process p = Runtime.getRuntime().exec("lprm -P"+service.getName()+" all");
                    new Thread(new Emptier(p.getInputStream())).start();
                    new Thread(new Emptier(p.getErrorStream())).start();
                }
                catch(Exception e) {
                    debug("error removing job");
                }
            }
            debug("complete");
        }
        
        void waitForFinish(String user) throws Exception {
            PrintServiceAttributeSet serviceSet;
            Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
            for(;;) {
                AttributeSet attr = getAttributes();
                if(attr!=null) set.addAll(attr);
                if(user!=null) {
                    set.add(new JobOriginatingUserName(user, null));
                }
                else debug("no user");
                Attribute[] attrs = set.toArray();
                for(Iterator i=attrListeners.iterator(); !oldPJSet.equals(set) && i.hasNext();) {
                    Object[] vals = (Object[] )i.next();
                    PrintJobAttributeListener l =
                    (PrintJobAttributeListener )vals[0];
                    PrintJobAttributeSet s =
                    (PrintJobAttributeSet )vals[1];
                    PrintJobAttributeSet temp =
                    new HashPrintJobAttributeSet();
                    for(int j=attrs.length-1; j>=0; j--) {
                        if(s==null || s.containsKey(attrs[j].getCategory())) {
                            temp.add(attrs[j]);
                        }
                        else debug("missing"+attrs[j]);
                    }
                    l.attributeUpdate(new PrintJobAttributeEvent(this, temp));
                }
                oldPJSet.clear();
                //update print service
                serviceSet = ps.getAttributes();
                for(Iterator i=ps.listeners.iterator(); !oldPSSet.equals(serviceSet) && i.hasNext();) {
                    PrintServiceAttributeListener l = (PrintServiceAttributeListener )i.next();
                    l.attributeUpdate(new PrintServiceAttributeEvent(getPrintService(), serviceSet));
                }
                oldPSSet.clear();
                if(serviceSet!=null) oldPSSet.addAll(serviceSet);

                if(set!=null) oldPJSet.addAll(set);
                if(set.containsValue(JobState.ABORTED)) break;
                if(set.containsValue(JobState.CANCELED)) break;
                if(set.containsValue(JobState.COMPLETED)) break;
//                if(set.containsValue(JobState.PROCESSING_STOPPED)) break;
                //				Thread.sleep(500);
            }
System.out.println("pj "+set.get(JobState.class));
            oldPSSet.clear();
            oldPSSet.add(PrinterState.IDLE);
            for(Iterator i=ps.listeners.iterator(); i.hasNext();) {
                PrintServiceAttributeListener l = (PrintServiceAttributeListener )i.next();
                l.attributeUpdate(new PrintServiceAttributeEvent(getPrintService(), oldPSSet));
            }
            Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
        }
        
        public void removePrintJobAttributeListener(PrintJobAttributeListener l) {
            attrListeners.remove(l);
        }
        public void removePrintJobListener(PrintJobListener l) { listeners.remove(l); }
    }
}

