/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you 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 .
 */
package org.libreoffice.report;

import com.sun.star.beans.UnknownPropertyException;
import com.sun.star.beans.XPropertySet;
import com.sun.star.container.XIndexAccess;
import com.sun.star.container.XNameAccess;
import com.sun.star.lang.IndexOutOfBoundsException;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.sdb.XParametersSupplier;
import com.sun.star.sdbc.DataType;
import com.sun.star.sdbc.SQLException;
import com.sun.star.sdbc.XResultSetMetaData;
import com.sun.star.sdbc.XResultSetMetaDataSupplier;
import com.sun.star.sdbc.XRow;
import com.sun.star.sdbc.XRowSet;
import com.sun.star.sdbcx.XColumnsSupplier;
import com.sun.star.uno.Any;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.util.DateTime;
import com.sun.star.util.Time;

import java.sql.Timestamp;

public class SDBCReportData implements DataSource
{

    private final XRowSet rowSet;
    private final XRow row;
    private int rowCount;
    private XIndexAccess parameters;
    private int firstParameterIndex = -1;
    private int columnCount;
    private final String[] columnNames;
    private final int[] columnTypes;

    public SDBCReportData(final XRowSet rowSet) throws SQLException
    {
        row = UnoRuntime.queryInterface(XRow.class, rowSet);
        this.rowSet = rowSet;

        if (rowSet == null)
        {
            rowCount = 0;
            columnCount = 0;
            columnTypes = new int[1];
            columnNames = new String[1];
        }
        else
        {
            final XParametersSupplier xSuppParams = UnoRuntime.queryInterface(
                    XParametersSupplier.class, rowSet);
            if (xSuppParams != null)
            {
                parameters = xSuppParams.getParameters();
            }

            final XColumnsSupplier columnsSup = UnoRuntime.queryInterface(XColumnsSupplier.class, rowSet);
            final XNameAccess columns = columnsSup.getColumns();
            final String[] columnNamesList = columns.getElementNames();
            final XResultSetMetaDataSupplier sup = UnoRuntime.queryInterface(XResultSetMetaDataSupplier.class, rowSet);
            final XResultSetMetaData resultSetMetaData = sup.getMetaData();

            columnCount = resultSetMetaData.getColumnCount();
            firstParameterIndex = columnCount + 1;
            if (parameters != null)
            {
                columnCount += parameters.getCount();
            }

            columnTypes = new int[columnCount];
            columnNames = new String[columnCount];

            for (int i = 1; i <= columnCount; ++i)
            {
                if (i < firstParameterIndex)
                {
                    columnNames[i - 1] = columnNamesList[i - 1];// resultSetMetaData.getColumnName(i);
                    columnTypes[i - 1] = resultSetMetaData.getColumnType(i);
                }
                else
                {
                    try
                    {
                        final XPropertySet paramColumn = UnoRuntime.queryInterface(
                                XPropertySet.class, parameters.getByIndex(i - firstParameterIndex));
                        columnNames[i - 1] = (String) paramColumn.getPropertyValue("Name");
                        columnTypes[i - 1] = (Integer) paramColumn.getPropertyValue("Type");
                    }
                    catch (Exception e)
                    {
                        columnNames[i - 1] = "Error";
                        columnTypes[i - 1] = DataType.CHAR;
                    }
                }
            }

            if (rowSet.last())
            {
                rowCount = rowSet.getRow();
                rowSet.beforeFirst();
            }
            else
            {
                rowCount = 0;
            }
        }
    }

    public int getColumnCount() throws DataSourceException
    {
        return columnCount;
    }

    public int getRowCount()
    {
        return rowCount;
    }

    public String getColumnName(final int column) throws DataSourceException
    {
        return columnNames[column - 1];
    }

    public boolean absolute(final int row) throws DataSourceException
    {
        if (rowSet == null)
        {
            return false;
        }
        try
        {
            if (row == 0)
            {
                rowSet.beforeFirst();
                return true;
            }
            return rowSet.absolute(row);
        }
        catch (SQLException e)
        {
            throw new DataSourceException(e.getMessage(), e);
        }
    }

    public boolean next() throws DataSourceException
    {
        if (rowSet == null)
        {
            return false;
        }
        try
        {
            return rowSet.next();
        }
        catch (SQLException e)
        {
            throw new DataSourceException(e.getMessage(), e);
        }
    }

    public void close() throws DataSourceException
    {
    }

