package org.yi.happy.archive.block;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

import org.yi.happy.archive.Bytes;

/**
 * A data object for a data block.
 */
public final class GenericBlock extends AbstractBlock implements Block {

    private final Bytes body;

    private final Map<String, String> meta;

    /**
     * create empty
     */
    public GenericBlock(Map<String, String> meta, Bytes body) {
	for (Map.Entry<String, String> i : meta.entrySet()) {
	    checkHeader(i.getKey(), i.getValue());
	}

	if (body == null) {
	    body = new Bytes();
	}

	this.meta = Collections
		.unmodifiableMap(new LinkedHashMap<String, String>(meta));
	this.body = body;
    }

    public GenericBlock() {
	this.meta = Collections.emptyMap();
	this.body = new Bytes();
    }

    /**
     * create a fully defined block
     * 
     * @param body
     *            the data part of the block
     * @param meta
     *            the meta headers, as pairs of names and values, must be an
     *            even number of elements
     * @return the created block
     */
    public static GenericBlock create(Bytes body, String... meta) {
	if (meta.length % 2 != 0) {
	    throw new IllegalArgumentException("meta needs to be pairs");
	}

	Map<String, String> m = new LinkedHashMap<String, String>();
	for (int i = 0; i < meta.length; i += 2) {
	    if (m.containsKey(meta[i])) {
		throw new IllegalArgumentException("repeated header: "
			+ meta[i]);
	    }
	    m.put(meta[i], meta[i + 1]);
	}

	return new GenericBlock(m, body);
    }

    /**
     * get the block meta data
     * 
     * @return the map of meta data fields.
     */
    @Override
    public Map<String, String> getMeta() {
	return meta;
    }

    /**
     * get the data block
     * 
     * @return the internal data block, never null.
     */
    public Bytes getBody() {
	return body;
    }

    @Override
    public int hashCode() {
	final int PRIME = 31;
	int result = 1;
	result = PRIME * result + body.hashCode();
	result = PRIME * result + meta.hashCode();
	return result;
    }

    @Override
    public boolean equals(Object obj) {
	if (this == obj) {
	    return true;
	}
	if (obj == null) {
	    return false;
	}
	if (getClass() != obj.getClass()) {
	    return false;
	}
	final GenericBlock other = (GenericBlock) obj;
	if (!body.equals(other.body)) {
	    return false;
	}
	if (!meta.equals(other.meta)) {
	    return false;
	}
	return true;
    }

    /**
     * Check a meta-data header, throwing an {@link IllegalArgumentException} on
     * failure.
     * 
     * @param name
     *            the name part of the header.
     * @param value
     *            the value part of the header.
     */
    public static void checkHeader(String name, String value) {
	for (int i = 0; i < name.length(); i++) {
	    char c = name.charAt(i);
	    if (c == ':' || c == '\r' || c == '\n') {
		throw new IllegalArgumentException(
			"name can not have ':' or newline");
	    }
	}

	for (int i = 0; i < value.length(); i++) {
	    char c = value.charAt(i);
	    if (c == '\r' || c == '\n') {
		throw new IllegalArgumentException("value can not have newline");
	    }
	}
    }

}

@@ -160,20 +160,21 @@
 **%0A * A 
-data
+value
  object 
@@ -179,20 +179,23 @@
 t for a 
-data
+generic
  block.%0A
@@ -373,14 +373,184 @@
 eate
- empty
+, checking that the meta-data headers are valid.%0A     * %0A     * @param meta%0A     *            the meta-data headers.%0A     * @param body%0A     *            the bytes of the body.
 %0A   
@@ -879,24 +879,61 @@
 ody;%0A    %7D%0A%0A
+    /**%0A     * create blank.%0A     */%0A
     public G
@@ -1018,32 +1018,32 @@
 %0A    %7D%0A%0A    /**%0A
-
      * create a 
@@ -1057,24 +1057,25 @@
 efined block
+.
 %0A     * %0A   
@@ -2722,74 +2722,10 @@
 ader
-, throwing an %7B@link IllegalArgumentException%7D on%0A     * failure
 .%0A
+
     
@@ -2814,16 +2814,16 @@
 m value%0A
-
      *  
@@ -2858,24 +2858,261 @@
 the header.%0A
+     * @throws IllegalArgumentException%0A     *             if the name or value have a newline in them, or if the name%0A     *             has a ':' in it.%0A     * @throws NullPointerException%0A     *             if name or value are null.%0A
      */%0A    

update the documentation of GenericBlock.