package rene.util.xml;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

import rene.util.SimpleByteBuffer;
import rene.util.SimpleStringBuffer;
import rene.util.list.ListElement;

public class XmlReader
{	BufferedReader In;
	SimpleStringBuffer buf=new SimpleStringBuffer(10000);

	public XmlReader ()
	{	In=null;
	}

	public XmlReader (BufferedReader in)
	{	In=in;
	}

	public XmlReader (InputStream in)
		throws XmlReaderException
	{	try
		{	// read the file into a buffer
			BufferedInputStream rin=new BufferedInputStream(in);
			SimpleByteBuffer bb=new SimpleByteBuffer(10000);
			while (true)
			{	int k=rin.read();
				if (k<0) break;
				bb.append((byte)k);
			}
			rin.close();
			byte b[]=bb.getByteArray();
			
			// Try to open an ASCII stream, or a default stream
			ByteArrayInputStream bin=new ByteArrayInputStream(b);
			XmlReader R=null;
			try
			{	R=new XmlReader(new BufferedReader(new InputStreamReader(bin,"ASCII")));
			}
			catch (UnsupportedEncodingException ex)
			{	R=new XmlReader(new BufferedReader(new InputStreamReader(bin)));
			}		
						
			// Determine the encoding
			String Encoding=null;
			while (true)
			{	while (true)
				{	int c=R.read();
					if (c==-1) throw new Exception("<?xml> tag not found");
					if (c=='<') break;
				}
				if (R.found("?xml"))
				{	String s=R.scanFor("?>");
					if (s==null) throw new Exception("<?xml> tag error");
					int n=s.indexOf("encoding=\"");
					if (n>=0)
					{	n+="encoding=\"".length();
						s=s.substring(n);
						int m=s.indexOf('\"');
						if (m<0) throw new Exception("Closing bracket missing");
						Encoding=s.substring(0,m).toUpperCase();
						if (Encoding.equals("UTF-8")) Encoding="UTF8";
							// for IE5 !
						break;
					}
					break;
				}
			}
			
			// Open a stream with this encoding
			bin=new ByteArrayInputStream(b);
			if (Encoding==null)
				In=new BufferedReader(new InputStreamReader(bin));
			else
				try 
				{	In=new BufferedReader(new InputStreamReader(
						bin,Encoding));
				}
				catch (UnsupportedEncodingException e)
				{	try
					{	In=new BufferedReader(new InputStreamReader(
							bin,"ASCII"));
					}
					catch (UnsupportedEncodingException ex)
					{	In=new BufferedReader(new InputStreamReader(bin));
					}					
				}
		}
		catch (Exception e)
		{	throw new XmlReaderException(e.toString());
		}
	}

	public void init (InputStream in)
		throws XmlReaderException
	{	try
		{	// read the file into a buffer
			BufferedInputStream rin=new BufferedInputStream(in);
			SimpleByteBuffer bb=new SimpleByteBuffer(10000);
			while (true)
			{	int k=rin.read();
				if (k<0) break;
				bb.append((byte)k);
			}
			rin.close();
			byte b[]=bb.getByteArray();
			
			// Try to open an ASCII stream, or a default stream
			ByteArrayInputStream bin=new ByteArrayInputStream(b);
			XmlReader R=null;
			try
			{	R=new XmlReader(new BufferedReader(new InputStreamReader(bin,"ASCII")));
			}
			catch (UnsupportedEncodingException ex)
			{	R=new XmlReader(new BufferedReader(new InputStreamReader(bin)));
			}		
						
			// Determine the encoding
			String Encoding=null;
			while (true)
			{	while (true)
				{	int c=R.read();
					if (c==-1) throw new Exception("<?xml> tag not found");
					if (c=='<') break;
				}
				if (R.found("?xml"))
				{	String s=R.scanFor("?>");
					if (s==null) throw new Exception("<?xml> tag error");
					int n=s.indexOf("encoding=\"");
					if (n>=0)
					{	n+="encoding=\"".length();
						s=s.substring(n);
						int m=s.indexOf('\"');
						if (m<0) throw new Exception("Closing bracket missing");
						Encoding=s.substring(0,m).toUpperCase();
						if (Encoding.equals("UTF-8")) Encoding="UTF8";
							// for IE5 !
						break;
					}
					break;
				}
			}
			
			// Open a stream with this encoding
			bin=new ByteArrayInputStream(b);
			if (Encoding==null)
				In=new BufferedReader(new InputStreamReader(bin));
			else
				try 
				{	In=new BufferedReader(new InputStreamReader(
						bin,Encoding));
				}
				catch (UnsupportedEncodingException e)
				{	try
					{	In=new BufferedReader(new InputStreamReader(
							bin,"ASCII"));
					}
					catch (UnsupportedEncodingException ex)
					{	In=new BufferedReader(new InputStreamReader(bin));
					}					
				}
		}
		catch (Exception e)
		{	throw new XmlReaderException(e.toString());
		}
	}

