/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * 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 .
 */

#include <comphelper/string.hxx>
#include <sal/log.hxx>
#include <o3tl/string_view.hxx>
#include "createparser.hxx"
#include "utils.hxx"
#include <com/sun/star/sdbc/DataType.hpp>

using namespace ::comphelper;
using namespace css::sdbc;

namespace
{
/// Returns substring of sSql from the first occurrence of '(' until the
/// last occurrence of ')' (excluding the parenthesis)
std::u16string_view lcl_getColumnPart(std::u16string_view sSql)
{
    size_t nBeginIndex = sSql.find('(');
    if (nBeginIndex == std::u16string_view::npos)
    {
        SAL_WARN("dbaccess", "No column definitions found");
        return std::u16string_view();
    }
    sal_Int32 nCount = sSql.rfind(')') - nBeginIndex - 1;
    auto sPart = sSql.substr(nBeginIndex + 1, nCount);
    return sPart;
}

/// Constructs a vector of strings that represents the definitions of each
/// column or constraint.
///
/// @param sColumnPart part of the create statement inside the parenthesis
/// containing the column definitions
std::vector<OUString> lcl_splitColumnPart(std::u16string_view sColumnPart)
{
    std::vector<OUString> sParts = string::split(sColumnPart, sal_Unicode(u','));
    std::vector<OUString> sReturn;

    OUStringBuffer current(128);
    for (auto const& part : sParts)
    {
        current.append(part);
        if (current.lastIndexOf("(") > current.lastIndexOf(")"))
            current.append(","); // it was false split
        else
        {
            sReturn.push_back(current.toString());
            current.setLength(0);
        }
    }
    return sReturn;
}

sal_Int32 lcl_getAutoIncrementDefault(std::u16string_view sColumnDef)
{
    // TODO what if there are more spaces?
    size_t nPos = sColumnDef.find(u"GENERATED BY DEFAULT AS IDENTITY");
    if (nPos != std::u16string_view::npos && nPos > 0)
    {
        // TODO parse starting sequence stated by "START WITH"
        return 0;
    }
    return -1;
}

std::u16string_view lcl_getDefaultValue(std::u16string_view sColumnDef)
{
    static constexpr std::u16string_view DEFAULT_KW = u"DEFAULT";
    size_t nDefPos = sColumnDef.find(DEFAULT_KW);
    if (nDefPos > 0 && nDefPos != std::u16string_view::npos
        && lcl_getAutoIncrementDefault(sColumnDef) < 0)
    {
        std::u16string_view fromDefault
            = o3tl::trim(sColumnDef.substr(nDefPos + DEFAULT_KW.size()));

        // next word is the value
        size_t nNextSpace = fromDefault.find(' ');
        return (nNextSpace > 0 && nNextSpace != std::u16string_view::npos)
                   ? fromDefault.substr(0, nNextSpace)
                   : fromDefault;
    }
    return std::u16string_view();
}

bool lcl_isNullable(std::u16string_view sColumnDef)
{
    return sColumnDef.find(u"NOT NULL") == std::u16string_view::npos;
}

bool lcl_isPrimaryKey(std::u16string_view sColumnDef)
{
    return sColumnDef.find(u"PRIMARY KEY") != std::u16string_view::npos;
}

sal_Int32 lcl_getDataTypeFromHsql(std::u16string_view sTypeName)
{
    if (sTypeName == u"CHAR")
        return DataType::CHAR;
    else if (sTypeName == u"VARCHAR" || sTypeName == u"VARCHAR_IGNORECASE")
        return DataType::VARCHAR;
    else if (sTypeName == u"TINYINT")
        return DataType::TINYINT;
    else if (sTypeName == u"SMALLINT")
        return DataType::SMALLINT;
    else if (sTypeName == u"INTEGER")
        return DataType::INTEGER;
    else if (sTypeName == u"BIGINT")
        return DataType::BIGINT;
    else if (sTypeName == u"NUMERIC")
        return DataType::NUMERIC;
    else if (sTypeName == u"DECIMAL")
        return DataType::DECIMAL;
    else if (sTypeName == u"BOOLEAN")
        return DataType::BOOLEAN;
    else if (sTypeName == u"LONGVARCHAR")
        return DataType::LONGVARCHAR;
    else if (sTypeName == u"LONGVARBINARY")
        return DataType::LONGVARBINARY;
    else if (sTypeName == u"CLOB")
        return DataType::CLOB;
    else if (sTypeName == u"BLOB")
        return DataType::BLOB;
    else if (sTypeName == u"BINARY")
        return DataType::BINARY;
    else if (sTypeName == u"VARBINARY")
        return DataType::VARBINARY;
    else if (sTypeName == u"DATE")
        return DataType::DATE;
    else if (sTypeName == u"TIME")
        return DataType::TIME;
    else if (sTypeName == u"TIMESTAMP")
        return DataType::TIMESTAMP;
    else if (sTypeName == u"DOUBLE")
        return DataType::DOUBLE;
    else if (sTypeName == u"REAL")
        return DataType::REAL;
    else if (sTypeName == u"FLOAT")
        return DataType::FLOAT;

    assert(false);
    return -1;
}

void lcl_addDefaultParameters(std::vector<sal_Int32>& aParams, sal_Int32 eType)
{
    if (eType == DataType::CHAR || eType == DataType::BINARY || eType == DataType::VARBINARY
        || eType == DataType::VARCHAR)
        aParams.push_back(8000); // from SQL standard
}

struct ColumnTypeParts
{
    OUString typeName;
    std::vector<sal_Int32> params;
};

/**
 * Separates full type descriptions (e.g. NUMERIC(5,4)) to type name (NUMERIC) and
 * parameters (5,4)
 */
ColumnTypeParts lcl_getColumnTypeParts(std::u16string_view sFullTypeName)
{
    ColumnTypeParts parts;
    auto nParenPos = sFullTypeName.find('(');
    if (nParenPos > 0 && nParenPos != std::u16string_view::npos)
    {
        parts.typeName = o3tl::trim(sFullTypeName.substr(0, nParenPos));
        std::u16string_view sParamStr
            = sFullTypeName.substr(nParenPos + 1, sFullTypeName.find(')') - nParenPos - 1);
        auto sParams = string::split(sParamStr, sal_Unicode(u','));
        for (const auto& sParam : sParams)
        {
            parts.params.push_back(sParam.toInt32());
        }
    }
    else
    {
        parts.typeName = o3tl::trim(sFullTypeName);
        lcl_addDefaultParameters(parts.params, lcl_getDataTypeFromHsql(parts.typeName));
    }
    return parts;
}

} // unnamed namespace

