/*
 * Created on Feb 7, 2004
 *
 */
package LumiSoft.Data;

import java.io.*;
import org.w3c.dom.*;

import sun.awt.image.ByteInterleavedRaster;
import sun.misc.BASE64Encoder;
import javax.xml.parsers.*;

/**
 * Represents an in-memory cache of data.
 */
public class DataSet 
{
	private String                 m_DataSetName = "";
	private DataTableCollection    m_pTables     = null;
	private DataRelationCollection m_pRelations  = null; 
	
	/**
	 * Default constructor.
	 *
	 */
	public DataSet()
	{
		m_pTables     = new DataTableCollection(this);
		m_pRelations  = new DataRelationCollection();
		m_DataSetName = "NewDataSet"; 		
	}
	
	/**
	 * Creates new dataset with specified name.
	 * @param name DataSet name.
	 */
	public DataSet(String name)
	{
		this();
		
		m_DataSetName = name;	
	}
	
	/**
	 * Reads xml from specified file.
	 * @param fileName File name from where to read DataSet.
	 * @throws Exception
	 */
	public void ReadXml(String fileName) throws Exception
	{
		FileInputStream stream = new FileInputStream(fileName);
			
		ReadXml(stream);
			
		stream.close();
	}
	
		
	/**
	 * Reads xml from specified stream.
	 * @param stream Stream from where to read DataSet.
	 */
	public void ReadXml(InputStream stream)
	{
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		
        try{
           DocumentBuilder builder = factory.newDocumentBuilder();
           Document document = builder.parse(stream);
             
           NodeList nList = document.getChildNodes();
           
           // Search dataset node
           Node dataSetNode = null;
           for(int i=0;i<nList.getLength();i++){
           		if(nList.item(i).getNodeType() == 1){
           			dataSetNode = nList.item(i);
           		}
           }
           
           // Root node is dataset name
           if(dataSetNode == null){
           		System.out.println("Error no root node");
           		return;
           }
           
           m_DataSetName = dataSetNode.getNodeName();
           
           // Read schema
           if(dataSetNode.getChildNodes().item(1).getChildNodes().item(1).getChildNodes().item(1) != null){
	           NodeList dtSchemaNodes = dataSetNode.getChildNodes().item(1).getChildNodes().item(1).getChildNodes().item(1).getChildNodes().item(1).getChildNodes();
	     
	           for(int i=0;i<dtSchemaNodes.getLength();i++){           	
	           		if(dtSchemaNodes.item(i).getNodeType() == 1){
	           			// Parse table info
	           			Node dtNode = dtSchemaNodes.item(i);
	           			String tableName = dtNode.getAttributes().getNamedItem("name").getNodeValue();
	           			DataTable dt = this.getTables().Add(tableName);
	           			           			          			
	           			Node dcColumnNodes = dtNode.getChildNodes().item(1).getChildNodes().item(1);
	           			for(int c=0;c<dcColumnNodes.getChildNodes().getLength();c++){
	           				if(dcColumnNodes.getChildNodes().item(c).getNodeType() == 1){
	           					NamedNodeMap colInfo = dcColumnNodes.getChildNodes().item(c).getAttributes();
	           					
	           					String dataTypeStr = colInfo.getNamedItem("type").getNodeValue().toLowerCase();
	           					Class dataType = String.class;
	           					if(dataTypeStr.equals("xs:datetime")){
	           						dataType = java.util.Date.class;
	           					}
	           					else if(dataTypeStr.equals("xs:base64binary")){
	           						dataType = byte[].class;
	           					}
	           					else if(dataTypeStr.equals("xs:decimal")){
	           						dataType = java.math.BigDecimal.class;
	           					}
	           					else if(dataTypeStr.equals("xs:boolean")){
	           						dataType = boolean.class;
	           					}
	           					else if(dataTypeStr.equals("xs:int")){
	           						dataType = int.class;
	           					}
	           				
	           					dt.getColumns().Add(colInfo.getNamedItem("name").getNodeValue(),dataType);
	           		//			System.out.println(tableName + " " + colInfo.getNamedItem("name") + " " + colInfo.getNamedItem("type"));
	           				}
	           			}
	           		}
	           }
           }
           
           // Parse data
           nList = dataSetNode.getChildNodes();
           for(int i=0;i<nList.getLength();i++){
           		if(nList.item(i).getNodeType() == 1 && !nList.item(i).getNodeName().startsWith("xs:")){
           			Node tableNode = nList.item(i);
           			NodeList columns = tableNode.getChildNodes();
           			
           			// See if table already exists, if not, then create it
           			if(!m_pTables.Contains(tableNode.getNodeName())){
           				m_pTables.Add(tableNode.getNodeName());
           				DataTable dt = m_pTables.get(tableNode.getNodeName());
           				
           				// Create columns           				
           				for(int c=0;c<columns.getLength();c++){
           					if(columns.item(c).getNodeType() == 1){
           						dt.getColumns().Add(columns.item(c).getNodeName());
           			//			System.out.println(dt.getTableName() + " " + columns.item(c).getNodeName());
           					}           					
           				}
           			}
           			DataTable dt = m_pTables.get(tableNode.getNodeName());
         			
           			// Add row
           			DataRow dr = dt.NewRow();
           			int colCounter = 0;
       				for(int c=0;c<columns.getLength();c++){
       					if(columns.item(c).getNodeType() == 1){
       						String columnName = columns.item(c).getNodeName();
       						
       						if(columns.item(c).getChildNodes().item(0) != null){ 
       							dr.set(columnName,columns.item(c).getChildNodes().item(0).getNodeValue());
       						}
       						else{
       							// Set default value
       							dr.set(columnName,"");
       						}
       							
       						colCounter++;
       					}           					
       				}
       				
       				// Insert row to DataTable
       				dt.getRows().Add(dr.getItemArray());
           		}
           }     
        }
        catch(Exception x){
        	x.printStackTrace();
        }
	}
	
