import java.io.*;
import java.net.*;
import java.util.*;
import java.sql.*;

import org.apache.xerces.parsers.*;
import org.w3c.dom.*;
import org.w3c.dom.traversal.*;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import org.xml.sax.SAXException;
import org.apache.xalan.xslt.XSLTProcessorFactory;
import org.apache.xalan.xslt.XSLTInputSource;
import org.apache.xalan.xslt.XSLTResultTarget;
import org.apache.xalan.xslt.XSLTProcessor;

// Supplier Class
// This is the entry point for the Supplier servers and creates three threads, one for each supplier.
// In a real distributed system, each of these 'threads' would run on a seperate machine.
public class Supplier {
	static SupplierServer AWServer, PHServer, ORServer;			// three supplier servers
 
	public static void main(String[] args)
	{
		DBManager dbManager = new DBManager();
		AWServer = new SupplierServer(dbManager, 1313, "A", null, null);			// this is the 'standard'
		PHServer = new SupplierServer(dbManager, 1314, "B", "stylesheets/supplier/PHtoAWQuery.xsl", "stylesheets/supplier/AWtoPHResponse.xsl");
		ORServer = new SupplierServer(dbManager, 1315, "C", "stylesheets/supplier/ORtoAWQuery.xsl", "stylesheets/supplier/AWtoORResponse.xsl");
			
		AWServer.start();
		PHServer.start();
		ORServer.start();
		dbManager.start();
	}
}

// SupplierServer Class
// A SupplierServer listens for Socket connections on a given port, and uses the given Xslt stylesheets
// to transform an arriving query into it's 'standard' form (AW), and to convert the server's response
// into the proper format.
class SupplierServer extends Thread
{
	ServerSocket server;					// the socket server
	String _Title, _Quantity;				// globals for a given book title and quantity
	DBManager _DBManager;					// allows servers to make database calls
	
	String _XslQueryURL;					// the path to the Xslt stylesheet to transform a query into the 'standard'
	String _XslResponseURL;					// the path to the Xslt stylesheet to transform the 'standard' response to a specific response
	String _ServerName;
	
	// SupplierServer
	// Inputs:	port - a port for the ServerSocket to listen on
	//			xslQueryURL - a URL path to the xslt styleshhet to stransform a query into the 'standard'
	//			xslResponseURL - a URL path to the Xslt stylesheet to transform the 'standard' response to a specific response
	SupplierServer(DBManager dbManager, int port, String serverName, String xslQueryURL, String xslResponseURL)
	{
		_DBManager = dbManager;
		_XslQueryURL = xslQueryURL;
		_XslResponseURL = xslResponseURL;
		_ServerName = serverName;
		
		try
		{
			server = new ServerSocket(port);			// create a new server
		}
		catch(Exception e)
		{
			System.out.println(e.toString());			// report any error to the console
		}
	}

	// run
	// starts the ServerSocket and listens on the specified port.
	// when it receives a connection, it grabs the socket's stream (a supplier query in Xml), handles it, and returns the response
	// ***NOTE: since the server grabs ONE LINE from the socket, it is important that client's do not inluce '\n' or '\r' characters in them!!
	public void run() {
		try
		{
			System.out.println("Server Initialized at " + server.getInetAddress().getHostName() + " on port " + server.getLocalPort());
			while(true)
			{
				Socket s = server.accept();
				System.out.println("Received connection from " + s.getInetAddress().getHostAddress() + " on port " + s.getLocalPort() + "\n");
				try {
					// Read the first line sent by the client
					InputStream in = s.getInputStream();
					BufferedReader d = new BufferedReader(new InputStreamReader(in));
					String line = d.readLine();
					System.out.println("Client said: " + line);
					

					System.out.println("\n");
					// handle the client's query
					String response = HandleClientQuery(line);
					
					// Wrap a PrintStream around the Socket's OutputStream
					PrintStream out = new PrintStream(s.getOutputStream());
					out.println(response);
					out.close();
				
					// Be sure to close the connection
					s.close();
				}
				catch (Exception e)
				{
					System.out.println(e.toString());
				}
			}
		}
		catch (Exception e)
		{
			System.out.println(e.toString());
		}
	}
	
