/******************************************
 * XML2Shape.java
 * Uses the NVS shapefile library to read 
 *  the specified XML file and translate
 *  it into an ESRI shapefile
 *
 *****************************************/


import java.io.*;
import com.nvs.shapefile.*;
import java.util.zip.*;
import java.io.*;
import java.util.*;
import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;
import org.apache.xerces.parsers.*;

public class XML2Shape 
{
	private static String strXMLFile;
	private static String strShapefile;
	
	public static void main(String args[])
	{
		if(args.length < 2)
		{
			System.out.println("Usage: XML2Shape <xml_file> <shape_file>");
			return;
		}
		
		strXMLFile = args[0];
		strShapefile = args[1];
		
		new XML2Shape();
	}
	
	public XML2Shape()
	{	
		Shapefile shp = new Shapefile();
		try
		{	
			TableDescription tableDesc = null;
			BoundingBox box = null;
		
			SAXBuilder builder = new SAXBuilder(true);		
			Document doc = builder.build(new FileInputStream(strXMLFile));
			
			Element elShapefile = doc.getRootElement();
			
			Element elBox = elShapefile.getChild("bounding-box");
			if(elBox != null)
			{
				box = processBox(elBox);
				shp.setBoundingBox(box);
			}
			
			Element elTable = elShapefile.getChild("table");
			if(elTable != null)
			{
				tableDesc = processTable(elTable);
				shp.setTableDescription(tableDesc);
			}
				
			if(elShapefile.getAttributeValue("type").equals("points"))
			{
				shp.setType(Shapefile.SHAPETYPE_POINT);
			}
			else if(elShapefile.getAttributeValue("type").equals("lines"))
			{
				shp.setType(Shapefile.SHAPETYPE_POLYLINE);
			}
			else if(elShapefile.getAttributeValue("type").equals("polygons"))
			{
				shp.setType(Shapefile.SHAPETYPE_POLYGON);
			}
			else if(elShapefile.getAttributeValue("type").equals("mixed"))
			{
				// Need to implement this eventually
				return;
			}
			
			// Shape Objects
			Iterator itrShapeObjects = elShapefile.getChildren("shape").iterator();
			while(itrShapeObjects.hasNext())
			{
				Element elShape = (Element)itrShapeObjects.next();
				if(elShape.getAttributeValue("type").equals("point"))
				{
					if(shp.getType() != Shapefile.SHAPETYPE_POINT)
					{
						// Throw some exception
					}	
					
					shp.addShapeObject(processPointShape(elShape));
				}
				else if(elShape.getAttributeValue("type").equals("line"))
				{
					if(shp.getType() != Shapefile.SHAPETYPE_POLYLINE)
					{
						// Throw some exception
					}	
					System.out.println("adding shapeobject - line");
					shp.addShapeObject(processLineShape(elShape));
				}
				else if(elShape.getAttributeValue("type").equals("polygon"))
				{
					if(shp.getType() != Shapefile.SHAPETYPE_POLYGON)
					{
						// Throw some exception
					}	
					System.out.println("adding shapeobject - polygon");
					shp.addShapeObject(processPolygonShape(elShape));
				}
			}
				
			ByteArrayOutputStream bosMain = new ByteArrayOutputStream();
			ByteArrayOutputStream bosIndex = new ByteArrayOutputStream();
			ByteArrayOutputStream bosRecords = new ByteArrayOutputStream();
			
			shp.write(bosMain, bosIndex, bosRecords);
			
			ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(strShapefile + ".zip"));
			zip.setMethod(ZipOutputStream.DEFLATED);
			zip.setLevel(Deflater.BEST_COMPRESSION);
					
			byte[] buffer = new byte[1024];         
			int length;
			
			ByteArrayInputStream bisMain = new ByteArrayInputStream(bosMain.toByteArray());
			ByteArrayInputStream bisIndex = new ByteArrayInputStream(bosIndex.toByteArray());
			ByteArrayInputStream bisRecords = new ByteArrayInputStream(bosRecords.toByteArray());
			
			ZipEntry entryMain = new ZipEntry(strShapefile + ".shp");
			zip.putNextEntry(entryMain);
			while ((length = bisMain.read(buffer)) >= 0) 
			{                  
				zip.write(buffer, 0, length);  
			}
			zip.closeEntry();
			
			ZipEntry entryIndex = new ZipEntry(strShapefile + ".shx");
			zip.putNextEntry(entryIndex);
			while ((length = bisIndex.read(buffer)) >= 0) 
			{                  
				zip.write(buffer, 0, length);  
			}
			zip.closeEntry();
			
			ZipEntry entryRecords = new ZipEntry(strShapefile + ".dbf");
			zip.putNextEntry(entryRecords);
			while ((length = bisRecords.read(buffer)) >= 0) 
			{                  
				zip.write(buffer, 0, length);  
			}
			zip.closeEntry();
			zip.close();
		}
		catch(JDOMException e)
		{
			e.printStackTrace();
		}
		catch(InvalidFieldNameException e)
		{
			e.printStackTrace();
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	
	
	
	
	private ShapeObject processPointShape(Element elPointShape)
	{
		ShapeObject shpObj = new ShapeObject(ShapeObject.POINT);
		Element elPoint = elPointShape.getChild("point");
		Point pt = new Point(Double.parseDouble(elPoint.getAttributeValue("x")), Double.parseDouble(elPoint.getAttributeValue("y")));
		shpObj.addPoint(pt);
		
		Record rec = new Record();
		Iterator itrFields = elPointShape.getChildren("field").iterator();
		while(itrFields.hasNext())
		{
			Element elField = (Element)itrFields.next();
			RecordField recField = new RecordField();
			recField.setName(elField.getAttributeValue("name"));
			recField.setValue(elField.getAttributeValue("value"));
			rec.addField(recField);
		}
		
		shpObj.setRecord(rec);
		
		return shpObj;
	}
	
	
	
	
	
	private ShapeObject processLineShape(Element elLineShape)
	{
		ShapeObject shpObj = new ShapeObject(ShapeObject.POLYLINE);
		Iterator itrPoints = elLineShape.getChildren("point").iterator();
		while(itrPoints.hasNext())
		{
			Element elPoint = (Element)itrPoints.next();
			Point pt = new Point(Double.parseDouble(elPoint.getAttributeValue("x")), Double.parseDouble(elPoint.getAttributeValue("y")));
			System.out.println("Adding point - " + elPoint.getAttributeValue("x") + " " + elPoint.getAttributeValue("y"));
			shpObj.addPoint(pt);
		}
		
		Record rec = new Record();
		Iterator itrFields = elLineShape.getChildren("field").iterator();
		while(itrFields.hasNext())
		{
			Element elField = (Element)itrFields.next();
			RecordField recField = new RecordField();
			recField.setName(elField.getAttributeValue("name"));
			recField.setValue(elField.getAttributeValue("value"));
			System.out.println("Adding record " + elField.getAttributeValue("name") + " " + elField.getAttributeValue("value"));
			rec.addField(recField);
		}
		shpObj.setRecord(rec);
		
		return shpObj;
	}
	
	
	private ShapeObject processPolygonShape(Element elLineShape)
	{
		ShapeObject shpObj = new ShapeObject(ShapeObject.POLYGON);
		Iterator itrPoints = elLineShape.getChildren("point").iterator();
		while(itrPoints.hasNext())
		{
			Element elPoint = (Element)itrPoints.next();
			Point pt = new Point(Double.parseDouble(elPoint.getAttributeValue("x")), Double.parseDouble(elPoint.getAttributeValue("y")));
			System.out.println("Adding point - " + elPoint.getAttributeValue("x") + " " + elPoint.getAttributeValue("y"));
			shpObj.addPoint(pt);
		}
		
		Record rec = new Record();
		Iterator itrFields = elLineShape.getChildren("field").iterator();
		while(itrFields.hasNext())
		{
			Element elField = (Element)itrFields.next();
			RecordField recField = new RecordField();
			recField.setName(elField.getAttributeValue("name"));
			recField.setValue(elField.getAttributeValue("value"));
			System.out.println("Adding record " + elField.getAttributeValue("name") + " " + elField.getAttributeValue("value"));
			rec.addField(recField);
		}
		shpObj.setRecord(rec);
		
		return shpObj;
	}
	
	
	private TableDescription processTable(Element elTD)
	{
		TableDescription tableDesc = new TableDescription();
		
		Iterator itrTD = elTD.getChildren("entry").iterator();
		while(itrTD.hasNext())
		{
			int iType = Shapefile.FIELDTYPE_CHARACTER;
			Element elEntry = (Element)itrTD.next();
			
			String strDatatype = elEntry.getAttributeValue("datatype");
			
			if(strDatatype.equals("string"))
			{
				iType = Shapefile.FIELDTYPE_CHARACTER;
			}
			
			tableDesc.addTableDescriptor(new TableDescriptor(elEntry.getAttributeValue("name"), iType));
		}
		return tableDesc;
	}
	
	
	
	
	
	private BoundingBox processBox(Element elBox)
	{
		BoundingBox box = new BoundingBox();
		
		if(elBox.getAttributeValue("xmin") != null)
			box.setXMin(Double.parseDouble(elBox.getAttributeValue("xmin")));
			
		if(elBox.getAttributeValue("xmax") != null)
			box.setXMax(Double.parseDouble(elBox.getAttributeValue("xmax")));
			
		if(elBox.getAttributeValue("ymin") != null)
			box.setYMin(Double.parseDouble(elBox.getAttributeValue("ymin")));
			
		if(elBox.getAttributeValue("ymax") != null)
			box.setYMax(Double.parseDouble(elBox.getAttributeValue("ymax")));
			
		return box;
	}
}