	/**
	 * Writes the current data for the DataSet to the specified file.
	 * @param fileName The file name (including the path) to which to write.
	 * @throws Exception
	 */
	public void WriteXml(String fileName) throws Exception
	{
		WriteXml(fileName,false);
	}
	/**
	 * Writes the current data for the DataSet to the specified file.
	 * @param fileName The file name (including the path) to which to write.
	 * @param writeSchema
	 * @throws Exception
	 */
	public void WriteXml(String fileName,boolean writeSchema) throws Exception
	{
		FileOutputStream fos = new FileOutputStream(fileName);
		WriteXml(fos,writeSchema);
		fos.close();
	}
	
	/**
	 * Writes DataSet data to specified stream.
	 * @param stream Stream where to write data.
	 * @throws Exception
	 */
	public void WriteXml(OutputStream stream) throws Exception
	{
		WriteXml(stream,false);
	}
	/**
	 * Writes DataSet data to specified stream.
	 * @param stream Stream where to write data.
	 * @param writeSchema Specifies if schema must be written.
	 * @throws Exception
	 */
	public void WriteXml(OutputStream stream,boolean writeSchema) throws Exception
	{
		StringBuffer sBuffer = new StringBuffer();
        
		sBuffer.append("<" + getDataSetName() + ">\r\n");
		
		//--- Write schema
		if(writeSchema){
			WriteSchema(sBuffer);
		}
		
		//--- Write data ----------------------------------------------------------------------------------------//
		for(int t=0;t<getTables().getCount();t++){
			DataTable dt = m_pTables.get(t);
			for(int r=0;r < dt.getRows().getCount();r++){
				DataRow dr = dt.getRows().get(r);
				sBuffer.append("  <" + dt.getTableName() + ">\r\n");
				for(int c=0;c<dt.getColumns().getCount();c++){
					DataColumn dc = dt.getColumns().get(c);
					
					String value = "";
					if(dr.get(c) != null){												
						if(dc.getDataType() == java.util.Date.class){							
							java.text.SimpleDateFormat f = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSZ");
							value = f.format(dr.get(c));
							value = value.substring(0,value.length() - 2) + ":" + value.substring(value.length() - 2);
						}
						else if(dc.getDataType() == byte[].class){
							BASE64Encoder base64 = new BASE64Encoder();
							value = base64.encode((byte[])dr.get(c));
						}
						else{						
							value = dr.get(c).toString();
						}
						
						sBuffer.append("    <" + dc.getColumnName() + ">" + ToXmlValue(value) + "</" + dc.getColumnName() + ">\r\n");
					}					
				}
				sBuffer.append("  </" + dt.getTableName() + ">\r\n");
			}
		}
		//--- End of data writing ------------------------------------------------------------------------------//
		
		sBuffer.append("</" + getDataSetName() + ">\r\n");
	
		stream.write(sBuffer.toString().getBytes("UTF8"));		
	}
	
	/**
	 * Writes Xml schema to specified stream.
	 * @param stream
	 * @throws Exception
	 */
	public void WriteXmlSchema(OutputStream stream) throws Exception
	{
		StringBuffer sBuffer = new StringBuffer();
        
		sBuffer.append("<" + getDataSetName() + ">\r\n");
				
		WriteSchema(sBuffer);
		
		sBuffer.append("</" + getDataSetName() + ">\r\n");
		
		stream.write(sBuffer.toString().getBytes("UTF8"));		
	}
	
	/**
	 * Clears the DataSet of any data by removing all rows in all tables.
	 */
	public void Clear()
	{
		for(int i=0;i<m_pTables.getCount();i++){
			m_pTables.get(i).Clear();
		}
	}
	