	// HandleClientQuery
	// Inputs:	query - the Xml string that contains a client's query
	// Returns: a string that is the sever's response to the query in a single line of Xml
	private String HandleClientQuery (String query)
	{
		Document document;			//
		DOMParser parser;
		String response;

		// transform the query here
		if(_XslQueryURL != null)
		{
			// if we must, transform the query from its specific state into the 'standard' query (AW)
			query = TransformXml(query, _XslQueryURL);
			System.out.println("New Query: " + query);
		}
		
		try
		{
			// interpret the AW Query by moving through it's tree
			parser = new DOMParser();									// create an Xml parser
		
			StringReader xmlReader = new StringReader(query);
			parser.parse(new org.xml.sax.InputSource(xmlReader));		// parse the Xml
			document = parser.getDocument();							// get the storage tree
			Node root = document.getDocumentElement();					// move to the root of the storage tree
			
			// get title and quantity information by moving through the AW Xml tree
			TraceAWTree(root);
			
			// if the title and quantity we're set, return a failure
			if(_Title.equals("") || _Quantity.equals(""))
			{
				_Title = "";
				_Quantity = "";
				response = CreateFailXml();
			}
			else
			{
				// success!  look info up in the database
				
				String sqlString = "SELECT TITLE, AUTHOR, PRICE, ISBN FROM BOOKS B WHERE B.TITLE='" + _Title + "' AND B.SERVER='" + _ServerName + "'";
				ResultSet rs = _DBManager.SQLQuery(sqlString);
				if(rs != null)
				{
					if(rs.next() == true)
					{
						// book was found
						response = CreateSuccessXml(_Title, rs.getString("AUTHOR"), rs.getString("PRICE"), rs.getString("ISBN"), _Quantity, (new Long(_DBManager.GetXactId())).toString());
					}
					else
					{
						// book not found
						response = CreateFailXml();
					}
				}
				else
				{
					response = CreateFailXml();
				}	
			}
		}
		catch(Exception e)
		{
			response = CreateFailXml();				// something bad happened, report the error
			System.out.println(e.toString() + "\n");
		}
		
		// transform the response here from the 'standard' (AW) response to the specific response
		if(_XslResponseURL != null)
		{
			response = TransformXml(response, _XslResponseURL);
			System.out.println("New Response: " + response + "\n");
		}
		
		return RemoveCR(response);				// return the response WITHOUT any line breaks
	}
	
	// CreateSuccessXml
	// creates an Xml string with the appropriate values and formatting
	private String CreateSuccessXml(String title, String author, String price, String isbn, String quantity, String id)
	{
		String xml = "<?xml version='1.0'?>";
		xml += "<single_book_order>";
		xml += "<title>" + title + "</title>";
		xml += "<author>" + author + "</author>";
		xml += "<price>" + price + "</price>";
		xml += "<isbn>" + isbn + "</isbn>";
		xml += "<qty>" + quantity + "</qty>";
		xml += "<order_id>" + id + "</order_id>";
		xml += "</single_book_order>";
		return xml;
	}
	
	// CreateFailXml
	// creates an Xml string formatted for an 'item not found'
	private String CreateFailXml()
	{
		String xml = "<?xml version='1.0'?><single_book_order><fail/></single_book_order>";
		return xml;
	}
	
	// TraceAWTree
	// given a Node in an Xml tree, do a DFS search on it to find the important nodes: 'title' and 'qty'
	private void TraceAWTree(Node node)
	{
		Node textNode;
		if(node != null)
		{
			// trace me
			switch(node.getNodeType())
			{
			case Node.ELEMENT_NODE:							// the interesting nodes are of type 'ELEMENT_NODE'
				if("title".equals(node.getNodeName()) == true)
				{
					// move to the child and grab its text
					textNode = node.getFirstChild();		// the text is the first child of this node
					_Title = textNode.getNodeValue();		// grab the text
				}
				else if("qty".equals(node.getNodeName()) == true)
				{
					// move to the child and grab its text 
					textNode = node.getFirstChild();		// the text is the first child of this node
					_Quantity = textNode.getNodeValue();	// grab the text
				}
				break;
			}
		
			// trace my children in a DFS manner
			NodeList children = node.getChildNodes();
			if(children != null)
			{
				for(int i=0; i<children.getLength(); i++)
				{
					TraceAWTree(children.item(i));		
				}
			}
		}
	}
	
	// TransformXml
	// transforms a string of Xml using the specified stylesheet
	// Inputs:	xmlString - the Xml string to transform
	//			xslURL - the URL to the stylesheet to use
	// Returns: a string representing the transformed Xml
	private static String TransformXml (String xmlString, String xslURL)
	{
		String result;
		try
		{
			// get the processor
			XSLTProcessor processor = org.apache.xalan.xslt.XSLTProcessorFactory.getProcessor();

			// read the string into the proper Reader type, and prepare the output buffer
			StringReader xmlReader = new StringReader(xmlString);
			StringWriter resultBuffer = new StringWriter();
	
			// transform the Xml string
			processor.process(new XSLTInputSource(xmlReader),
							  new XSLTInputSource(xslURL),
					          new XSLTResultTarget(resultBuffer));
			
			// get the resulting string
			result = resultBuffer.toString();
		}
		catch(Exception e)
		{
			result = "";
		}
		return result;
	}

