// PutListResource.java
// $Id: PutListResource.java,v 1.12 1997/07/31 07:01:56 bmahe Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.jigsaw.filters;

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

import w3c.util.*;
import w3c.tools.store.*;
import w3c.cvs.*;
import w3c.jigsaw.auth.AuthFilter;
import w3c.jigsaw.http.*;
import w3c.www.http.*;
import w3c.jigsaw.forms.*;
import w3c.jigsaw.resources.*;
import w3c.jigsaw.html.*;

public class PutListResource extends PostableResource {
    private static final boolean debug = true;

    /**
     * Attribute index - The file used to store the modification list.
     */
    protected static int ATTR_FILE = -1;
    /**
     * Attribute index - The user's local space.
     */
    protected static int ATTR_SPACE = -1;
    /**
     * Attribute index - The web server public space.
     */
    protected static int ATTR_ROOT = -1;
    

    static {
	Class     c = null;
	Attribute a = null;

	try {
	    c = Class.forName("w3c.jigsaw.filters.PutListResource");
	} catch (Exception ex) {
	    ex.printStackTrace();
	    System.exit(1);
	}
	// Register the file attribute:
	a = new FileAttribute("file"
			      , null
			      , Attribute.EDITABLE|Attribute.MANDATORY);
	ATTR_FILE = AttributeRegistry.registerAttribute(c, a);
	// Register the space attribute:
	a = new FileAttribute("space"
			      , null
			      , Attribute.EDITABLE|Attribute.MANDATORY);
	ATTR_SPACE = AttributeRegistry.registerAttribute(c, a);
	// Register the server root:
	a = new FileAttribute("root"
			      , null
			      , Attribute.EDITABLE| Attribute.MANDATORY);
	ATTR_ROOT = AttributeRegistry.registerAttribute(c, a);
    }

    /**
     * Known entries.
     */
    Hashtable entries = null;
    /**
     * Our server context properties.
     */
    ObservableProperties props = null;

    /**
     * Compute the path of the public file for the given local file.
     * This method uses the <em>space</em> and <em>root</em> attributes
     * to translate the path of the given file from the user's local space
     * to the public (server) space.
     * @return A File instance, or <strong>null</strong>.
     */

    protected File getServerFile(File file) {
	String fpath  = file.getAbsolutePath();
	String fspace = getSpace().getAbsolutePath();
	if ( ! fpath.startsWith(fspace) )
	    return null;
	return new File(getRoot(), fpath.substring(fspace.length()));
    }

    /**
     * Get the file to use to store the edited list of files.
     * @return The file.
     */

    public File getFile() {
	return (File) getValue(ATTR_FILE, null);
    }

    /**
     * Get the root directory of the public server to update.
     * @return The root directory of the public server space, supposed to
     * be controled by CVS.
     */

    public File getRoot() {
	return (File) getValue(ATTR_ROOT, null);
    }

    /**
     * Get this user's local CVS spacxe root directory.
     * @return The usre's root of the CVS local space, assumed to be 
     * under CVS control.
     */

    public File getSpace() {
	return (File) getValue(ATTR_SPACE, null);
    }

    /**
     * Dump the current list of edited files back to disk.
     */

    public synchronized void writeList() {
	File file   = getFile();
	File backup = null;
	// Save old version if available:
	if ( file.exists() ) {
	    backup = new File(getFile()+".bak");
	    if ( backup.exists() )
		backup.delete();
	    file.renameTo(backup);
	}
	try {
	    // This also resets the file:
	    DataOutputStream out = (new DataOutputStream
				    (new FileOutputStream(file)));
	    Enumeration enum = entries.elements();
	    // Dump all entries:
	    out.writeInt(entries.size());
	    while (enum.hasMoreElements()) {
		PutedEntry e = (PutedEntry) enum.nextElement();
		e.pickle(out);
	    }
	    out.close();
	} catch (IOException ex) {
	    // FIXME
	    ex.printStackTrace();
	    if ( backup != null )
		backup.renameTo(file);
	}
    }