namespace dbahsql
{
CreateStmtParser::CreateStmtParser() {}

void CreateStmtParser::parsePrimaryKeys(std::u16string_view sPrimaryPart)
{
    size_t nParenPos = sPrimaryPart.find('(');
    if (nParenPos > 0 && nParenPos != std::u16string_view::npos)
    {
        std::u16string_view sParamStr
            = sPrimaryPart.substr(nParenPos + 1, sPrimaryPart.rfind(')') - nParenPos - 1);
        auto sParams = string::split(sParamStr, sal_Unicode(u','));
        for (const auto& sParam : sParams)
        {
            m_PrimaryKeys.push_back(sParam);
        }
    }
}

void CreateStmtParser::parseColumnPart(std::u16string_view sColumnPart)
{
    auto sColumns = lcl_splitColumnPart(sColumnPart);
    for (const OUString& sColumn : sColumns)
    {
        if (sColumn.startsWithIgnoreAsciiCase("PRIMARY KEY"))
        {
            parsePrimaryKeys(sColumn);
            continue;
        }

        if (sColumn.startsWithIgnoreAsciiCase("CONSTRAINT"))
        {
            m_aForeignParts.push_back(sColumn);
            continue;
        }

        bool bIsQuoteUsedForColumnName(sColumn[0] == '\"');

        // find next quote after the initial quote
        // or next space if quote isn't used as delimiter
        auto nEndColumnName
            = bIsQuoteUsedForColumnName ? sColumn.indexOf("\"", 1) + 1 : sColumn.indexOf(" ");
        OUString rColumnName = sColumn.copy(0, nEndColumnName);

        const OUString sFromTypeName(o3tl::trim(sColumn.subView(nEndColumnName)));

        // Now let's manage the column type
        // search next space to get the whole type name
        // eg: INTEGER, VARCHAR(10), DECIMAL(6,3)
        auto nNextSpace = sFromTypeName.indexOf(" ");
        std::u16string_view sFullTypeName;
        if (nNextSpace > 0)
            sFullTypeName = sFromTypeName.subView(0, nNextSpace);
        // perhaps column type corresponds to the last info here
        else
            sFullTypeName = sFromTypeName;

        ColumnTypeParts typeParts = lcl_getColumnTypeParts(sFullTypeName);

        bool bCaseInsensitive = typeParts.typeName.indexOf("IGNORECASE") >= 0;
        bool isPrimaryKey = lcl_isPrimaryKey(sColumn);

        if (isPrimaryKey)
            m_PrimaryKeys.push_back(rColumnName);

        const std::u16string_view sColumnWithoutName
            = sColumn.subView(sColumn.indexOf(typeParts.typeName));

        ColumnDefinition aColDef(rColumnName, lcl_getDataTypeFromHsql(typeParts.typeName),
                                 std::move(typeParts.params), isPrimaryKey,
                                 lcl_getAutoIncrementDefault(sColumnWithoutName),
                                 lcl_isNullable(sColumnWithoutName), bCaseInsensitive,
                                 OUString(lcl_getDefaultValue(sColumnWithoutName)));

        m_aColumns.push_back(aColDef);
    }
}

void CreateStmtParser::parse(std::u16string_view sSql)
{
    // TODO Foreign keys
    if (!o3tl::starts_with(sSql, u"CREATE"))
    {
        SAL_WARN("dbaccess", "Not a create statement");
        return;
    }

    m_sTableName = utils::getTableNameFromStmt(sSql);
    std::u16string_view sColumnPart = lcl_getColumnPart(sSql);
    parseColumnPart(sColumnPart);
}

} // namespace dbahsql

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
