/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

#include <aws/core/utils/stream/ResponseStream.h>
#include <aws/core/utils/memory/stl/AWSStringStream.h>
#include <aws/core/utils/logging/LogMacros.h>

#if defined(_GLIBCXX_FULLY_DYNAMIC_STRING) && _GLIBCXX_FULLY_DYNAMIC_STRING == 0 && defined(__ANDROID__)
#include <aws/core/utils/stream/SimpleStreamBuf.h>
using DefaultStreamBufType = Aws::Utils::Stream::SimpleStreamBuf;
#else
using DefaultStreamBufType = Aws::StringBuf;
#endif

using namespace Aws::Utils::Stream;

const int ResponseStream::ResponseStream::xindex = std::ios_base::xalloc();

ResponseStream::ResponseStream(void) :
    m_underlyingStream(nullptr)
{
}

ResponseStream::ResponseStream(Aws::IOStream* underlyingStreamToManage) :
    m_underlyingStream(underlyingStreamToManage)
{
    RegisterStream();
}

ResponseStream::ResponseStream(const Aws::IOStreamFactory& factory) :
    m_underlyingStream(factory())
{
    RegisterStream();
}

ResponseStream::ResponseStream(ResponseStream&& toMove) : m_underlyingStream(toMove.m_underlyingStream)
{
    toMove.DeregisterStream();
    toMove.m_underlyingStream = nullptr;
    RegisterStream();
}

ResponseStream& ResponseStream::operator=(ResponseStream&& toMove)
{
    if(m_underlyingStream == toMove.m_underlyingStream)
    {
        return *this;
    }

    ReleaseStream();
    toMove.DeregisterStream();
    m_underlyingStream = toMove.m_underlyingStream;
    toMove.m_underlyingStream = nullptr;
    RegisterStream();

    return *this;
}

Aws::IOStream& ResponseStream::GetUnderlyingStream() const
{
    if (!m_underlyingStream)
    {
        assert(m_underlyingStream);
        AWS_LOGSTREAM_FATAL("ResponseStream", "Unexpected nullptr m_underlyingStream");
        static DefaultUnderlyingStream fallbackStream; // we are already in UB, let's just not crash existing apps
        return fallbackStream;
    }
    return *m_underlyingStream;
}

ResponseStream::~ResponseStream()
{
    ReleaseStream();
}

void ResponseStream::ReleaseStream()
{
    if (m_underlyingStream)
    {
        DeregisterStream();
        Aws::Delete(m_underlyingStream);
    }

    m_underlyingStream = nullptr;
}

void ResponseStream::RegisterStream()
{
    if (m_underlyingStream)
    {
        ResponseStream* pThat = static_cast<ResponseStream*>(m_underlyingStream->pword(ResponseStream::xindex));
        if (pThat != nullptr)
        {
            // callback is already registered
            assert(pThat != this); // Underlying stream must not be owned by more than one ResponseStream
        }
        else
        {
            m_underlyingStream->register_callback(ResponseStream::StreamCallback, ResponseStream::xindex);
        }
        m_underlyingStream->pword(ResponseStream::xindex) = this;
    }
}

void ResponseStream::DeregisterStream()
{
    if (m_underlyingStream)
    {
        assert(static_cast<ResponseStream*>(m_underlyingStream->pword(ResponseStream::xindex)) == this); // Attempt to deregister another ResponseStream's stream
        m_underlyingStream->pword(ResponseStream::xindex) = nullptr; // ios does not support deregister, so just erasing the context
    }
}

void ResponseStream::StreamCallback(Aws::IOStream::event evt, std::ios_base& stream, int idx)
{
    if (evt == std::ios_base::erase_event)
    {
        ResponseStream* pThis = static_cast<ResponseStream*>(stream.pword(idx));
        if (pThis)
        {
            // m_underlyingStream is being destructed, let's avoid double destruction or having a dangling pointer
            pThis->m_underlyingStream = nullptr;
        }
    }
}

static const char *DEFAULT_STREAM_TAG = "DefaultUnderlyingStream";

DefaultUnderlyingStream::DefaultUnderlyingStream() :
    Base( Aws::New< DefaultStreamBufType >( DEFAULT_STREAM_TAG ) )
{}

DefaultUnderlyingStream::DefaultUnderlyingStream(Aws::UniquePtr<std::streambuf> buf) :
    Base(buf.release())
{}

DefaultUnderlyingStream::~DefaultUnderlyingStream()
{
    if( rdbuf() )
    {
        Aws::Delete( rdbuf() );
    }
}

static const char* RESPONSE_STREAM_FACTORY_TAG = "ResponseStreamFactory";

Aws::IOStream* Aws::Utils::Stream::DefaultResponseStreamFactoryMethod() 
{
    return Aws::New<Aws::Utils::Stream::DefaultUnderlyingStream>(RESPONSE_STREAM_FACTORY_TAG);
}