//  Copyright 2004 The Apache Software Foundation
//
// 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 org.apache.tapestry.contrib.jdbc;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

/**
 *  Class for creating and executing JDBC statements.  Allows statements to be assembled
 *  incrementally (like a {@link StringBuffer}), but also tracks parameters, shielding
 *  the developer from the differences between constructing and 
 *  using a JDBC 
 *  {@link java.sql.Statement} and 
 *  a JDBC {@link java.sql.PreparedStatement}.  This class is somewhat skewed towards
 *  Oracle, which works more efficiently with prepared staments than
 *  simple SQL.
 * 
 *  <p>In addition, implements {@link #toString()} in a useful way (you can see the
 *  SQL and parameters), which is invaluable when debugging.
 * 
 *  <p>{@link #addParameter(int)} (and all overloaded versions of it for scalar types)
 *  adds a "?" to the statement and records the parameter value.
 * 
 *  <p>{@link #addParameter(Integer)} (and all overloaded version of it for wrapper
 *  types) does the same ... unless the value is null, in which case "NULL" is
 *  inserted into the statement.
 * 
 *  <p>{@link #addParameterList(int[], String)} (and all overloaded versions of it)
 *  simply invokes the appropriate {@link #addParameter(int)}, adding the
 *  separator in between parameters.
 *
 *  @version $Id: StatementAssembly.java 243791 2004-02-19 17:38:13Z hlship $
 *  @author Howard Lewis Ship
 *
 **/

public class StatementAssembly
{
    private StringBuffer _buffer = new StringBuffer();

    private static final String NULL = "NULL";

    public static final String SEP = ", ";

    /**
     *  List of {@link IParameter}
     * 
     **/

    private List _parameters;

    private int _lineLength;
    private int _maxLineLength = 80;
    private int _indent = 5;

    /**
     *  Default constructor; uses a maximum line length of 80 and an indent of 5.
     *
     **/

    public StatementAssembly()
    {
    }

    public StatementAssembly(int maxLineLength, int indent)
    {
        _maxLineLength = maxLineLength;
        _indent = indent;
    }

    /**
     *  Clears the assembly, preparing it for re-use.
     * 
     *  @since 1.0.7
     * 
     **/

    public void clear()
    {
        _buffer.setLength(0);
        _lineLength = 0;

        if (_parameters != null)
            _parameters.clear();
    }

    /**
     *  Maximum length of a line.
     *
     **/

    public int getMaxLineLength()
    {
        return _maxLineLength;
    }

    /**
     *  Number of spaces to indent continuation lines by.
     *
     **/

    public int getIndent()
    {
        return _indent;
    }

    /**
     *  Adds text to the current line, unless that would make the line too long, in
     *  which case a new line is started (and indented) before adding the text.
     *
     *  <p>Text is added as-is, with no concept of quoting.  To add arbitrary strings
     *  (such as in a where clause), use {@link #addParameter(String)}.
     *
     *
     **/

    public void add(String text)
    {
        int textLength;

        textLength = text.length();

        if (_lineLength + textLength > _maxLineLength)
        {
            _buffer.append('\n');

            for (int i = 0; i < _indent; i++)
                _buffer.append(' ');

            _lineLength = _indent;
        }

        _buffer.append(text);
        _lineLength += textLength;
    }
    
    public void add(short value)
    {
        add(Short.toString(value));
    }
    
    public void add(int value)
    {
        add(Integer.toString(value));
    }
    
    public void add(long value)
    {
        add(Long.toString(value));
    }
    
    public void add(float value)
    {
        add(Float.toString(value));
    }
    
    public void add(double value)
    {
        add(Double.toString(value));
    }

    /**
     *  Adds a date value to a {@link StatementAssembly} converting
     *  it to a {@link java.sql.Timestamp} first.
     *
     **/

    public void addParameter(Date date)
    {
        if (date == null)
        {
            add("NULL");
            return;
        }

        Calendar calendar = GregorianCalendar.getInstance();

        calendar.setTime(date);
        calendar.set(Calendar.MILLISECOND, 0);

        Date adjusted = calendar.getTime();

        Timestamp timestamp = new Timestamp(adjusted.getTime());

        addParameter(timestamp);
    }

    /** 
     *  Adds a separator (usually a comma and a space) to the current line, regardless
     *  of line length.  This is purely aesthetic ... it just looks odd if a separator
     *  gets wrapped to a new line by itself.
     *
     **/

    public void addSep(String text)
    {
        _buffer.append(text);
        _lineLength += text.length();
    }

    /**
     *  Starts a new line, without indenting.
     *
     **/

