/*
 * Distributed as part of mchange-commons-java 0.2.11
 *
 * Copyright (C) 2015 Machinery For Change, Inc.
 *
 * Author: Steve Waldman <swaldman@mchange.com>
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of EITHER:
 *
 *     1) The GNU Lesser General Public License (LGPL), version 2.1, as 
 *        published by the Free Software Foundation
 *
 * OR
 *
 *     2) The Eclipse Public License (EPL), version 1.0
 *
 * You may choose which license to accept if you wish to redistribute
 * or modify this work. You may offer derivatives of this work
 * under the license you have chosen, or you may provide the same
 * choice of license which you have been offered here.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received copies of both LGPL v2.1 and EPL v1.0
 * along with this software; see the files LICENSE-EPL and LICENSE-LGPL.
 * If not, the text of these licenses are currently available at
 *
 * LGPL v2.1: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 *  EPL v1.0: http://www.eclipse.org/org/documents/epl-v10.php 
 * 
 */

package com.mchange.v1.db.sql;

import java.io.*;
import java.net.*;
import java.sql.*;
import java.util.*;
import org.xml.sax.*;
import javax.xml.parsers.*;

public class XmlSchema implements Schema
{
    private final static int CREATE = 0;
    private final static int DROP   = 1;

    List createStmts;
    List dropStmts;
    Map  appMap;

    public XmlSchema(URL xmlSchema) throws SAXException, IOException, ParserConfigurationException
    {parse(xmlSchema.openStream());}

    public XmlSchema(InputStream xmlStream) throws SAXException, IOException, ParserConfigurationException
    {parse(xmlStream);}

    public XmlSchema()
    {}

    public void parse(InputStream is) throws SAXException, IOException, ParserConfigurationException
    {
	createStmts = new ArrayList();
	dropStmts   = new ArrayList();
	appMap      = new HashMap();

	InputSource isrc = new InputSource();
	isrc.setByteStream(is);

	/*
	 * This doesn't seem to matter. How do we validate???
	 */
	isrc.setSystemId(XmlSchema.class.getResource("schema.dtd").toExternalForm());

	SAXParser sp = SAXParserFactory.newInstance().newSAXParser();
	MySaxHandler testHandler = new MySaxHandler();

	// apparently the finalized interface no
	// longer includes these functions..
	// everything is set, I hope, in parse...

	//sp.setDocumentHandler(testHandler);
	//sp.setErrorHandler(testHandler);

	sp.parse(isrc, testHandler);
    }

    private void doStatementList(List stmtList, Connection con)
	throws SQLException
    {
	if (stmtList != null)
	    {
		Statement stmt = null;
		try
		    {
			stmt = con.createStatement();
			for (Iterator ii = stmtList.iterator(); ii.hasNext();)
			    stmt.executeUpdate((String) ii.next());
			con.commit();
		    }
		catch (SQLException e)
		    {
			ConnectionUtils.attemptRollback(con);
			e.fillInStackTrace();
			throw e;
		    }
		finally
		    {StatementUtils.attemptClose(stmt);}
	    }
    }

    public String getStatementText(String appName, String stmtName)
    {
	SqlApp app = (SqlApp) appMap.get(appName);
	String out = null;
	if (app != null)
	    out = app.getStatementText(stmtName);
	return out;
    }

    public void createSchema(Connection con) throws SQLException
    {doStatementList(createStmts, con);}

    public void dropSchema(Connection con) throws SQLException
    {doStatementList(dropStmts, con);}


    public static void main(String[] argv)
    {
	try
	    {
		Schema s = new XmlSchema(XmlSchema.class.getResource("/com/mchange/v1/hjug/hjugschema.xml"));
		
	    }
	catch (Exception e)
	    {e.printStackTrace();}

    }

    class MySaxHandler extends HandlerBase
    {
	int          state = -1;             //-1 unless we are in drop or create
	boolean      in_statement    = false;
	boolean      in_comment      = false;
	StringBuffer charBuff        = null;
	SqlApp       currentApp      = null; //non-null iff we are in an application
	String       currentStmtName = null; //non-null iff we are in a named (application) stmt

	public void startElement(String name, AttributeList attributes)
	{
	    if (name.equals("create"))
		state = CREATE;
	    else if (name.equals("drop"))
		state = DROP;
	    else if (name.equals("statement"))
		{
		    in_statement = true;
		    charBuff = new StringBuffer();
		    if (currentApp != null)
			{
			    for (int i = 0, len = attributes.getLength(); i < len; ++i)
				{
				    String attr = attributes.getName(i);
				    if (attr.equals("name"))
					{
					    currentStmtName = attributes.getValue(i);
					    break;
					}
				}
			}
		}
	    else if (name.equals("comment"))
		in_comment = true;
	    else if (name.equals("application"))
		{
		    for (int i = 0, len = attributes.getLength(); i < len; ++i)
			{
			    String attr = attributes.getName(i);
			    if (attr.equals("name"))
				{
				    String appName = attributes.getValue(i);
				    currentApp     = (SqlApp) appMap.get(appName);
				    if (currentApp == null)
					{
					    currentApp = new SqlApp();
					    appMap.put(appName.intern(), currentApp);
					}
				    break;
				}
			}
		}
	}

	public void characters(char[] ch, int start, int length)
	    throws SAXException
	{
	    if (!in_comment)
		{
		    if (in_statement)
			charBuff.append(ch, start, length);
		}
	}

	public void endElement(String name)
	{
	    if (name.equals("statement"))
		{
		    String stmtStr = charBuff.toString().trim();
		    if (state == CREATE)
			createStmts.add(stmtStr);
		    else if (state == DROP)
			dropStmts.add(stmtStr);
		    //System.out.println("Statement: " + stmtStr);
		    else if (currentApp != null && currentStmtName != null) //named app stmt
			currentApp.setStatementText(currentStmtName, stmtStr);
		}
	    else if (name.equals("create") || name.equals("drop"))
		state = -1;
	    else if (name.equals("comment"))
		in_comment = false;
	    else if (name.equals("application"))
		currentApp = null;
	}

	//from IBM examples,.,.
	/** Warning. */
	public void warning(SAXParseException ex) {
	    System.err.println("[Warning] "+
			       //getLocationString(ex)+": "+
			       ex.getMessage());
	}
		
	/** Error. */
	public void error(SAXParseException ex) {
	    System.err.println("[Error] "+
			       //getLocationString(ex)+": "+
			       ex.getMessage());
	}
		
	/** Fatal error. */
	public void fatalError(SAXParseException ex) throws SAXException {
	    System.err.println("[Fatal Error] "+
			       //getLocationString(ex)+": "+
			       ex.getMessage());
	    throw ex;
	}
    }

    class SqlApp
    {
	Map stmtMap = new HashMap();

	public void setStatementText(String name, String sql)
	{stmtMap.put(name, sql);}

	public String getStatementText(String name)
	{return (String) stmtMap.get(name);}
    }
}
