package railo.commons.lang;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;


/**
 * class to handle characters, similar to StringBuffer, but dont copy big blocks of char arrays.
 */
public class ByteBuffer {

	private final static int BLOCK_LENGTH = 1024;
	private byte buffer[];
	private int pos=0;
	private int length=0;
	private final Entity root=new Entity(null);
	private Entity curr=root;
	private final String charset;
	
	/**
	 * default constructor
	 */
	public ByteBuffer(String charset) {
		this(charset,BLOCK_LENGTH);
	}

	/**
	 * constructor with size of the buffer
	 * @param size
	 */
	public ByteBuffer(String charset,int size) {
		buffer = new byte[size];
		this.charset=charset;
	}
	
	public void append(char c) throws IOException {
		append(new String(new char[]{c}));
	}
	
	/**
	* method to appennd a charr array to the buffer
	* @param c char array to append
	 * @throws IOException 
	*/
	public void append(char c[]) throws IOException {
		append(new String(c));
	}
	
	public void append(byte c[]) {
		int maxlength=buffer.length-pos;
		if(c.length<maxlength) {
			System.arraycopy(c, 0, buffer, pos, c.length);
			pos+=c.length;
		}
		else {
			System.arraycopy(c, 0, buffer, pos, maxlength);
			curr.next=new Entity(buffer);
			curr=curr.next;
			length+=buffer.length;
			buffer=new byte[(buffer.length>c.length-maxlength)?buffer.length:c.length-maxlength];
			if(c.length>maxlength) {
				System.arraycopy(c, maxlength, buffer, 0, c.length-maxlength);
				pos=c.length-maxlength;
			}
			else {
				pos=0;
			}
		}
	}
	
	/**
	* method to append a part of a char array
	* @param c char array to get part from
	* @param off start index on the char array
	* @param len length of the sequenz to get from array
	 * @throws IOException 
	*/
	public void append(char c[], int off, int len) throws IOException {
		append(new String(c,off,len));
	}
	
	/**
	* Method to append a string to char buffer
	* @param str String to append
	 * @throws IOException 
	*/
	public void append(String str) throws IOException {
		append(str.getBytes(charset));
	}

	/**
	* method to append a part of a String
	* @param str string to get part from
	* @param off start index on the string
	* @param len length of the sequenz to get from string
	 * @throws IOException 
	*/
	public void append(String str, int off, int len) throws IOException {
		append(str.substring(off, off+len));
	}

	/**
	* method to writeout content of the char buffer in a writer,
	* this is faster than get char array with (toCharArray()) and write this
	in writer.
	* @param writer writer to write inside
	* @throws IOException
	*/
	public void writeOut(OutputStream os) throws IOException {
		Entity e=root;
		while(e.next!=null) {
			e=e.next;
			os.write(e.data);
		}
		os.write(buffer,0,pos);
	}
	
	@Override
	public String toString() {
		try {
			return new String(getBytes(),charset);
		} catch (UnsupportedEncodingException e) {
			return new String(getBytes());
		}
	}

	/**
	* clear the content of the buffer
	*/
	public void clear() {
		if(size()==0)return;
        buffer = new byte[buffer.length];
		root.next=null;
		pos = 0;
		length=0;
		curr=root;
	}

	/**
	 * @return returns the size of the content of the buffer
	 */
	public int size() {
		return length+pos;
	}
	
	
	private class Entity {
		private byte[] data;
		private Entity next;
		
		private Entity(byte[] data) {
			this.data=data;
		}
	}


    public byte[] getBytes() {
    	ByteArrayOutputStream baos=new ByteArrayOutputStream();
		try {
			writeOut(baos);
		} 
		catch (IOException e) {}
        return baos.toByteArray();
    }

	
	public static void main(String[] args) throws IOException {
		ByteBuffer cb=new ByteBuffer("utf-8",3);
		cb.append("12");
		cb.append("34");
		cb.append("5");
		cb.append("6");
		cb.append("7");
		cb.append("890-");
		cb.append("1234567890-1234567890-");
		System.out.println(cb+"->"+cb.size());
		
		cb=new ByteBuffer("utf-8",3);
		cb.append("12",0,2);
		cb.append("34",0,2);
		cb.append("5",0,1);
		cb.append("xxx6ggg",3,1);
		cb.append("7zzz",0,1);
		cb.append("890-",0,4);
		cb.append("1234567890-1234567890-",0,22);
		System.out.println(cb+"->"+cb.size());
		
		
		cb=new ByteBuffer("utf-8",3);
		cb.append(new char[]{'1','2'});
		cb.append(new char[]{'3','4'});
		cb.append(new char[]{'5'});
		cb.append(new char[]{'6'});
		cb.append(new char[]{'7'});
		cb.append(new char[]{'8','9','0','-'});
		cb.append(new char[]{'1','2','3','4','5','6','7','8','9','0','-','1','2','3','4','5','6','7','8','9','0','-'});
		System.out.println(cb+"->"+cb.size());

		
		
		cb=new ByteBuffer("utf-8",3);
		cb.append(new char[]{'X','1','2','X'},1,2);
		cb.append(new char[]{'3','4'},0,2);
		cb.append(new char[]{'5'},0,1);
		cb.append(new char[]{'6'},0,1);
		cb.append(new char[]{'7'},0,1);
		cb.append(new char[]{'8','9','0','-'},0,4);
		cb.append(new char[]{'1','2','3','4','5','6','7','8','9','0','-','1','2','3','4','5','6','7','8','9','0','-'},0,22);
		System.out.println(cb+"->"+cb.size());
		
		cb=new ByteBuffer("utf-8",3);
		cb.append(new char[]{'Š'});
		System.out.println(cb+"->"+cb.size());
		
	}
}