/*
 * Filename: Feed.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.core.service;

import edu.columbia.print.ninja.core.authenticate.Authenticator;
import com.sun.rio.resources.watcher.*;
import javax.print.attribute.standard.*;
import javax.print.attribute.*;
import net.jini.print.service.PrintService;
import java.rmi.*;
import java.util.*;
import net.jini.core.lease.Lease;
import com.millipad.util.RemoteBlockingStack;
import net.jini.print.job.*;
import java.rmi.*;
import java.rmi.server.*;

/**
 * Feeds authenticated PrintJobs to a PrinterService
 * @author Joe Wang <jw168@columbia.edu>
 * @since 1.0
 */
class Feed implements Runnable {
    
    Ninja list;
    RemoteBlockingStack stack;
    transient CounterWatch jobCompleteWatch = null;
    transient StopWatch jobTimeWatch = null;
    int retries;
    boolean printing;
    
    /**
     * @param list The list that holds the jobs
     * @param stack the provider for feed input
     */
    Feed(Ninja list, RemoteBlockingStack stack) throws Exception {
        this.list = list;
        this.stack = stack;
    }
    
    public boolean isPrinting() {return printing;}
    
    public void run() {
        
        for(;;) {
            Object[] values = null;
            try {
                values = (Object[] )stack.pop();
            }
            catch(java.rmi.RemoteException e) {
                debug("no pop");
            }
            RemoteDocPrintRequest req = (RemoteDocPrintRequest )values[0];
            PrintService ps = (PrintService )values[1];
            ps = (PrintService )list.printers.get(ps);
            Authenticator auth = null;
            //get the resource
            MultiLeasedResource.LeasedEntry rsrc = null;
            for(Iterator i=list.listLand.leasedResources(); i.hasNext();) {
                MultiLeasedResource.LeasedEntry entry = (MultiLeasedResource.LeasedEntry )i.next();
                Object[] v = (Object[] )list.listLand.getValue(entry.getCookie());
                if(!v[1].equals(req)) continue;
                rsrc = entry;
                break;
            }
            if(rsrc==null) {
                debug("resource not found for "+req);
                continue;
            }
            rsrc.lock();
            try {
                auth = (Authenticator )((MarshalledObject )values[2]).get();
            }
            catch(Exception e) {
                debug("can't get auth"+e);
                rsrc.unlock();
                continue;
            }
            Object cookie = values[3];
            try {
                if(!auth.authenticate(cookie)) {
                    debug("auth failed");
                    DocPrintRequest pj = ps.createDocPrintRequest();
                    pj.setAttributes(req.getAttributes());
                    net.jini.print.job.PrintJobAndLease result = pj.print(0L);
                    result.printJob.addEventListener(false, false, null, null, false, (net.jini.core.event.RemoteEventListener )req, null, 0L);
/*
                    net.jini.core.event.RemoteEventListener rel = (net.jini.core.event.RemoteEventListener )req;
                    PrintJobAttributeSet pjas = new HashPrintJobAttributeSet();
                    pjas.add(JobState.ABORTED);
                    ps.createDocPrintRequest();
                    PrintJobEvent pje = new PrintJobEvent((PrintService )RemoteObject.toStub(list), 0L, 0L, null, null, pjas);
                    rel.notify(pje);
*/                    rsrc.unlock();
                    continue;
                }
            }
            catch(Exception e) {
                debug("can't auth "+e);
                rsrc.unlock();
                continue;
            }
            javax.print.attribute.PrintJobAttributeSet status = new javax.print.attribute.HashPrintJobAttributeSet();
            status.add(JobState.PENDING);
            //			list.attributeUpdate(new PrintJobAttributeEvent(job, status));
            
            net.jini.print.job.DocPrintRequest pj = null;
            try {
                javax.print.DocFlavor[] flavor = {null};
                MarshalledObject[] docAttrs = {null};
                java.io.FileInputStream[] doc = {null};
                //				Lease lease = (Lease )list.auths.get(auth);
                
                //				try {lease.cancel();}
                //				catch(Exception e) {}
                
                javax.print.attribute.PrintRequestAttributeSet tmp = new javax.print.attribute.HashPrintRequestAttributeSet();
                tmp.addAll(req.getAttributes());
                
                PrintJobAttributeSet resultSet = null;
                net.jini.print.job.PrintJobAndLease result = null;
                printing = true;
                for(int i=0;i<=retries;i++) {
                    list.loadJob(req, null, docAttrs, flavor, doc);
                    req.setAttributes(tmp);
                    
                    try {
                        if(jobTimeWatch==null && list.watchFinder.isWatchAvailable()) {
                            jobTimeWatch = list.watchFinder.getWatch(list.getName()+": Print Time");
                        }
                        if(jobTimeWatch!=null) jobTimeWatch.startTiming();
                    }
                    catch(Exception ex) {
                        jobTimeWatch = null;
                    }
                    
                    pj = ps.createDocPrintRequest();
                    pj.setDoc(new org.jpwg.jini.print.data.InputStreamDoc(doc[0], flavor[0].getMimeType(), (DocAttributeSet )docAttrs[0].get()));
                    pj.setAttributes(req.getAttributes());
                    result = pj.print(0L);
                    result.printJob.addEventListener(false, false, null, null, false, (net.jini.core.event.RemoteEventListener )req, null, 0L);
                    //wait for completion
                    //hack for use with RemotePrintServiceAdapter
                    synchronized(result.printJob) {
                        try {result.printJob.wait();}
                        catch(Exception e) {e.printStackTrace();}
                    }
                    resultSet = result.printJob.getAttributes();
                    if(!resultSet.containsValue(JobState.ABORTED)) break;
                    if(i<retries) debug("retry");
                }
                auth.postauthenticate(resultSet, cookie);
                printing = false;
                JobMediaSheetsCompleted sheets = (JobMediaSheetsCompleted )result.printJob.getAttributes().get(JobMediaSheetsCompleted.class);
                try {
                    if(jobCompleteWatch==null && list.watchFinder.isWatchAvailable()) {
                        StopWatch watch = list.watchFinder.getWatch(list.getName()+": Pages Printed");
                        if(watch!=null) jobCompleteWatch = new CounterWatch(watch.getWatchDataSource(), list.getName()+": Pages Printed");
                    }
                    if(jobCompleteWatch!=null && sheets!=null) jobCompleteWatch.increment(sheets.getValue());
                }
                catch(Exception ex) {
                    jobCompleteWatch = null;
                }
                try {
                    if(jobTimeWatch!=null) jobTimeWatch.stopTiming();
                }
                catch(Exception ex) {
                    debug(ex);
                    jobTimeWatch = null;
                }
                //				status.add(JobState.COMPLETED);
                //				list.attributeUpdate(new PrintJobAttributeEvent(job, status));
                rsrc.unlock();
            }
            catch(Exception e) {
                e.printStackTrace();
                debug("feed error");
                status.add(JobState.ABORTED);
                //				list.attributeUpdate(new PrintJobAttributeEvent(job, status));
            }
            rsrc.unlock();
            //cancel listener
            //			ps.removePrintServiceAttributeListener(list);
        }
    }
    
    public void setRetries(int retry) {
        this.retries = retry;
    }
    
    private static final void debug(Object i) {com.millipad.util.Debug.print(Feed.class, i);}
    
}

