package mihi;

import java.io.*;
import java.net.*;
import java.util.*;
import java.text.*;
import javax.servlet.*;
import javax.servlet.http.*;

import freenet.*;
import freenet.node.*;
import freenet.support.*;
import freenet.client.*;
import freenet.support.servlet.*;

/* use the following code in your freenet.ini/conf: *
 * 
     mainport.params.servlet.mdata.uri=/servlet/metadata
     mainport.params.servlet.mdata.method=GET
     mainport.params.servlet.mdata.class=mihi.MetaDataServlet
     mainport.params.servlet.mdata.name=MetaData
 *
 */

public class MetaDataServlet extends HttpServlet {

    private static final String
	LOCATION2 ="/servlet/metadata",
	LOCATION  = LOCATION2+"/";

    private static final String startURL =
	"SSK@M7yZgrl8gwtAe1xEcR5Xyv4tFsoPAgM/fiw/8//";
    
    private Node node;
    private ClientFactory clientFactory;
    private BucketFactory bucketFactory;


    public void init() throws ServletException
    {
	super.init();
	ServletContext context = getServletContext();
	node = (Node)context.getAttribute("freenet.node.Node");
 	clientFactory = (ClientFactory) context.getAttribute
	    ("freenet.client.ClientFactory");
	bucketFactory = (BucketFactory) context.getAttribute
	    ("freenet.support.BucketFactory");
    }


    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
	throws ServletException, IOException {
	// preparing the URI parameter
	String uri;
	try {
	    uri=freenet.support.URLDecoder.decode(req.getRequestURI());
	    if (uri.length() < LOCATION2.length()
		|| !uri.substring(0,LOCATION2.length()).equals(LOCATION2)) {
		makeError(resp, uri, "Internal error: Does not start with "+
			  LOCATION+":", "The Metadata servlet does not seem "+
			  "to be installed at "+LOCATION+
			  ". Sorry, cannot do anything about that.");
		return;
	    }
	} catch (URLEncodedFormatException e) {
	    uri="";
	}
	if (uri.length()<LOCATION.length())
	    uri="";
	else
	    uri=uri.substring(LOCATION.length());
	// preparing the Date parameter 
	DateFormat df=new SimpleDateFormat("yyyy-MM-dd");
	df.setTimeZone(TimeZone.getTimeZone("GMT"));
	Date dt = new Date();
	String date=req.getParameter("date");
	if (date == null) {
	    date=df.format(dt);
	} else {
	    try {
		df.setLenient(true);
		dt = df.parse(date);
	    } catch (ParseException e) {
		makeError(resp, date, "Incorrect date", "The date you "+
			  "entered is an incorrect date. <a href=\""+
			  LOCATION+"\">Go back to the Metadata servlet"+
			  " page</a>, or go to the "+
			  "<a href=\"/\">main page</a>");
		return;
	    }
	}
	// reading the zap parameter
	String zap=req.getParameter("zap");
	if ("yes".equals(zap)) {
	    // zap and forget...
	    Bucket dummy=new NullBucket();
	    getData(new FreenetURI(uri),0,dummy, dummy, true);
	}
	// doing redirect if necessary
	String puri=req.getParameter("uri");
	if (puri!=null) {
	    resp.sendRedirect(LOCATION+puri+"?date="+date);
	} else if (uri.indexOf("//") != -1) {
	    resp.sendRedirect(LOCATION+uri.substring(0,uri.indexOf("//"))+
			      "?date="+date);
	}
	long ttime;
	// done with preparing
	if (uri.length()==0) {
	    try {
		ClientSVK svk = new ClientSVK(node.randSource);
		String privkey= Base64.encode(svk.getPrivateKey());
		String pubkey= Base64.encode(svk.getPublicKeyFingerPrint());
                String entropy = Base64.encode(svk.getCryptoKey());
		makeGUI(resp,pubkey,privkey, entropy, date);
	    } catch (KeyException ex) {
		makeError(resp, "Exception while creating new key pair",
			  ex.toString(),"Unexpected error...");
	    }
	} else {
	    FreenetURI furi=new FreenetURI(uri);
	    Bucket meta=bucketFactory.makeBucket(-1),
		data=bucketFactory.makeBucket(-1);
	    try {
		if (!getData(furi, 0, meta,data, false)) {
		    makeError(resp, uri, "Data not found",
			      "The file you requested was not found in your "+
			      "local datastore. <br>You can <a href=\""+
			      LOCATION+ "\">try another address</a>, "+
			      "<a href=\"/"+uri+"\">retrieve this file</a> "+
			      "or <a href=\"/\">go to the main page.");
		    return;
		} else {
		    StringBuffer sb=new StringBuffer();
		    InputStream in = meta.getInputStream();
		    byte[] bs = new byte[4096];
		    int len;
		    while((len=in.read(bs)) != -1) {
			sb.append(new String(bs,0,len));
		    }
		    String zapUri=LOCATION+uri+"?date="+date+"&amp;zap=yes";
		    makeResult(resp, uri, zapUri,
			       parse(sb.toString(), date, dt),
			       data.size(), date);
		}
	    } finally {
		bucketFactory.freeBucket(meta);
		bucketFactory.freeBucket(data);
	    }
	}
    }