	/**
	Scan an xml file. This function reads, until <?xml is found.
	then it skips this declaration and scans the rest of the
	items.
	*/
	public XmlTree scan ()
		throws XmlReaderException
	{	while (true)
		{	while (true)
			{	int c=read();
				if (c==-1) return null;
				if (c=='<') break;
			}
			if (found("?xml"))
			{	String s=scanFor("?>");
				if (s==null) return null;
				XmlTree t=new XmlTree(new XmlTagRoot());
				t.addchild(new XmlTree(new XmlTagPI(s)));
				scanContent(t);
				return t;
			}
		}
	}
	
	public void scanContent (XmlTree t)
		throws XmlReaderException
	{	//System.out.println("Sanning for "+t.getTag().name()+" ("+
		//	t.getTag().countParams()+")");
		while (true)
		{	String s=scanFor('<');
			if (s==null)
			{	if (t.getTag() instanceof XmlTagRoot) return;
				exception("File ended surprisingly");
			}
			if (!empty(s))
			{	t.addchild(new XmlTree(new XmlTagText(
					XmlTranslator.toText(s))));
			}
			if (found("!--"))
			{	s=scanFor("-->");
				continue;
			}
			if (found("!"))
			{	s=scanTagFor('>');
				continue;
			}
			if (found("?"))
			{	s=scanTagFor("?>");
				t.addchild(new XmlTree(new XmlTagPI(s)));
				continue;
			}
			s=scanTagFor('>');
			if (s==null)
				exception("> missing");
			if (s.startsWith("/"))
			{	if (s.substring(1).equals(t.getTag().name()))
					return;
				else 
					exception("End tag without start tag");
			}
			if (s.endsWith("/"))
			{	t.addchild(new XmlTree(new XmlTag(s.substring(0,s.length()-1))));
			}
			else
			{	XmlTree t0=new XmlTree(new XmlTag(s));
				scanContent(t0);
				t.addchild(t0);
			}
		}
	}

	public boolean empty (String s)
	{	int n=s.length();
		for (int i=0; i<n; i++)
		{	char c=s.charAt(i);
			if (c!=' ' && c!='\n' && c!='\t') return false;
		}
		return true;
	}

	/**
	Skip Blanks.
	@return Non-blank character or -1 for EOF.
	*/
	public int skipBlanks ()
		throws XmlReaderException
	{	while (true)
		{	int c=read();
			if (c==' ' || c=='\t' || c=='\n') continue;
			else return c;
		}
	}

	/**
	Scan for an end character.
	@return String between the current position and the end character, or null.
	*/
	public String scanFor (char end)
		throws XmlReaderException
	{	buf.clear();
		int c=read();
		while (c!=end)
		{	buf.append((char)c);
			c=read();
			if (c<0) return null;
		}
		return buf.toString();
	}
	
	/**
	Scan for a specific string.
	@return String between the current position and the string.
	*/
	public String scanFor (String s)
		throws XmlReaderException
	{	buf.clear();
		while (!found(s))
		{	int c=read();
			if (c<0) return null;
			buf.append((char)c);
		}
		for (int i=0; i<s.length(); i++) read();
		return buf.toString();
	}
	