    /**
     * Restore the list from the file.
     */

    public synchronized void readList() {
	File file = getFile();
	try {
	    DataInputStream in = (new DataInputStream
				  (new FileInputStream(file)));
	    int size = in.readInt();
	    while (--size >= 0) {
		PutedEntry e = (PutedEntry) AttributeHolder.unpickle(in, null);
		addEntry(e);
	    }
	} catch (IOException ex) {
	    // FIXME
	    ex.printStackTrace();
	}
    }

    public void addEntry(PutedEntry e) {
	entries.put(e.getKey(), e);
    }

    public PutedEntry lookupEntry(Request request) {
	HTTPResource r = (HTTPResource) request.getTargetResource();
	String       k = request.getURL().toExternalForm();
	if ( r instanceof FileResource )
	    k = ((FileResource) r).getFile().getAbsolutePath().toString();
	return (PutedEntry) entries.get(k);
    }

    /**
     * Register the given request, which must has a PUT method.
     * @param file The modified file.
     */

    public synchronized void registerRequest(Request request) {
	PutedEntry e = lookupEntry(request);
	if ( e == null ) {
	    e = PutedEntry.makeEntry(request);
	    addEntry(e);
	} else {
	    e.update(request);
	}
    }

    public synchronized void notifyUnload() {
	writeList();
	super.notifyUnload();
    }

    /**
     * Dump the list of modified files.
     * @param request The request to handle.
     * @return A Reply instance.
     */

    public Reply get(Request request) 
	throws HTTPException
    {
	HtmlGenerator g = new HtmlGenerator("Modified files");
	g.append("<h1>List of modified files</h1>");
	g.append("<form action=\""+request.getURL()+"\" method=\"POST\">");
	g.append("<dl>");
	// Dump all entries:
	Enumeration enum = entries.elements();
	while ( enum.hasMoreElements() ) {
	    PutedEntry e      = (PutedEntry) enum.nextElement();
	    String     fname  = e.getFilename();
	    String     author = e.getAuthor();
	    long       time   = e.getTime();
	    String     url    = e.getURL();

	    g.append("<dt><input type=\"checkbox\" name=\""
                     + e.getKey() + "\" value =\"mark\">"
		     , (fname != null) ? fname : url
		     , "</dt><dd>");
	    if ( fname != null ) {
		File         file   = new File(fname);
		File         dir    = new File(file.getParent());
		// Compute the CVS directory URL for the file:
		URL          cvsurl = null;
		try {
		    cvsurl = new URL(new URL(url), "CVS");
		} catch (Exception ex) {
		    cvsurl = null;
		}
		// Display status:
		int st = -1;
		try {
		    // Local status first:
		    CvsDirectory cvs = CvsDirectory.getManager(dir, props);
		    st  = cvs.status(file.getName());
		    if ( cvsurl != null )
			g.append("Status: <em><a href=\""+cvsurl+ "\">"
				 , cvs.statusToString(st)
				 , "</a></em><br>");
		    else
			g.append("Status: <em>"
				 , cvs.statusToString(st)
				 , "</em><br>");
		} catch (CvsException ex) {
		    g.append("Status: <strong>CVS ERROR</strong>: "
			     , ex.getMessage()
			     , "<br>");
		}
		// Publish status next (when possible)
		if ( st != CVS.FILE_Q ) {
		    try {
			File         sf  = getServerFile(file); 
			File         sd  = new File(sf.getParent());
			CvsDirectory sc  = CvsDirectory.getManager(sd, props);
			int          sst = sc.status(file.getName());
			if ((st == CVS.FILE_M) || (sst != CVS.FILE_OK))
			    g.append("Publish: (needed) <em>"
				     , sc.statusToString(sst)
				     , "</em><br>");
			else
			    g.append("Publish: <em>"
				     , sc.statusToString(sst)
				     , "</em><br>");
		    } catch (CvsException ex) {
			g.append("Publish: <strong>CVS ERROR</strong>: "
				 , ex.getMessage()
				 , "<br>");
		    }
		}
	    }
	    // Display author:
	    if ( author != null )
		g.append("Modified by <em>"+author+"</em> on <strong>"
			 + new Date(time).toString() + "</strong>.<br>");
	    else
		g.append("Modified on <strong>"
			 + new Date(time).toString()
			 + "</strong>.<br>");
	}
	g.append("</dl>");
	// The command button:
	g.append ("<hr>Perform action on marked entries:<p>") ;
	g.append("<input type=\"radio\" name=\"action\" value=\"publish\">"
		 + "Publish ");
	g.append("<input type=\"radio\" name=\"action\" value=\"remove\">"
		 + "Remove ");
	g.append ("</p>") ;
	g.append("<input type=\"submit\" name=\"submit\" value=\"do !\">") ;
	g.append ("</form>") ;
	g.close();
	Reply reply = createDefaultReply(request, HTTP.OK);
	reply.addPragma("no-cache");
	reply.setNoCache();
	reply.setStream(g);
	return reply;
    }

