package org.prevayler.foundation;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.EOFException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

public class Chunking {
  private static final String ASCII="US-ASCII";
  private static final byte[] CRLF=new byte[]{'\r','\n'};
  private static final String SIZE="0|[1-9A-F][0-9A-F]{0,6}|[1-7][0-9A-F]{7}";
  private static final String TOKEN="[^\u0000-\u0020()<>@,;:\\\\\"/\\[\\]?={}\u007F-\uFFFF]+";
  private static final String HEADER="(" + SIZE + ")(;"+ TOKEN+ "="+ TOKEN+ ")*\r\n";
  private static final Pattern TOKEN_PATTERN=Pattern.compile(TOKEN);
  private static final Pattern HEADER_PATTERN=Pattern.compile(HEADER);
  private static boolean validToken(  String token){
    return TOKEN_PATTERN.matcher(token).matches();
  }
  public static void writeChunk(  OutputStream stream,  Chunk chunk) throws IOException {
    stream.write(Integer.toHexString(chunk.getBytes().length).toUpperCase().getBytes(ASCII));
    Iterator iterator=chunk.getParameters().entrySet().iterator();
    while (iterator.hasNext()) {
      Map.Entry entry=(Map.Entry)iterator.next();
      String name=(String)entry.getKey();
      String value=(String)entry.getValue();
      if (!validToken(name)) {
        throw new IOException("Invalid parameter name '" + name + "'");
      }
      if (!validToken(value)) {
        throw new IOException("Invalid parameter value '" + value + "'");
      }
      stream.write(';');
      stream.write(name.getBytes(ASCII));
      stream.write('=');
      stream.write(value.getBytes(ASCII));
    }
    stream.write(CRLF);
    stream.write(chunk.getBytes());
    stream.write(CRLF);
  }
  public static Chunk readChunk(  InputStream stream) throws IOException {
    String header=readLine(stream);
    if (header == null) {
      return null;
    }
    if (!HEADER_PATTERN.matcher(header).matches()) {
      throw new IOException("Chunk header corrupted");
    }
    StringTokenizer tokenizer=new StringTokenizer(header,";=\r\n");
    int size=Integer.parseInt(tokenizer.nextToken(),16);
    Map parameters=new LinkedHashMap();
    while (tokenizer.hasMoreTokens()) {
      String name=tokenizer.nextToken();
      String value=tokenizer.nextToken();
      parameters.put(name,value);
    }
    byte[] bytes=new byte[size];
    int total=0;
    while (total < size) {
      int read=stream.read(bytes,total,size - total);
      if (read == -1) {
        throw new EOFException("Unexpected end of stream in chunk data");
      }
      total+=read;
    }
    int cr=stream.read();
    int lf=stream.read();
    if (cr == -1 || cr == '\r' && lf == -1) {
      throw new EOFException("Unexpected end of stream in chunk trailer");
    }
 else     if (cr != '\r' || lf != '\n') {
      throw new IOException("Chunk trailer corrupted");
    }
    return new Chunk(bytes,parameters);
  }
  private static String readLine(  InputStream stream) throws IOException {
    ByteArrayOutputStream header=new ByteArrayOutputStream();
    while (true) {
      int b=stream.read();
      if (b == -1) {
        if (header.size() == 0) {
          return null;
        }
 else {
          throw new EOFException("Unexpected end of stream in chunk header");
        }
      }
      header.write(b);
      if (b == '\n') {
        return header.toString(ASCII);
      }
    }
  }
}