	/**
	Scan tag for an end character (interpreting " and ')
	@return String between the current position and the end character, or null.
	*/
	public String scanTagFor (char end)
		throws XmlReaderException
	{	buf.clear();
		int c=read();
		while (c!=end)
		{	if (c=='\"')
			{	buf.append((char)c);
				while (true)
				{	c=read();
					if (c<0) return null;
					if (c=='\"') break;
					buf.append((char)c);
				}
				buf.append((char)c);
			}
			else if (c=='\'')
			{	buf.append((char)c);
				while (true)
				{	c=read();
					if (c<0) return null;
					if (c=='\'') break;
					buf.append((char)c);
				}
				buf.append((char)c);
			}
			else buf.append((char)c);
			c=read();
			if (c<0) return null;
		}
		return buf.toString();
	}
	
	/**
	Scan tag for a specific string (interpreting " and ')
	@return String between the current position and the string.
	*/
	public String scanTagFor (String s)
		throws XmlReaderException
	{	buf.clear();
		while (!found(s))
		{	int c=read();
			if (c<0) return null;
			if (c=='\"')
			{	buf.append((char)c);
				while (true)
				{	c=read();
					if (c<0) return null;
					if (c=='\"') break;
					buf.append((char)c);
				}
				buf.append((char)c);
			}
			else if (c=='\'')
			{	buf.append((char)c);
				while (true)
				{	c=read();
					if (c<0) return null;
					if (c=='\'') break;
					buf.append((char)c);
				}
				buf.append((char)c);
			}
			else buf.append((char)c);
		}
		for (int i=0; i<s.length(); i++) read();
		return buf.toString();
	}
	
	String Line=null;
	int LinePos;
	
	public int read ()
		throws XmlReaderException
	{	try
		{	if (Line==null)
			{	Line=In.readLine();
				LinePos=0;
				// System.out.println("Read --> "+Line);
			}
			if (Line==null) return -1;
			if (LinePos>=Line.length())
			{	Line=null;
				return '\n';
			}
			return Line.charAt(LinePos++);
		}
		catch (Exception e)
		{	return -1;
		}
	}

	/**
	@return If the string is at the current line position.
	*/
	public boolean found (String s)
	{	int n=s.length();
		if (LinePos+n>Line.length()) return false;
		for (int i=0; i<n; i++)
			if (s.charAt(i)!=Line.charAt(LinePos+i)) return false;
		return true;
	}
	
	public void exception (String s)
		throws XmlReaderException
	{	throw new XmlReaderException(s,Line,LinePos);
	}
	
	/**
	A test program.
	*/
	public static void main (String args[])
	{	try
		{	BufferedReader in=new BufferedReader(
				new InputStreamReader(
				new FileInputStream("rene\\util\\xml\\test.xml"),"UTF8"));
			XmlReader reader=new XmlReader(in);
			XmlTree tree=reader.scan();
			in.close();
			print(tree);
		}
		catch (XmlReaderException e)
		{	System.out.println(e.toString()+"\n"+
				e.getLine()+"\n"+"Position : "+e.getPos());
		}
		catch (IOException e)
		{	System.out.println(e);
		}
	}
	
	public static void print (XmlTree t)
	{	XmlTag tag=t.getTag();
		System.out.print("<"+tag.name());
		for (int i=0; i<tag.countParams(); i++)
		{	System.out.print(" "+tag.getParam(i)+"=\""+tag.getValue(i)+"\"");
		}
		System.out.println(">");
		ListElement el=t.children().first();
		while (el!=null)
		{	print((XmlTree)(el.content()));
			el=el.next();
		}
		System.out.println("</"+tag.name()+">");
	}
	
	public static boolean testXml (String s)
	{	int i=0;
		while (i<s.length())
		{	char c=s.charAt(i);
			if (c=='<') break;
			i++;
		}
		if (i>=s.length()) return false;
		if (s.substring(i).startsWith("<?xml") || s.substring(i).startsWith("< ?xml") ) return true;
		return false;
	}
}