    public void newLine()
    {
        if (_buffer.length() != 0)
            _buffer.append('\n');

        _lineLength = 0;
    }

    /**
     * Starts a new line, then adds the given text.
     *
     **/

    public void newLine(String text)
    {
        if (_buffer.length() != 0)
            _buffer.append('\n');

        _buffer.append(text);

        _lineLength = text.length();
    }

    public void addList(String[] items, String separator)
    {
        for (int i = 0; i < items.length; i++)
        {
            if (i > 0)
                addSep(separator);

            add(items[i]);
        }
    }

    public void addParameterList(int[] items, String separator)
    {
        for (int i = 0; i < items.length; i++)
        {
            if (i > 0)
                addSep(separator);

            addParameter(items[i]);
        }
    }

    public void addParameterList(Integer[] items, String separator)
    {
        for (int i = 0; i < items.length; i++)
        {
            if (i > 0)
                addSep(separator);

            addParameter(items[i]);
        }
    }

    public void addParameterList(long[] items, String separator)
    {
        for (int i = 0; i < items.length; i++)
        {
            if (i > 0)
                addSep(separator);

            addParameter(items[i]);
        }
    }

    public void addParameterList(Long[] items, String separator)
    {
        for (int i = 0; i < items.length; i++)
        {
            if (i > 0)
                addSep(separator);

            addParameter(items[i]);
        }
    }

    public void addParameterList(String[] items, String separator)
    {
        for (int i = 0; i < items.length; i++)
        {
            if (i > 0)
                addSep(separator);

            addParameter(items[i]);
        }
    }

    public void addParameterList(double[] items, String separator)
    {
        for (int i = 0; i < items.length; i++)
        {
            if (i > 0)
                addSep(separator);

            addParameter(items[i]);
        }
    }

    public void addParameter(Object value)
    {
        if (value == null)
            add(NULL);
        else
            addParameter(new ObjectParameter(value));
    }

    public void addParameter(Timestamp timestamp)
    {
        if (timestamp == null)
            add(NULL);
        else
            addParameter(new TimestampParameter(timestamp));
    }

    public void addParameter(String value)
    {
        if (value == null)
            add(NULL);
        else
            addParameter(new StringParameter(value));
    }

    public void addParameter(int value)
    {
        addParameter(new IntegerParameter(value));
    }

    public void addParameter(Integer value)
    {
        if (value == null)
            add(NULL);
        else
            addParameter(value.intValue());
    }

    public void addParameter(long value)
    {
        addParameter(new LongParameter(value));
    }

    public void addParameter(Long value)
    {
        if (value == null)
            add(NULL);
        else
            addParameter(value.longValue());
    }

    public void addParameter(float value)
    {
        addParameter(new FloatParameter(value));
    }

    public void addParameter(Float value)
    {
        if (value == null)
            add(NULL);
        else
            addParameter(value.floatValue());
    }

    public void addParameter(double value)
    {
        addParameter(new DoubleParameter(value));
    }

    public void addParameter(Double value)
    {
        if (value == null)
            add(NULL);
        else
            addParameter(value.doubleValue());
    }

    public void addParameter(short value)
    {
        addParameter(new ShortParameter(value));
    }

    public void addParameter(Short value)
    {
        if (value == null)
            add(NULL);
        else
            addParameter(value.shortValue());
    }

    public void addParameter(boolean value)
    {
        addParameter(value ? BooleanParameter.TRUE : BooleanParameter.FALSE);
    }

    public void addParameter(Boolean value)
    {
        if (value == null)
            add(NULL);
        else
            addParameter(value.booleanValue());
    }

    private void addParameter(IParameter parameter)
    {
        if (_parameters == null)
            _parameters = new ArrayList();

        _parameters.add(parameter);

        add("?");
    }

    /**
     *  Creates and returns an {@link IStatement} based on the SQL and parameters
     *  acquired.
     *
     **/

    public IStatement createStatement(Connection connection) throws SQLException
    {
        String sql = _buffer.toString();

        if (_parameters == null || _parameters.isEmpty())
            return new SimpleStatement(sql, connection);

        return new ParameterizedStatement(sql, connection, _parameters);
    }

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

        buffer.append(Integer.toHexString(hashCode()));
        buffer.append("[SQL=\n<");
        buffer.append(_buffer);
        buffer.append("\n>");

        if (_parameters != null)
        {
            int count = _parameters.size();
            for (int i = 0; i < count; i++)
            {
                Object parameter = _parameters.get(i);

                buffer.append(" ?");
                buffer.append(i + 1);
                buffer.append('=');

                buffer.append(parameter);
            }
        }

        buffer.append(']');

        return buffer.toString();
    }
}