#include "lycon/io/bitstream.h"

#include <cassert>

namespace lycon
{
const int BS_DEF_BLOCK_SIZE = 1 << 15;

bool bsIsBigEndian(void)
{
    return (((const int*)"\0\x1\x2\x3\x4\x5\x6\x7")[0] & 255) != 0;
}

/////////////////////////  RBaseStream ////////////////////////////

bool RBaseStream::isOpened()
{
    return m_is_opened;
}

void RBaseStream::allocate()
{
    if (!m_allocated)
    {
        m_start = new uchar[m_block_size];
        m_end = m_start + m_block_size;
        m_current = m_end;
        m_allocated = true;
    }
}

RBaseStream::RBaseStream()
{
    m_start = m_end = m_current = 0;
    m_file = 0;
    m_block_size = BS_DEF_BLOCK_SIZE;
    m_is_opened = false;
    m_allocated = false;
}

RBaseStream::~RBaseStream()
{
    close();   // Close files
    release(); // free  buffers
}

void RBaseStream::readBlock()
{
    setPos(getPos()); // normalize position

    if (m_file == 0)
    {
        if (m_block_pos == 0 && m_current < m_end)
            return;
        throw RBS_THROW_EOS;
    }

    fseek(m_file, m_block_pos, SEEK_SET);
    size_t readed = fread(m_start, 1, m_block_size, m_file);
    m_end = m_start + readed;
    m_current = m_start;

    if (readed == 0 || m_current >= m_end)
        throw RBS_THROW_EOS;
}

bool RBaseStream::open(const String& filename)
{
    close();
    allocate();

    m_file = fopen(filename.c_str(), "rb");
    if (m_file)
    {
        m_is_opened = true;
        setPos(0);
        readBlock();
    }
    return m_file != 0;
}

void RBaseStream::close()
{
    if (m_file)
    {
        fclose(m_file);
        m_file = 0;
    }
    m_is_opened = false;
    if (!m_allocated)
        m_start = m_end = m_current = 0;
}

void RBaseStream::release()
{
    if (m_allocated)
        delete[] m_start;
    m_start = m_end = m_current = 0;
    m_allocated = false;
}

void RBaseStream::setPos(int pos)
{
    assert(isOpened() && pos >= 0);

    if (!m_file)
    {
        m_current = m_start + pos;
        m_block_pos = 0;
        return;
    }

    int offset = pos % m_block_size;
    m_block_pos = pos - offset;
    m_current = m_start + offset;
}

int RBaseStream::getPos()
{
    assert(isOpened());
    return m_block_pos + (int)(m_current - m_start);
}

void RBaseStream::skip(int bytes)
{
    assert(bytes >= 0);
    m_current += bytes;
}

/////////////////////////  RLByteStream ////////////////////////////

RLByteStream::~RLByteStream()
{
}

int RLByteStream::getByte()
{
    uchar* current = m_current;
    int val;

    if (current >= m_end)
    {
        readBlock();
        current = m_current;
    }

    val = *((uchar*)current);
    m_current = current + 1;
    return val;
}

int RLByteStream::getBytes(void* buffer, int count)
{
    uchar* data = (uchar*)buffer;
    int readed = 0;
    assert(count >= 0);

    while (count > 0)
    {
        int l;

        for (;;)
        {
            l = (int)(m_end - m_current);
            if (l > count)
                l = count;
            if (l > 0)
                break;
            readBlock();
        }
        memcpy(data, m_current, l);
        m_current += l;
        data += l;
        count -= l;
        readed += l;
    }
    return readed;
}

////////////  RLByteStream & RMByteStream <Get[d]word>s ////////////////

RMByteStream::~RMByteStream()
{
}

int RLByteStream::getWord()
{
    uchar* current = m_current;
    int val;

    if (current + 1 < m_end)
    {
        val = current[0] + (current[1] << 8);
        m_current = current + 2;
    }
    else
    {
        val = getByte();
        val |= getByte() << 8;
    }
    return val;
}

int RLByteStream::getDWord()
{
    uchar* current = m_current;
    int val;

    if (current + 3 < m_end)
    {
        val = current[0] + (current[1] << 8) + (current[2] << 16) + (current[3] << 24);
        m_current = current + 4;
    }
    else
    {
        val = getByte();
        val |= getByte() << 8;
        val |= getByte() << 16;
        val |= getByte() << 24;
    }
    return val;
}

int RMByteStream::getWord()
{
    uchar* current = m_current;
    int val;

    if (current + 1 < m_end)
    {
        val = (current[0] << 8) + current[1];
        m_current = current + 2;
    }
    else
    {
        val = getByte() << 8;
        val |= getByte();
    }
    return val;
}

int RMByteStream::getDWord()
{
    uchar* current = m_current;
    int val;

    if (current + 3 < m_end)
    {
        val = (current[0] << 24) + (current[1] << 16) + (current[2] << 8) + current[3];
        m_current = current + 4;
    }
    else
    {
        val = getByte() << 24;
        val |= getByte() << 16;
        val |= getByte() << 8;
        val |= getByte();
    }
    return val;
}

/////////////////////////// WBaseStream /////////////////////////////////

// WBaseStream - base class for output streams
WBaseStream::WBaseStream()
{
    m_start = m_end = m_current = 0;
    m_file = 0;
    m_block_size = BS_DEF_BLOCK_SIZE;
    m_is_opened = false;
    m_buf = 0;
}

WBaseStream::~WBaseStream()
{
    close();
    release();
}

bool WBaseStream::isOpened()
{
    return m_is_opened;
}

void WBaseStream::allocate()
{
    if (!m_start)
        m_start = new uchar[m_block_size];

    m_end = m_start + m_block_size;
    m_current = m_start;
}

void WBaseStream::writeBlock()
{
    int size = (int)(m_current - m_start);

    assert(isOpened());
    if (size == 0)
        return;

    if (m_buf)
    {
        size_t sz = m_buf->size();
        m_buf->resize(sz + size);
        memcpy(&(*m_buf)[sz], m_start, size);
    }
    else
    {
        fwrite(m_start, 1, size, m_file);
    }
    m_current = m_start;
    m_block_pos += size;
}

bool WBaseStream::open(const String& filename)
{
    close();
    allocate();

    m_file = fopen(filename.c_str(), "wb");
    if (m_file)
    {
        m_is_opened = true;
        m_block_pos = 0;
        m_current = m_start;
    }
    return m_file != 0;
}

bool WBaseStream::open(std::vector<uchar>& buf)
{
    close();
    allocate();

    m_buf = &buf;
    m_is_opened = true;
    m_block_pos = 0;
    m_current = m_start;

    return true;
}

void WBaseStream::close()
{
    if (m_is_opened)
        writeBlock();
    if (m_file)
    {
        fclose(m_file);
        m_file = 0;
    }
    m_buf = 0;
    m_is_opened = false;
}

void WBaseStream::release()
{
    if (m_start)
        delete[] m_start;
    m_start = m_end = m_current = 0;
}

int WBaseStream::getPos()
{
    assert(isOpened());
    return m_block_pos + (int)(m_current - m_start);
}

///////////////////////////// WLByteStream ///////////////////////////////////

WLByteStream::~WLByteStream()
{
}

void WLByteStream::putByte(int val)
{
    *m_current++ = (uchar)val;
    if (m_current >= m_end)
        writeBlock();
}

void WLByteStream::putBytes(const void* buffer, int count)
{
    uchar* data = (uchar*)buffer;

    assert(data && m_current && count >= 0);

    while (count)
    {
        int l = (int)(m_end - m_current);

        if (l > count)
            l = count;

        if (l > 0)
        {
            memcpy(m_current, data, l);
            m_current += l;
            data += l;
            count -= l;
        }
        if (m_current == m_end)
            writeBlock();
    }
}

void WLByteStream::putWord(int val)
{
    uchar* current = m_current;

    if (current + 1 < m_end)
    {
        current[0] = (uchar)val;
        current[1] = (uchar)(val >> 8);
        m_current = current + 2;
        if (m_current == m_end)
            writeBlock();
    }
    else
    {
        putByte(val);
        putByte(val >> 8);
    }
}

void WLByteStream::putDWord(int val)
{
    uchar* current = m_current;

    if (current + 3 < m_end)
    {
        current[0] = (uchar)val;
        current[1] = (uchar)(val >> 8);
        current[2] = (uchar)(val >> 16);
        current[3] = (uchar)(val >> 24);
        m_current = current + 4;
        if (m_current == m_end)
            writeBlock();
    }
    else
    {
        putByte(val);
        putByte(val >> 8);
        putByte(val >> 16);
        putByte(val >> 24);
    }
}

///////////////////////////// WMByteStream ///////////////////////////////////

WMByteStream::~WMByteStream()
{
}

void WMByteStream::putWord(int val)
{
    uchar* current = m_current;

    if (current + 1 < m_end)
    {
        current[0] = (uchar)(val >> 8);
        current[1] = (uchar)val;
        m_current = current + 2;
        if (m_current == m_end)
            writeBlock();
    }
    else
    {
        putByte(val >> 8);
        putByte(val);
    }
}

void WMByteStream::putDWord(int val)
{
    uchar* current = m_current;

    if (current + 3 < m_end)
    {
        current[0] = (uchar)(val >> 24);
        current[1] = (uchar)(val >> 16);
        current[2] = (uchar)(val >> 8);
        current[3] = (uchar)val;
        m_current = current + 4;
        if (m_current == m_end)
            writeBlock();
    }
    else
    {
        putByte(val >> 24);
        putByte(val >> 16);
        putByte(val >> 8);
        putByte(val);
    }
}
}