    private static java.sql.Date getDate(final Object obj)
    {
        final java.sql.Date date;
        if (obj instanceof com.sun.star.util.Date)
        {
            final com.sun.star.util.Date unodate = (com.sun.star.util.Date) obj;
            date = java.sql.Date.valueOf(getDateString(unodate.Year, unodate.Month, unodate.Day).toString());
        }
        else
        {
            date = null;
        }
        return date;
    }

    private static StringBuffer getTimeString(final int hours, final int minutes, final int seconds)
    {
        final StringBuffer timeString = new StringBuffer();
        if (hours < 10)
        {
            timeString.append('0');
        }
        timeString.append(hours);
        timeString.append(':');
        if (minutes < 10)
        {
            timeString.append('0');
        }
        timeString.append(minutes);
        timeString.append(':');
        if (seconds < 10)
        {
            timeString.append('0');
        }
        timeString.append(seconds);
        return timeString;
    }

    private static StringBuffer getDateString(final int years, final int months, final int days)
    {
        final StringBuffer str = new StringBuffer();
        str.append(years);
        final StringBuffer str2 = new StringBuffer("0000");
        str2.delete(0, str.length());
        str.insert(0, str2);
        str.append('-');
        if (months < 10)
        {
            str.append('0');
        }
        str.append(months);
        str.append('-');
        if (days < 10)
        {
            str.append('0');
        }
        str.append(days);
        return str;
    }

    private static java.sql.Time getTime(final Object obj)
    {
        final java.sql.Time time;
        if (obj instanceof Time)
        {
            final Time unoTime = (Time) obj;
            time = java.sql.Time.valueOf(getTimeString(unoTime.Hours, unoTime.Minutes, unoTime.Seconds).toString());
        }
        else
        {
            time = null;
        }
        return time;
    }

    private static Timestamp getTimestamp(final Object obj)
    {
        final Timestamp ts;
        if (obj instanceof DateTime)
        {
            final DateTime unoTs = (DateTime) obj;
            final StringBuffer str = getDateString(unoTs.Year, unoTs.Month, unoTs.Day);
            str.append(' ');
            str.append(getTimeString(unoTs.Hours, unoTs.Minutes, unoTs.Seconds));
            str.append('.');
            str.append(String.format("%09d", unoTs.NanoSeconds));
            ts = java.sql.Timestamp.valueOf(str.toString());
        }
        else
        {
            ts = null;
        }
        return ts;
    }

    public Object getObject(final int column) throws DataSourceException
    {
        if (rowSet == null)
        {
            return null;
        }
        try
        {
            final boolean isParameterValue = (parameters != null) && (column >= firstParameterIndex);
            Object obj;
            final boolean wasNull;
            if (isParameterValue)
            {
                final XPropertySet paramCol = UnoRuntime.queryInterface(
                        XPropertySet.class, parameters.getByIndex(column - firstParameterIndex));
                obj = paramCol.getPropertyValue("Value");
                wasNull = obj == null;
            }
            else
            {
                obj = row.getObject(column, null);
                wasNull = row.wasNull();
            }

            if (wasNull)
            {
                obj = null;
            }
            else
            {
                obj = convertObject(columnTypes[column - 1], obj);
            }
            return obj;
        }
        catch (SQLException ex)
        {
            throw new DataSourceException(ex.getMessage(), ex);
        }
        catch (UnknownPropertyException ex)
        {
            throw new DataSourceException(ex.getMessage(), ex);
        }
        catch (IndexOutOfBoundsException ex)
        {
            throw new DataSourceException(ex.getMessage(), ex);
        }
        catch (WrappedTargetException ex)
        {
            throw new DataSourceException(ex.getMessage(), ex);
        }
    }

    private Object convertObject(final int type, final Object obj)
    {
        Object ret;
        switch (type)
        {
            case DataType.DATE:
                ret = getDate(obj);
                break;
            case DataType.TIME:
                ret = getTime(obj);
                break;
            case DataType.TIMESTAMP:
                ret = getTimestamp(obj);
                break;
            case DataType.DECIMAL:
            case DataType.NUMERIC:
                if (!(obj instanceof Any))
                {
                    try
                    {
                        ret = new java.math.BigDecimal(String.valueOf(obj));
                    }
                    catch (NumberFormatException ex)
                    {
                        ret = obj;
                    }
                }
                else
                {
                    ret = obj;
                }
                break;
            default:
                ret = obj;
                break;
        }
        return ret;
    }
}
