package w3c.jigsaw.admin;

import java.io.*;
import java.util.*;

import w3c.jigsaw.daemon.*;
import w3c.tools.store.*;
import w3c.www.mime.*;
import w3c.www.http.*;
import w3c.jigsaw.http.*;
import w3c.jigsaw.resources.*;

/**
 * The server side resource broker.
 */

public class ResourceBroker extends HTTPResource {
    /**
     * The MIME type Jigsaw uses for tunneling the admin protocol.
     */
    public static MimeType conftype = null;
    static {
	try {
	    conftype = new MimeType("x-jigsaw/config");
	} catch (Exception ex) {
	    ex.printStackTrace();
	}
    }

    /**
     * The object that knows how to write the admin protocol.
     */
    protected AdminWriter writer = null;
    /**
     * The ServerHandlerManager we export.
     */
    protected ServerHandlerManager shm = null;
    /**
     * The controlling ServerHandler.
     */
    protected AdminServer admin = null;

    /**
     * Trigger an HTTP exception.
     * @param request The request we couldn't fulfill.
     * @param msg The error message.
     * @exception HTTPException Always thrown.
     */

    protected void error(Request request, String msg) 
	throws HTTPException
    {
	Reply reply = request.makeReply(HTTP.INTERNAL_SERVER_ERROR);
	reply.setContent(msg);
	throw new HTTPException(reply);
    }

    protected Reply okReply(Request request, byte bits[]) {
	Reply reply = request.makeReply(HTTP.OK);
	reply.setContentType(conftype);
	if ( bits != null ) {
	    ByteArrayInputStream in = new ByteArrayInputStream(bits);
	    reply.setStream(in);
	}
	return reply;
    }

    protected Reply okReply(Request request) {
	return okReply(request, null);
    }

    /**
     * Check that request incomming content type.
     * @param request The request to check.
     * @exception HTTPException If the request type doesn't match admin.
     */

    protected void checkContentType(Request request) 
	throws HTTPException
    {
	if ( request.getContentType().match(conftype) < 0 ) 
	    error(request, "invalid MIME type: "+request.getContentType());
    }

    /**
     * Get a data input stream out of that request input stream
     * @param request The request to get data from.
     * @exception HTTPException If we couldn't get the request's content.
     * @return A DataInputStream instance to read the request's content.
     */

    protected DataInputStream getDataInputStream(Request request) 
	throws HTTPException
    {
	// How fun HTTP/1.1 is, allowing us to double the network traffic :-(
	// If this is a 1.1 request, send a 100 continue:
	Client client = request.getClient();
	if ( client != null ) {
	    try {
		client.sendContinue();
	    } catch (IOException ex) {
		throw new HTTPException(ex.getMessage());
	    }
	}
	// Now, only, get the data:
	try {
	    return new DataInputStream(request.getInputStream());
	} catch (IOException ex) {
	    error(request, "invalid request");
	}
	// not reached:
	return null;
    }

    /**
     * Lookup the target of the given request.
     * @param request The request whose target is to be fetched.
     * @return A Resource instance.
     * @exception HTTPException If the resource couldn't be located.
     */

    public Resource lookup(Request request) 
	throws HTTPException
    {
	// Create lookup state and get rid of root resource requests:
	LookupState   ls = new LookupState(request);
	LookupResult  lr = new LookupResult(null);
	ls.markInternal();
	if ( ! ls.hasMoreComponents() ) 
	    return admin.getRoot();
	// Lookup the target resource:
	ServerHandler sh = shm.lookupServerHandler(ls.getNextComponent());
	if ( sh == null ) 
	    error(request, "unknown server handler");
	// Lookup that resource, from the config resource of that server:
	ContainerResource cr = sh.getConfigResource();
	if ( cr != null ) {
	    if ( ls.hasMoreComponents() ) {
		lr.setTarget(cr);
		cr.lookup(ls, lr);
		// Emit back this resource (after a check):
		Resource result = lr.getTarget();
		if ( result != null )
		    return result;
	    } else {
		return cr;
	    }
	}
	error(request, "unknown resource");
	// not reached
	return null;
    }

    /**
     * Set a set of attribute values for the target resource.
     * @param request The request to handle.
     * @return A Reply instance.
     * @exception HTTPException If some error occurs.
     */

    public Reply remoteSetValues(Request request) 
	throws HTTPException
    {
	DataInputStream in = getDataInputStream(request);
	// Get the target resource to act on:
	Resource r = lookup(request);
	// Decode the new attribute values:
	Attribute attrs[] = r.getAttributes();
	int       idx     = -1;
	try {
	    while ((idx = in.readInt()) != -1) {
		Attribute a = attrs[idx];
		Object    v = a.unpickle(in);
		try {
		    r.setValue(idx, v);
		} catch (IllegalAttributeAccess ex) {
		    ex.printStackTrace();
		}
	    }
	} catch (IOException ex) {
	    error(request, "bad request");
	}
	// All the changes done, return OK:
	return okReply(request);
    }

    /**
     * Get a set of attribute values.
     * @param request The request to handle.
     * @return A Reply instance.
     * @exception HTTPException If some error occurs.
     */

