/**
 *   Copyright 2007 Y.Murakamin
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package net.murakamin.sticker.commands;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;

import net.murakamin.sticker.CommandRunner;
import net.murakamin.sticker.Commands;
import net.murakamin.sticker.ConnectionPool;
import net.murakamin.sticker.StickerContext;
import net.murakamin.sticker.commands.enums.PostChildCommandExecutionType;
import net.murakamin.sticker.commands.enums.PreChildCommandExecutionType;
import net.murakamin.sticker.commands.enums.TargetType;
import net.murakamin.sticker.commands.exception.CommandExecutionException;

/**
 * 
 * The command of the child element is repeatedly executed. The obtained record
 * ..number of lines.. is repeated by the
 * 
 * {@link net.murakamin.sticker.commands.CursorCommand &lt;cursor&gt;} command.
 * It is possible to use it in tag to which each field value of the record
 * obtained by the
 * {@link net.murakamin.sticker.commands.CursorCommand &lt;cursor&gt;} command
 * is enclosed with the
 * {@link net.murakamin.sticker.commands.ForCommand &lt;for&gt;} command.
 * 
 * @author Y.Murakamin
 * @see net.murakamin.sticker.commands.CursorCommand
 * @see net.murakamin.sticker.commands.IfCommand
 * @see net.murakamin.sticker.commands.ExportCommand
 * @see net.murakamin.sticker.commands.ImportCommand
 * @see net.murakamin.sticker.commands.InfoCommand
 * @see net.murakamin.sticker.commands.QueryImportCommand
 * @see net.murakamin.sticker.commands.Shell
 * @see net.murakamin.sticker.commands.SqlCommand
 * @see net.murakamin.sticker.commands.ParentCommand
 * @see net.murakamin.sticker.commands.ForCommand
 * @see net.murakamin.sticker.commands.enums.TargetType
 */
public class ForCommand implements ParentCommand
{

	Commands childlen = new Commands();
	private TargetType target = TargetType.remote;
	private String name;
	private Statement cursorStatement = null;
	private ResultSet cursorRecordSet = null;

	/**
	 * @see net.murakamin.sticker.commands.ParentCommand#addChild(ExecutableCommand)
	 */
	public void addChild(final ExecutableCommand child)
	{
		this.childlen.add(child);
	}

	/**
	 * The defined cursor is opened, and the record is acquired from the data
	 * base. The acquired frequencies of the number of records and the command
	 * of the child element is repeatedly executed.
	 * 
	 * @see net.murakamin.sticker.StickerContext
	 * @see net.murakamin.sticker.commands.ExecutableCommand#execute(CommandRunner)
	 */
	public void execute(final CommandRunner runner) throws Exception
	{
		ConnectionPool pool = runner.getConnectionPool();
		StickerContext context = runner.getStickerContext();

		Connection con = pool.getLocalConnection();

		CursorCommand cursor = (CursorCommand) context
		        .get(CursorCommand.CURSOR_NAME_PREFIX + this.getName());
		if (cursor == null)
		{
			throw new CommandExecutionException(
			        Messages
			                .getString("net.murakamin.sticker.commands.CursorCommand.Corsor_not_found") //$NON-NLS-1$
			                + this.getName());
		}

		if (this.target == TargetType.remote)
		{
			con = pool.getRemoteConnection();
		}

		try
		{
			this.cursorStatement = con.createStatement();
			this.cursorRecordSet = cursorStatement
			        .executeQuery(cursor.getSql());
		} catch (Exception e)
		{
			if (this.cursorStatement != null)
			{
				this.cursorStatement.close();
			}
			if (this.cursorRecordSet != null)
			{
				this.cursorRecordSet.close();
			}
			this.cursorRecordSet = null;
		}

		if (this.cursorRecordSet == null)
		{
			throw new CommandExecutionException(
			        this,
			        Messages
			                .getString("net.murakamin.sticker.commands.CursorCommand.Cursor_open_failure") //$NON-NLS-1$
			                + this.getName());
		}
		runner.run(this);
	}

	/**
	 * @see net.murakamin.sticker.commands.ParentCommand
	 */
	public Commands getChildlen()
	{
		return this.childlen;
	}

	/**
	 * Get the cursor name used by the loop.
	 * 
	 * @return cursor name
	 */
	public String getName()
	{
		return name;
	}

	/**
	 * The kind of the data base is acquired the data acquisition of the
	 * {@link net.murakamin.sticker.commands.CursorCommand &lt;cursor&gt;}
	 * command ahead.
	 * 
	 * @return the source of data acquisition
	 */
	public String getTarget()
	{
		return target.name();
	}

