/*
 * 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
 *
 * 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.
 */

#include <algorithm>

#include "ignite/odbc/system/odbc_constants.h"
#include "ignite/odbc/app/parameter.h"
#include "ignite/odbc/utility.h"

namespace ignite
{
    namespace odbc
    {
        namespace app
        {
            Parameter::Parameter() :
                buffer(),
                sqlType(),
                columnSize(),
                decDigits(),
                nullData(false),
                storedData()
            {
                // No-op.
            }

            Parameter::Parameter(const ApplicationDataBuffer& buffer, int16_t sqlType,
                size_t columnSize, int16_t decDigits) :
                buffer(buffer),
                sqlType(sqlType),
                columnSize(columnSize),
                decDigits(decDigits),
                nullData(false),
                storedData()
            {
                // No-op.
            }

            Parameter::Parameter(const Parameter& other) :
                buffer(other.buffer),
                sqlType(other.sqlType),
                columnSize(other.columnSize),
                decDigits(other.decDigits),
                nullData(other.nullData),
                storedData(other.storedData)
            {
                // No-op.
            }

            Parameter::~Parameter()
            {
                // No-op.
            }

            Parameter& Parameter::operator=(const Parameter &other)
            {
                buffer = other.buffer;
                sqlType = other.sqlType;
                columnSize = other.columnSize;
                decDigits = other.decDigits;

                return *this;
            }

            void Parameter::Write(impl::binary::BinaryWriterImpl& writer, int offset, SqlUlen idx) const
            {
                if (buffer.GetInputSize() == SQL_NULL_DATA)
                {
                    writer.WriteNull();

                    return;
                }

                // Buffer to use to get data.
                ApplicationDataBuffer buf(buffer);
                buf.SetByteOffset(offset);
                buf.SetElementOffset(idx);

                SqlLen storedDataLen = static_cast<SqlLen>(storedData.size());

                if (buffer.IsDataAtExec())
                {
                    buf = ApplicationDataBuffer(buffer.GetType(),
                        const_cast<int8_t*>(&storedData[0]), storedDataLen, &storedDataLen);
                }

                switch (sqlType)
                {
                    case SQL_CHAR:
                    case SQL_VARCHAR:
                    case SQL_LONGVARCHAR:
                    {
                        utility::WriteString(writer, buf.GetString(columnSize));
                        break;
                    }

                    case SQL_SMALLINT:
                    {
                        writer.WriteObject<int16_t>(buf.GetInt16());
                        break;
                    }

                    case SQL_INTEGER:
                    {
                        writer.WriteObject<int32_t>(buf.GetInt32());
                        break;
                    }

                    case SQL_FLOAT:
                    {
                        writer.WriteObject<float>(buf.GetFloat());
                        break;
                    }

                    case SQL_DOUBLE:
                    {
                        writer.WriteObject<double>(buf.GetDouble());
                        break;
                    }

                    case SQL_TINYINT:
                    {
                        writer.WriteObject<int8_t>(buf.GetInt8());
                        break;
                    }

                    case SQL_BIT:
                    {
                        writer.WriteObject<bool>(buf.GetInt8() != 0);
                        break;
                    }

                    case SQL_BIGINT:
                    {
                        writer.WriteObject<int64_t>(buf.GetInt64());
                        break;
                    }

                    case SQL_TYPE_DATE:
                    case SQL_DATE:
                    {
                        writer.WriteDate(buf.GetDate());
                        break;
                    }

                    case SQL_TYPE_TIMESTAMP:
                    case SQL_TIMESTAMP:
                    {
                        writer.WriteTimestamp(buf.GetTimestamp());
                        break;
                    }

                    case SQL_TYPE_TIME:
                    case SQL_TIME:
                    {
                        writer.WriteTime(buf.GetTime());
                        break;
                    }

                    case SQL_BINARY:
                    case SQL_VARBINARY:
                    case SQL_LONGVARBINARY:
                    {
                        const ApplicationDataBuffer& constRef = buf;

                        const SqlLen* resLenPtr = constRef.GetResLen();

                        if (!resLenPtr)
                            break;

                        int32_t paramLen = static_cast<int32_t>(*resLenPtr);

                        writer.WriteInt8Array(reinterpret_cast<const int8_t*>(constRef.GetData()), paramLen);

                        break;
                    }

                    case SQL_GUID:
                    {
                        writer.WriteGuid(buf.GetGuid());

                        break;
                    }

                    case SQL_DECIMAL:
                    {
                        common::Decimal dec;
                        buf.GetDecimal(dec);

                        utility::WriteDecimal(writer, dec);

                        break;
                    }

                    default:
                        break;
                }
            }

            ApplicationDataBuffer& Parameter::GetBuffer()
            {
                return buffer;
            }

            const ApplicationDataBuffer& Parameter::GetBuffer() const
            {
                return buffer;
            }

            void Parameter::ResetStoredData()
            {
                storedData.clear();

                if (buffer.IsDataAtExec())
                    storedData.reserve(buffer.GetDataAtExecSize());
            }

            bool Parameter::IsDataReady() const
            {
                return !buffer.IsDataAtExec() ||
                    static_cast<SqlLen>(storedData.size()) == buffer.GetDataAtExecSize();
            }

            void Parameter::PutData(void* data, SqlLen len)
            {
                if (len == SQL_DEFAULT_PARAM)
                    return;

                if (len == SQL_NULL_DATA)
                {
                    nullData = true;

                    return;
                }

                if (buffer.GetType() == type_traits::OdbcNativeType::AI_CHAR ||
                    buffer.GetType() == type_traits::OdbcNativeType::AI_BINARY)
                {
                    SqlLen slen = len;

                    if (buffer.GetType() == type_traits::OdbcNativeType::AI_CHAR && slen == SQL_NTSL)
                    {
                        const char* str = reinterpret_cast<char*>(data);

                        slen = strlen(str);
                    }

                    if (slen <= 0)
                        return;

                    size_t beginPos = storedData.size();

                    storedData.resize(storedData.size() + static_cast<size_t>(slen));

                    memcpy(&storedData[beginPos], data, static_cast<size_t>(slen));

                    return;
                }

                size_t dataSize = buffer.GetDataAtExecSize();

                storedData.resize(dataSize);

                memcpy(&storedData[0], data, dataSize);
            }
        }
    }
}