    protected void performAction(Request request, String action, String key) {
	PutedEntry pe = (PutedEntry) entries.get(key);
	if ( pe == null ) {
	    // We're in troubles !
	    if ( debug )
		System.out.println("PutList: "+key+" not found !");
	    return ;
	}
	if ( action.equals("publish") ) {
	    File file  = new File(pe.getFilename());
	    File sfile = getServerFile(file); 
	    try {
		// First step: does the private version needs commit ?
		File         d  = new File(file.getParent());
		CvsDirectory c  = CvsDirectory.getManager(d, props);
		if ( c.status(file.getName()) == CVS.FILE_M ) {
		    String author = pe.getAuthor();
		    String env [] = { "USER="+author , 
				      "LOGNAME="+author };
		    String msg    = ((author != null)
				     ? "Published by "+author+" through Jigsaw"
				     : "Published through Jigsaw");
		    c.commit(file.getName(), msg, env);
		} else if ( debug ) {
		    System.out.println("PutList: no commit needed on "
				       + file.getAbsolutePath()
				       + " st="+c.status(file.getName()));
		}
		// Second step: publish
		File         sd = new File(sfile.getParent());
		CvsDirectory sc = CvsDirectory.getManager(sd, props);
		if (sc.status(sfile.getName()) != CVS.FILE_OK) {
		    sc.update(sfile.getName());
		} else if ( debug ) {
		    System.out.println("PutList: no update needed on "
				       + sfile.getAbsolutePath()
				       + " st="+sc.status(sfile.getName()));
		}
		// Last step: remove published entries:
		entries.remove(key);
	    } catch (CvsException ex) {
		ex.printStackTrace();
	    }
	} else if ( action.equals("remove") ) {
	    entries.remove(key);
	} else if ( debug ) {
	    System.out.println("PutList: "+action+" unknown.");
	}
    }

    public Reply handle(Request request, URLDecoder data) 
	throws HTTPException
    {
	// Get the action to perform:
	String action = data.getValue("action");
	if (action == null) {
	    Reply error = request.makeReply(HTTP.INTERNAL_SERVER_ERROR);
	    error.setContent("You must select the action to be performed.");
	    return error;
	}
	// Check all entries and perform action:
	Enumeration enum = entries.keys();
	while ( enum.hasMoreElements() ) {
	    String key = (String) enum.nextElement();
	    if (data.getValue(key) != null) {
		// Perform action on that entry:
		if ( debug )
		    System.out.println("PutList: "+action+" on "+key);
		performAction(request, action, key);
	    } else {
		if ( debug )
		    System.out.println("PutList: "+key+" not marked !");
	    }
	}
	return get(request);
    }

    public void initialize(Object values[]) {
	super.initialize(values);
	// Prepare empty entry list:
	File file = getFile();
	if ((file != null) && file.exists())
	    readList();
	// Get the server properties:
	this.props = getServer().getProperties();
    }

    public PutListResource() {
	super();
	this.entries = new Hashtable(11);
    }

}