	/**
	 * Copies the structure of the DataSet. Does not copy any data.
	 *
	 */
	public void Clone()
	{
		//TODO:
	}
	
	/**
	 * Copies both the structure and data for this DataSet.
	 *
	 */
	public DataSet Copy() throws Exception
	{		
		ByteArrayOutputStream ms = new ByteArrayOutputStream();
		WriteXml(ms,true);
		
		DataSet ds = new DataSet();
		ds.ReadXml(new ByteArrayInputStream(ms.toByteArray()));
		
		return ds;
	}
	
	/**
	 * Merges a specified DataTable and its schema into the current DataSet.
	 * @param table
	 */
	public void Merge(DataTable table)
	{
		// if table won't exist, create it
		if(!m_pTables.Contains(table.getTableName())){
			try{
				DataTable dtNew = m_pTables.Add(table.getTableName());
				
				for(int c=0;c<table.getColumns().getCount();c++){
					DataColumn dc = table.getColumns().get(c);
					
					dtNew.getColumns().Add(dc.getColumnName(),dc.getDataType());
				}
			}
			catch(Exception x){
				x.printStackTrace();
			}
		}
		
		DataTable destTable = getTables().get(table.getTableName());
		for(int i=0;i<table.getRows().getCount();i++){
			DataRow dr = table.getRows().get(i);
			
			destTable.getRows().Add(dr.getItemArray());
		}
	}
	
	
	/**
	 * Writes DataSet schema.
	 * @param buff
	 */
	private void WriteSchema(StringBuffer buff)
	{
		//--- Write schema ------------------------------//
		buff.append("  <xs:schema id=\"NewDataSet\" xmlns=\"\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\r\n");
		buff.append("  <xs:element name=\"NewDataSet\" msdata:IsDataSet=\"true\">\r\n");
		buff.append("    <xs:complexType>\r\n");
		buff.append("      <xs:choice maxOccurs=\"unbounded\">\r\n");
		
		for(int i=0;i<getTables().getCount();i++){
			DataTable dt = m_pTables.get(i);
			
			buff.append("        <xs:element name=\"" + dt.getTableName() + "\">\r\n");
			buff.append("          <xs:complexType>\r\n");
			buff.append("            <xs:sequence>\r\n");
			
			for(int c=0;c<getTables().get(i).getColumns().getCount();c++){
				DataColumn dc = getTables().get(i).getColumns().get(c);
				
				String dataType = "xs:string";
				if(dc.getDataType() == java.util.Date.class){
					dataType = "xs:dateTime";
				}
				else if(dc.getDataType() == java.math.BigDecimal.class){
					dataType = "xs:decimal";
				}
				else if(dc.getDataType() == byte[].class){
					dataType = "xs:base64Binary";
				}
				else if(dc.getDataType() == boolean.class){
					dataType = "xs:boolean";
				}
				else if(dc.getDataType() == int.class){
					dataType = "xs:int";
				}
			//	System.out.println(dc.getDataType());
				
				buff.append("              <xs:element name=\"" + dc.getColumnName() + "\" type=\"" + dataType + "\" minOccurs=\"0\" />\r\n");				
			}
			
			buff.append("            </xs:sequence>\r\n");
			buff.append("          </xs:complexType>\r\n");
			buff.append("        </xs:element>\r\n");
		}
		
		buff.append("      </xs:choice>\r\n");
		buff.append("    </xs:complexType>\r\n");
		buff.append("  </xs:element>\r\n");
		buff.append("</xs:schema>\r\n");
		//--- End of schema -------------------------------------------------------------------------------------//
	}
	
	/**
	 * Escapes all xml illegal values. Eg. <>&
	 * @param value
	 * @return
	 */
	private String ToXmlValue(String value)
	{
		value = value.replace("&","&amp;");
		value = value.replace("<","&lt;");
		value = value.replace(">","&gt;");		
		value = value.replace("'","&apos;");
		value = value.replace("\"","&quot;");
		
		return value;
	}
	
	
	/**
	 * Gets the name of the current DataSet.
	 */
	public String getDataSetName()
	{
		return m_DataSetName;
	}
	/**
	 * Sets the name of the current DataSet.
	 * @param value
	 */
	public void setDataSetName(String value)
	{
		if(m_DataSetName != value){
			m_DataSetName = value;
		}
	}
	
	/**
	 * Gets the collection of tables contained in the DataSet.
	 * @return
	 */
	public DataTableCollection getTables()
	{
		return m_pTables;
	}
	
	/**
	 * Get the collection of relations that link tables and allow navigation from parent tables to child tables.
	 * @return
	 */
	public DataRelationCollection getRelations()
	{
		return m_pRelations;
	}
}