	// RenoveCR
	// removes '\n' and '\r' characters from a string by replacing them with spaces ' '
	private static String RemoveCR (String target)
	{
		target = target.replace('\n', ' ');
		target = target.replace('\r', ' ');
		return target;
	}
		
}

// DBManager
// a thread that does database connections
// NOTE:
//	the _DBSource, _Username, and _Password for the database table have been changed for security purposes
//  _DBSource should be the string:  "jdbc:odbc:" + <your-datbase-dsn>"
//	_Username is your database username
//	_Password is your database password
class DBManager extends Thread
{
	private Connection		_Con = null;
	private String			_DBSource = "jdbc:odbc:xxx";		// not accurate, xxx is your DSN
	private String			_Username = "xxx";					// not accurate, xxx is your username
	private String			_Password = "xxx";					// not accurate, xxx is your password
	private long			_XactId;
	
	DBManager()
	{
		ReadPreferences();
	}
	
	public void run()
	{
		String command = "";
		InputStreamReader isr = new InputStreamReader(System.in);
		BufferedReader reader = new BufferedReader(isr);
		
		InitializeDBConnection(_DBSource, _Username, _Password);
		while(command.equals("q") == false)
		{
			DisplayMenu();
			try
			{
				command = reader.readLine();
				if(command.equals("s"))
				{
					String sql = "Select * From Books";
					ResultSet rs = SQLQuery(sql);
					while (rs.next())
					{
						String title = rs.getString("TITLE");
						String isbn = rs.getString("ISBN");
						String author = rs.getString("AUTHOR");
						String price = rs.getString("PRICE");
						String server = rs.getString("SERVER");
						
						System.out.println(title);
						System.out.println(author);
						System.out.println(isbn);
						System.out.println(price);
						System.out.println(server);
						System.out.println("");
					}
					
				}
			}
			catch(Exception e)
			{	
				System.out.println(e.toString());
			}
		}
		CloseDBConnection();
		WritePreferences();
		
		// kill the application by requesting an exit
		Runtime r = Runtime.getRuntime();
		r.exit(0);
	}
	
	private void DisplayMenu()
	{
		System.out.println("");
		System.out.println("Xml Supplier Server v.0.1");
		System.out.println("Menu: ");
		System.out.println(" m - Display Menu");
		System.out.println(" s - Simulate");
		System.out.println(" q - Quit");
		System.out.println("");
	}
	
	// initialize and open a database connection
	private void InitializeDBConnection (String db, String user, String password)
	{
		try
		{
			Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
		}
		catch (java.lang.ClassNotFoundException e)
		{
			
			System.out.print("ClassNotFoundExcpetion: ");
			System.out.println(e.getMessage());
		}
		
		try
		{
			_Con = DriverManager.getConnection(db, user, password);
		}
		catch (SQLException ex)
		{
			System.out.println(ex.toString());
		}
	}
	
	// close a database connection
	private void CloseDBConnection ()
	{
		if(_Con != null)
		{
			try
			{
				_Con.close();
			}
			catch (SQLException e) 
			{
				System.err.println(e.getMessage());
			}
		}
	}
	
	public ResultSet SQLQuery (String sql)
	{
		ResultSet rs = null;
		Statement stmt = null;

		try
		{
			stmt = _Con.createStatement();
			rs = stmt.executeQuery(sql);
		}
		catch (SQLException e)
		{
			System.out.println(e.getMessage());
		}
		return rs;
	}

	private void WritePreferences()
	{
		try
		{
			File f = new File("SupplierPref.txt");
			FileOutputStream fs = new FileOutputStream(f);
			PrintWriter pw = new PrintWriter(fs, true);
			pw.println(_XactId);
			pw.close();
			fs.close();
		}
		catch(Exception e)
		{
			System.out.println(e.toString());
		}
	}
	
	private void ReadPreferences()
	{
		try
		{
			File f = new File("SupplierPref.txt");
			FileInputStream fs = new FileInputStream(f);
			InputStreamReader is = new InputStreamReader(fs);
			BufferedReader bi = new BufferedReader(is);
			String line = bi.readLine();
			bi.close();
			is.close();
			fs.close();
			
			if(line != null)
			{
				_XactId = (new Long(line)).longValue();
			}
			else
			{
				_XactId = 1000;
			}
			System.out.println(_XactId);
		}
		catch(Exception e)
		{
			System.out.println(e.toString());
		}
	}
	
	public long GetXactId()
	{
		_XactId++;
		return _XactId;
	}
		
}