/*
 * Filename: RequestInserter.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 javax.print.attribute.standard.Compression;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.zip.Deflater;
import java.net.InetSocketAddress;

import javax.print.DocFlavor;
import javax.print.attribute.*;
import java.io.*;
import java.util.*;

import com.millipad.util.*;

/**
 * A conduit between the client and server.  Allows the client to stream
 * a document to the server.  This is a support class for the ListJob
 * @author Joe Wang <jw168@columbia.edu>
 * @since 1.0
 * @see edu.columbia.print.core.service.ListJob
 */
class RequestInserter implements Runnable, java.io.Serializable {
    static final long serialVersionUID = -2857477072294692140L;
    transient Ninja list;
    transient ServerSocketChannel channel;
    transient Selector selector;
    transient BlockingQueue queue;
    java.net.SocketAddress address;
    boolean compress;
    
    RequestInserter(Ninja list) throws Exception {
        this.list = list;
        
        queue = new BlockingQueue();
        for(int i=0; i<3; i++) queue.push(new Timer());
        //bind to a socket
        //problem if there are multiple ethernet cards
        address = new InetSocketAddress(0);
        channel = ServerSocketChannel.open();
        channel.configureBlocking(false);
        channel.socket().bind(address);
        selector = Selector.open();
        channel.register(selector, SelectionKey.OP_ACCEPT);
        address = new InetSocketAddress(
        java.net.InetAddress.getLocalHost(),
        channel.socket().getLocalPort()
        );
    }
    
    void debug(Object o) {com.millipad.util.Debug.print(RequestInserter.class, o);}
    
    public void setCompress(boolean value) {
        compress = value;
    }
    public boolean getCompress() {return compress;}

    void sendData(ListRequest job, javax.print.Doc doc, long duration) throws Exception {
        DocFlavor flavor = doc.getDocFlavor();
        DocAttributeSet docAttrs = doc.getAttributes();
        if(docAttrs==null) docAttrs = new HashDocAttributeSet();
        int MAX_COMPRESS_BUFFER = 4096;
        ByteBuffer buffer = ByteBuffer.allocateDirect(MAX_COMPRESS_BUFFER);
        SocketChannel socket = SocketChannel.open();
        socket.connect(address);
        
        WritableByteChannel channel = (WritableByteChannel )socket;
        ObjectOutputStream os = new ObjectOutputStream(
        Channels.newOutputStream(channel)
        );
        os.writeObject(job);
        os.writeLong(duration);
        os.writeObject(new DocFlavor(flavor.getMimeType(), "java.io.InputStream"));
        
        DocAttributeSet docAttrs2 = new HashDocAttributeSet();
        docAttrs2.addAll(docAttrs);
        if(compress) {
            Compression method = (Compression )docAttrs2.get(Compression.class);
            if(method==null || method.equals(Compression.NONE)) {
                java.io.OutputStream dos = new java.util.zip.DeflaterOutputStream(
                Channels.newOutputStream(channel),
                new Deflater(Deflater.BEST_COMPRESSION),
                MAX_COMPRESS_BUFFER
                );
                channel = Channels.newChannel(dos);
                docAttrs2.add(Compression.DEFLATE);
            }
        }
        os.writeObject(docAttrs2);
        
        java.nio.channels.ReadableByteChannel inChannel =
        Channels.newChannel(doc.getStreamForBytes());
        
        for(;;) {
            buffer.clear();
            if(inChannel.read(buffer)<0) break;
            buffer.flip();
            channel.write(buffer);
        }
        channel.close();
        inChannel.close();
        socket.close();
    }
    
    class ConnectionProcessor extends TimerTask {
        SocketChannel channel;
        ConnectionProcessor(SocketChannel channel) {
            this.channel = channel;
        }
        public void run() {
            Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
            try {
                ObjectInputStream is = new ObjectInputStream(
                Channels.newInputStream(channel)
                );
                RemoteDocPrintRequest request = (RemoteDocPrintRequest )is.readObject();
                long duration = is.readLong();
                DocFlavor flavor = (DocFlavor )is.readObject();
                DocAttributeSet docAttrs = (DocAttributeSet )is.readObject();
                RemoteDocPrintRequest stub = (RemoteDocPrintRequest )java.rmi.server.UnicastRemoteObject.exportObject(request);
                net.jini.core.lease.Lease lease =  list.listPolicy.leaseFor(
                list.listLand.acquireResource(new Object[] {request, stub}),
                duration
                );
                request.getAttributes().add(new javax.print.attribute.standard.JobHoldUntil(new java.util.Date(lease.getExpiration())));
                request.getAttributes().remove(javax.print.attribute.standard.Destination.class);
                list.insertingRequest(stub, docAttrs, lease);
                list.storeJob(request, lease, docAttrs, flavor, channel);
                list.insertRequest(request, stub, docAttrs, lease);
                channel.close();
            }
            catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    public void run() {
        for(;;) {
            try {
                for(; selector.select()>0;) {
                    java.util.Set set=selector.selectedKeys();
                    synchronized(set) {
                        for(java.util.Iterator i=set.iterator(); i.hasNext();) {
                            SelectionKey selection;
                            selection = (SelectionKey )i.next();
                            i.remove();
                            ServerSocketChannel nextChannel =
                            (ServerSocketChannel )selection.channel();
                            SocketChannel channel = nextChannel.accept();
                            Timer timer = (Timer )queue.pop();
                            timer.schedule(new ConnectionProcessor(channel), 0);
                            queue.push(timer);
                        }
                    }
                }
            }
            catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
}