    private String parse(String metadata, String date, Date dt) {
	// pass 1 - get dateredirect & offset
	long inc=86400, offs=0;
	StringTokenizer st=new StringTokenizer(metadata,"\n");
	while(st.hasMoreTokens()) {
	    String line = st.nextToken();
	    if (line.startsWith("DateRedirect.Increment=")) {
		inc=Integer.parseInt(line.substring(23),16);
	    } else if (line.startsWith("DateRedirect.Offset=")) {
		    offs=Integer.parseInt(line.substring(20),16);
	    }
	}
	long now=dt.getTime()/1000-offs;
	now =((now/inc)*inc) + offs;
	String slotnum = Integer.toHexString((int)now)+"-";
	// yes i know i will lose all empty lines, but usually there
	// are not any...
	st=new StringTokenizer(metadata,"\n");
	StringBuffer result=new StringBuffer(metadata.length());
	while(st.hasMoreTokens()) {
	    String line=st.nextToken();
	    line=line.replace('<','#'); // hack!
	    if (line.startsWith("Redirect.Target=")) {
		String prefix=line.substring(0,16);
		String text=line.substring(16);
		if (text.startsWith("freenet:")) {
		    text=text.substring(8);
		    prefix+="freenet:";
		}
		appendLink(result,prefix, text, text, date);
	    } else if (line.startsWith("DateRedirect.Target=")) {
		String prefix=line.substring(0,20);
		String text=line.substring(20);
		if (text.startsWith("freenet:")) {
		    text=text.substring(8);
		    prefix+="freenet:";
		}
		String target="";
		if (text.startsWith("KSK@")) {
		    target="KSK@"+slotnum+text.substring(4);
		} else if (text.startsWith("SSK@")) {
		    int pp = text.indexOf("/");
		    target=text.substring(0,pp+1)+slotnum+text.substring(pp+1);
		}
		appendLink(result,prefix, text, target, date);
	    } else if (line.startsWith("DateRedirect.Increment=")) {
		boolean fit= (inc==Integer.parseInt(line.substring(23),16));
		if (fit)
		    result.append("<font color=\"#008000\">");
		else
		    result.append("<font color=\"#FF0000\">");
		result.append(line).append("</font><br>\n");
	    } else if (line.startsWith("DateRedirect.Offset=")) {
		boolean fit = (offs==Integer.parseInt(line.substring(20),16));
		if (fit)
		    result.append("<font color=\"#008000\">");
		else
		    result.append("<font color=\"#FF0000\">");
		result.append(line).append("</font><br>\n");
	    } else if (line.startsWith("Name=")) {
		result.append("<b>").append(line).append("</b><br>\n");
	    } else if (line.indexOf("CHK@") != -1) {
		int pos = line.indexOf("CHK@");
		appendLink(result,line.substring(0,pos),line.substring(pos),
			   line.substring(pos), date);
	    } else if (line.indexOf("=")==-1 && !line.startsWith("End")) {
		result.append("<font size=-3>&nbsp;<br></font>").append(line)
		    .append("<br>\n");
	    } else {
		result.append(line).append("<br>\n");
	    }
	}
	return result.toString();
    }

    private void appendLink(StringBuffer result, String prefix, String text, String uri, String date) {
	result.append(prefix).append("<a href=\"").append(LOCATION)
	    .append(uri).append("?date=").append(date).append("\">")
	    .append(text).append("</a><br>\n");
    }

    private void makeResult(HttpServletResponse resp, String uri,
			    String zapUri, String metadata, long dataLength,
			    String date) throws IOException {

	String content ="<tt>"+metadata+
	    "</tt><hr>Data length: "+dataLength+" [<a href=\"/"+uri+
	    "\">View data</a>] "+
	    "[<a href=\""+zapUri+"\">Delete locally</a>]<hr>"+
	    "<form method=\"GET\">Date for <tt>DateRedirect</tt>s:"+
	    " <input type=\"text\" name=\"date\" value=\""+date+
	    "\" size=10><input type=\"submit\" value=\"Change\"></form>"+
	    "</html>";
	fillTemplate(resp,content,"Metadata viewer","Metadata of "+uri);
    }

    private void fillTemplate(HttpServletResponse resp,String content,
			      String title, String boxTitle)
	throws IOException {
	resp.setContentType("text/html");
	PrintWriter pw = resp.getWriter();
	HtmlTemplate pageTmp = HtmlTemplate.createTemplate
	    ("SimplePage.html"),
	    titleBoxTmp = HtmlTemplate.createTemplate("aqua/titleBox.tpl");
	pageTmp.set("TITLE",title);
	titleBoxTmp.set("TITLE", boxTitle);
	titleBoxTmp.set("CONTENT",content);
	pageTmp.set("BODY",titleBoxTmp);
	pageTmp.toHtml(pw);
	pw.close();    
    }
    
    private void makeError(HttpServletResponse resp,  String uri,
			   String msg, String description) throws IOException {
	String errmsg=msg+": "+uri;
	fillTemplate(resp, description, "Metadata viewer - error", errmsg);
    }
	
    private void makeGUI(HttpServletResponse resp, String pubkey,
			 String privkey, String entropy, String date)
	    throws IOException {
	String content="<form method=\"GET\">URI: <input type=\"text\" "+
	    "size=80 name=\"uri\" "+
	    "value=\""+startURL+"\">"+
	    "<br>Date: <input type=\"text\" size=10 name=\"date\" value=\""+
	    date + "\"><input type=\"submit\" value=\"Request\"></form><hr>"+
	    "<h2>Key Pair</h2>"+
	    "A new SSK key pair is:<br>"+
	    "public key: <b>SSK@"+pubkey+"PAgM,"+entropy+
	    "/</b><br>private key: <b>SSK@"+ privkey + "," + entropy +"/</b>";
	fillTemplate(resp,content,"Metadata servlet", "Metadata servlet");
    }

    private boolean getData(FreenetURI uri, int htl, Bucket metadata,
			    Bucket data, boolean nolocal) throws IOException {
 	GetRequest g=new GetRequest(htl, uri, metadata, data, nolocal);
	try {
	    Client c = clientFactory.getClient(g);
	    int state = c.blockingRun();
	    return state == g.DONE;
	} catch (KeyException ex) {
	    return false;
	}
    }
}