    public Reply remoteGetValues(Request request) 
	throws HTTPException
    {
	DataInputStream in = getDataInputStream(request);
	// Get the target resource:
	Resource              r    = lookup(request);
	ByteArrayOutputStream bout = new ByteArrayOutputStream();
	DataOutputStream      out  = new DataOutputStream(bout);
	Attribute attrs[] = r.getAttributes();
	int       idx     = -1;
	try {
	    while ((idx = in.readInt()) != -1) {
		Object v = r.getValue(idx, null);
		if ((v != null) && !attrs[idx].checkFlag(Attribute.DONTSAVE)) {
		    out.writeInt(idx);
		    attrs[idx].pickle(out, v);
		} else {
		    out.writeInt(-2-idx);
		}
	    }
	    out.writeInt(-1);
	    out.close();
	} catch (IOException ex) {
	    error(request, "bad request");
	}
	// Setup the reply:
	return okReply(request, bout.toByteArray());
    }

    /**
     * Get the set of attributes for the given resource.
     * @param request The request to handle.
     * @return A Reply instance.
     * @exception HTTPException If some error occurs.
     */

    public Reply remoteGetAttributes(Request request) 
	throws HTTPException
    {
	Resource r = lookup(request);
	// This request has no content
	ByteArrayOutputStream bout = new ByteArrayOutputStream();
	DataOutputStream      out  = new DataOutputStream(bout);
	try {
	    writer.writeAttributes(out, r.getAttributes());
	    out.close();
	} catch (IOException ex) {
	    error(request, "bad request");
	}
	// Setup the reply:
	return okReply(request, bout.toByteArray());
    }

    /**
     * Enumerate the resource identifiers of that resource.
     * @param request The request to handle.
     * @return A Reply instance.
     * @exception HTTPException If some error occurs.
     */

    public Reply remoteEnumerateIdentifiers(Request request) 
	throws HTTPException
    {
	Resource r = lookup(request);
	// Check that the resource is a container resource:
	if ( ! (r instanceof ContainerResource) ) 
	    error(request, "not a container");
	ContainerResource cr = (ContainerResource) r;
	// This request has no content
	ByteArrayOutputStream bout = new ByteArrayOutputStream();
	DataOutputStream      out  = new DataOutputStream(bout);
	Enumeration           e    = cr.enumerateResourceIdentifiers();
	try {
	    while ( e.hasMoreElements() ) 
		out.writeUTF((String) e.nextElement());
	    out.writeUTF("");
	    out.close();
	} catch (IOException ex) {
	    error(request, "bad request");
	}
	// Setup the reply:
	return okReply(request, bout.toByteArray());
    }

    /**
     * Return a resource back to the client.
     * @param request The request to handle.
     * @return A Reply instance.
     * @exception HTTPException If some error occurs.
     */

    public Reply remoteLoadResource(Request request) 
	throws HTTPException
    {
	Resource r = lookup(request);
	// This request has no content
	ByteArrayOutputStream bout = new ByteArrayOutputStream();
	DataOutputStream      out  = new DataOutputStream(bout);
	try {
	    writer.writeResource(out, r);
	    out.close();
	} catch (IOException ex) {
	    error(request, "bad request");
	}
	// Setup the reply:
	return okReply(request, bout.toByteArray());
    }

    public Reply remoteRegisterResource(Request request) 
	throws HTTPException
    {
	error(request, "not implemented");
	// not reached:
	return null;
    }

    public Reply remoteUnregisterFilter(Request request) 
	throws HTTPException
    {
	error(request, "not implemented");
	// not reached:
	return null;
    }

    public Reply remoteRegisterFilter(Request request) 
	throws HTTPException
    {
	error(request, "not implemented");
	// not reached:
	return null;
    }

    public Reply extended(Request request) 
	throws HTTPException, ClientException
    {
	String mth = request.getMethod();
	if ( mth.equals("SET-VALUES") ) {
	    checkContentType(request);
	    return remoteSetValues(request);
	} else if (mth.equals("GET-VALUES") ) {
	    checkContentType(request);
	    return remoteGetValues(request);
	} else if (mth.equals("GET-ATTRIBUTES")) {
	    checkContentType(request);
	    return remoteGetAttributes(request);
	} else if (mth.equals("ENUMERATE-IDENTIFIERS")) {
	    checkContentType(request);
	    return remoteEnumerateIdentifiers(request);
	} else if (mth.equals("LOAD-RESOURCE")) {
	    checkContentType(request);
	    return remoteLoadResource(request);
	} else if (mth.equals("REGISTER-RESOURCE") ) {
	    checkContentType(request);
	    return remoteRegisterResource(request);
	} else if (mth.equals("UNREGISTER-FILTER")) {
	    checkContentType(request);
	    return remoteUnregisterFilter(request);
	} else if (mth.equals("REGISTER-FILTER")) {
	    checkContentType(request);
	    return remoteRegisterFilter(request);
	} else {
	    return super.extended(request);
	}
    }

    /**
     * A real funny way to create resources..
     * @param shm The server handler manager instance to administer.
     * @param server The AdminServer instance.
     * @param writer The encoder for the Admin protocol.
     */

    public ResourceBroker(ServerHandlerManager shm
			  , AdminServer admin
			  , AdminWriter writer) {
	this.shm    = shm;
	this.admin  = admin;
	this.writer = writer;
    }
	
}
