/**
 * If the result of the HTTP request is a file on the harddisk,
 * you can use this class to fulfill that request.
 * It supports Content-Range.
 * 
 * Copyright 2007 Ben Bucksch
 * BSD
 */

package org.bucksch.httpserver;
import java.io.*;
import java.net.SocketException;
import org.bucksch.zeipis.all.Util;

public class HTTPFileResponse
{
  public void respond(HTTPRequest request, String filename, String mimetype, String descr)
    throws HTTPException
  {
    File file = new File(filename);
    respond(request, file, mimetype, descr);
  }
  public void respond(HTTPRequest request, File file, String mimetype, String descr)
    throws HTTPException
  {
    Util.debug("Serving file " + file.getAbsolutePath() + ", mimetype " + mimetype);
    if (!file.exists())
      throw new HTTPException(404, descr + " is missing on the harddisk");
    if (!file.canRead())
      throw new HTTPException(500, descr + " is not readable on the harddisk, please check the permissions");
    FileInputStream instream;
    try {
      instream = new FileInputStream(file);
    } catch (FileNotFoundException e) {
      throw new HTTPException(500, descr + " could not be opened", e);
    }

    // Range (partial files, for skipping in player)
    /* <http://www.apps.ietf.org/rfc/rfc2616.html#sec-14.16>
     * <http://www.apps.ietf.org/rfc/rfc2616.html#sec-14.35>
     */
    String range = request.getRequestHeaders().get("Range");
    long length = file.length();
    long start = 0;
    long end = length - 1;
    if (range != null)
    {
      if (!range.startsWith("bytes="))
        throw new HTTPException(400, "Only byte ranges supported", range);
      range = range.substring(6);
      if (range.contains(","))
        throw new HTTPException(400, "Multiple ranges not supported", range);
      if (range.contains("-"))
      {
        int sepPos = range.indexOf('-');
        if (sepPos == -1)
          throw new HTTPException(400, "Range invalid", range);
        start = Long.valueOf(range.substring(0, sepPos));
        if (sepPos > range.length())
          end = Long.valueOf(range.substring(sepPos + 1, range.length()));
        if (start >= length || end >= length)
          throw new HTTPException(406, "Requested range not satisfiable", range);
        Util.debug ("Sending range from " + start + " to " + end);
      }
      else
        start = Integer.valueOf(range);
    }
    try {
      if (start > 0)
        start = instream.skip(start);
      //Util.debug ("Actually skipped " + start + " bytes");
    } catch (IOException e) {
      throw new HTTPException(500, "Could not skip in " + descr, e);
    }

    if (mimetype != null)
      request.getResponseHeaders().put("Content-Type", mimetype);
    request.getResponseHeaders().put("Content-Length", (end - start + 1) + "");
    request.getResponseHeaders().put("Content-Range", "bytes " + start + "-" + end + "/" + length);
    request.getResponseHeaders().put("Accept-Ranges", "bytes");

    try {
      Util.debug("copying");
      request.sendResponseHeaders(start > 0 ? 206 : 200, false);
      copyBytesBlocking(new BufferedInputStream(instream), request.getResponseBody());
      request.close();
      Util.debug("done");
    } catch (Exception e) {
      if (e instanceof SocketException && e.getMessage() == "Connection reset")
        Util.debug("Client disconnected");
      else
        throw new HTTPException(500, "Error while reading " + descr, e);
    }
  }
  protected void copyBytesBlocking(InputStream in, OutputStream out)
    throws IOException
  {
    try {
      int count;
      byte[] buffer = new byte[4096];
      while (true)
      {
        count = in.read(buffer); // blocks until input data is available or EOF
        if (count == -1) // EOF
          break;
        out.write(buffer, 0, count);
      }
    } finally {
      in.close();
      out.close();
    }
  }
}