	/**
	 * @see net.murakamin.sticker.commands.ExecutableCommand#getVersionTerm()
	 */
	public VersionTerm getVersionTerm()
	{
		return new VersionTerm("0.1.3"); //$NON-NLS-1$
	}

	/**
	 * @see net.murakamin.sticker.commands.ExecutableCommand#isDebugPrint()
	 */
	public boolean isDebugPrint()
	{
		return false;
	}

	/**
	 * After the execution of the child element ends, the end judgment of the
	 * loop is done. When the loop processing doesn't end, one cursor position
	 * is advanced.
	 * 
	 * @see net.murakamin.sticker.commands.ParentCommand#postChildCommandExecute(CommandRunner)
	 * @return Enumerated type who shows whether it is end or continuance of
	 *         loop processing
	 * @see net.murakamin.sticker.commands.enums.PostChildCommandExecutionType
	 */
	public PostChildCommandExecutionType postChildCommandExecute(
	        final CommandRunner runner) throws Exception
	{
		boolean last = true;
		if (this.cursorRecordSet != null)
		{
			this.cursorRecordSet.next();
			last = this.cursorRecordSet.isAfterLast();
		}

		if (last)
		{
			try
			{
				if (this.cursorStatement != null)
				{
					this.cursorStatement.close();
				}
				if (this.cursorRecordSet != null)
				{
					this.cursorRecordSet.close();
				}
			} catch (SQLException e)
			{
				// Do nothing
			}
		} else
		{
			StickerContext context = runner.getStickerContext();
			if (this.cursorRecordSet != null)
			{
				ResultSetMetaData metaData = this.cursorRecordSet.getMetaData();
				Map<String, Object> fieldMap = new HashMap<String, Object>();
				for (int c = 1; c <= metaData.getColumnCount(); c++)
				{
					fieldMap.put(metaData.getColumnName(c).toUpperCase(),
					        this.cursorRecordSet.getObject(c));
				}

				context.pushFieldStack(fieldMap);
			}
		}

		return (last == true) ? PostChildCommandExecutionType.NOT_REPEAT
		        : PostChildCommandExecutionType.REPEAT_EXECUTE;
	}

	/**
	 * The cursor is opened when the loop processing is executed, and each field
	 * name of the record set opened with the cursor is preserved in
	 * {@link net.murakamin.sticker.StickerContext StickerContext}.
	 * 
	 * @see net.murakamin.sticker.commands.ParentCommand#preChildCommandExecute(CommandRunner)
	 * @return Enumerated type who shows whether to execute loop processing.When
	 *         the cursor doesn't open it, the loop processing is not executed.
	 * 
	 * @see net.murakamin.sticker.commands.enums.PreChildCommandExecutionType
	 * @see net.murakamin.sticker.StickerContext
	 */
	public PreChildCommandExecutionType preChildCommandExecute(
	        final CommandRunner runner) throws Exception
	{
		boolean execute = false;
		if (this.cursorRecordSet != null)
		{
			execute = this.cursorRecordSet.isBeforeFirst();
		}

		if (execute)
		{
			StickerContext context = runner.getStickerContext();
			ResultSetMetaData metaData = cursorRecordSet.getMetaData();
			cursorRecordSet.next();
			Map<String, Object> fieldMap = new HashMap<String, Object>();
			for (int c = 1; c <= metaData.getColumnCount(); c++)
			{
				fieldMap.put(metaData.getColumnName(c).toUpperCase(),
				        cursorRecordSet.getObject(c));
			}

			context.pushFieldStack(fieldMap);
		}

		return execute ? PreChildCommandExecutionType.CHILD_EXECUTE
		        : PreChildCommandExecutionType.CHILD_NOT_EXECUTE;
	}

	/**
	 * Set the cursor name used by the loop.
	 * 
	 * @return cursor name
	 */
	public void setName(final String name)
	{
		this.name = name;
	}

	/**
	 * The kind of the data base is acquired the data acquisition of the
	 * {@link net.murakamin.sticker.commands.CursorCommand &lt;cursor&gt;}
	 * command ahead.
	 * 
	 * @param target
	 *            the source of data acquisition
	 */
	public void setTarget(final String target)
	{
		this.target = TargetType.valueOf(target);
	}

	@Override
	public String toString()
	{
		StringBuffer buffer = new StringBuffer();

		buffer.append("<For name=\""); //$NON-NLS-1$
		buffer.append(this.getName());
		buffer.append("\" "); //$NON-NLS-1$
		buffer.append("target=\""); //$NON-NLS-1$
		buffer.append(this.getTarget());
		buffer.append("\">"); //$NON-NLS-1$

		return buffer.toString();
	}

}
