/*
    SuperCollider real time audio synthesis system
    Copyright (c) 2002 James McCartney. All rights reserved.
    http://www.audiosynth.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/


#pragma once

#include "SC_Types.h"
#include "SC_Endian.h"
#include <stdio.h>
#include <string.h>
#include <stdexcept>

template <class T> class SC_IOStream {
protected:
    T s;

public:
    SC_IOStream(): s(0) {}
    SC_IOStream(T inStream): s(inStream) {}

    void SetStream(T inStream) { s = inStream; }
    T GetStream() { return s; }

    // core routines
    void readData(char* data, size_t size);
    uint8 readUInt8();

    void writeData(char* data, size_t size);
    void writeUInt8(uint8 inInt);

    // built using core routines
    void writeInt8(int8 inInt) { writeUInt8((uint8)inInt); }

    void writeInt16_be(int16 inInt) {
        writeUInt8((uint8)(inInt >> 8));
        writeUInt8(inInt);
    }

    void writeInt16_le(int16 inInt) {
        writeUInt8((uint8)inInt);
        writeUInt8((uint8)(inInt >> 8));
    }

    void writeInt32_be(int32 inInt) {
        writeUInt8((uint8)(inInt >> 24));
        writeUInt8((uint8)(inInt >> 16));
        writeUInt8((uint8)(inInt >> 8));
        writeUInt8((uint8)inInt);
    }

    void writeInt32_le(int32 inInt) {
        writeUInt8((uint8)inInt);
        writeUInt8((uint8)(inInt >> 8));
        writeUInt8((uint8)(inInt >> 16));
        writeUInt8((uint8)(inInt >> 24));
    }

#if BYTE_ORDER == BIG_ENDIAN
    void writeFloat_be(float inFloat)
#else
    void writeFloat_le(float inFloat)
#endif
    {
        union {
            float f;
            uint8 c[4];
        } u;
        u.f = inFloat;
        writeUInt8(u.c[0]);
        writeUInt8(u.c[1]);
        writeUInt8(u.c[2]);
        writeUInt8(u.c[3]);
    }

#if BYTE_ORDER == BIG_ENDIAN
    void writeFloat_le(float inFloat)
#else
    void writeFloat_be(float inFloat)
#endif
    {
        union {
            float f;
            uint8 c[4];
        } u;
        u.f = inFloat;
        writeUInt8(u.c[3]);
        writeUInt8(u.c[2]);
        writeUInt8(u.c[1]);
        writeUInt8(u.c[0]);
    }


#if BYTE_ORDER == BIG_ENDIAN
    void writeDouble_be(double inDouble)
#else
    void writeDouble_le(double inDouble)
#endif
    {
        union {
            double f;
            uint8 c[8];
        } u;
        u.f = inDouble;
        writeUInt8(u.c[0]);
        writeUInt8(u.c[1]);
        writeUInt8(u.c[2]);
        writeUInt8(u.c[3]);
        writeUInt8(u.c[4]);
        writeUInt8(u.c[5]);
        writeUInt8(u.c[6]);
        writeUInt8(u.c[7]);
    }

#if BYTE_ORDER == BIG_ENDIAN
    void writeDouble_le(double inDouble)
#else
    void writeDouble_be(double inDouble)
#endif
    {
        union {
            double f;
            uint8 c[8];
        } u;
        u.f = inDouble;
        writeUInt8(u.c[7]);
        writeUInt8(u.c[6]);
        writeUInt8(u.c[5]);
        writeUInt8(u.c[4]);
        writeUInt8(u.c[3]);
        writeUInt8(u.c[2]);
        writeUInt8(u.c[1]);
        writeUInt8(u.c[0]);
    }


    int8 readInt8() { return (int8)readUInt8(); }

    int16 readInt16_be() {
        uint8 a = readUInt8();
        uint8 b = readUInt8();
        return (int16)((a << 8) | b);
    }

    int16 readInt16_le() {
        uint8 a = readUInt8();
        uint8 b = readUInt8();
        return (int16)((b << 8) | a);
    }

    int32 readInt32_be() {
        uint8 a = readUInt8();
        uint8 b = readUInt8();
        uint8 c = readUInt8();
        uint8 d = readUInt8();
        return (int32)((a << 24) | (b << 16) | (c << 8) | d);
    }

    int32 readInt32_le() {
        uint8 a = readUInt8();
        uint8 b = readUInt8();
        uint8 c = readUInt8();
        uint8 d = readUInt8();
        return (int32)((d << 24) | (c << 16) | (b << 8) | a);
    }

#if BYTE_ORDER == BIG_ENDIAN
    float readFloat_be()
#else
    float readFloat_le()
#endif
    {
        union {
            float f;
            uint8 c[4];
        } u;
        u.c[0] = readUInt8();
        u.c[1] = readUInt8();
        u.c[2] = readUInt8();
        u.c[3] = readUInt8();
        return u.f;
    }

#if BYTE_ORDER == BIG_ENDIAN
    float readFloat_le()
#else
    float readFloat_be()
#endif
    {
        union {
            float f;
            uint8 c[4];
        } u;
        u.c[3] = readUInt8();
        u.c[2] = readUInt8();
        u.c[1] = readUInt8();
        u.c[0] = readUInt8();
        return u.f;
    }


#if BYTE_ORDER == BIG_ENDIAN
    double readDouble_be()
#else
    double readDouble_le()
#endif
    {
        union {
            double f;
            uint8 c[8];
        } u;
        u.c[0] = readUInt8();
        u.c[1] = readUInt8();
        u.c[2] = readUInt8();
        u.c[3] = readUInt8();
        u.c[4] = readUInt8();
        u.c[5] = readUInt8();
        u.c[6] = readUInt8();
        u.c[7] = readUInt8();
        return u.f;
    }

#if BYTE_ORDER == BIG_ENDIAN
    double readDouble_le()
#else
    double readDouble_be()
#endif
    {
        union {
            double f;
            uint8 c[8];
        } u;
        u.c[7] = readUInt8();
        u.c[6] = readUInt8();
        u.c[5] = readUInt8();
        u.c[4] = readUInt8();
        u.c[3] = readUInt8();
        u.c[2] = readUInt8();
        u.c[1] = readUInt8();
        u.c[0] = readUInt8();
        return u.f;
    }

    void readSymbol(char* outString) {
        int length = readUInt8();
        readData(outString, length);
        outString[length] = 0;
    }

    void writeSymbol(char* inString) {
        size_t length = strlen(inString);
        writeUInt8((uint8)length);
        writeData(inString, length);
    }
};


// core routines
template <> inline void SC_IOStream<FILE*>::readData(char* data, size_t size) {
    size_t read = fread(data, 1, size, s);
    if (read != size)
        throw std::runtime_error("SC_IOStream<FILE*>::readData: read != size");
}

template <> inline uint8 SC_IOStream<FILE*>::readUInt8() { return (uint8)fgetc(s); }

template <> inline void SC_IOStream<FILE*>::writeData(char* data, size_t size) { fwrite(data, 1, size, s); }

template <> inline void SC_IOStream<FILE*>::writeUInt8(uint8 inInt) { fputc(inInt, s); }

// core routines
template <> inline void SC_IOStream<char*>::readData(char* data, size_t size) {
    memcpy(data, s, size);
    s += size;
}

template <> inline uint8 SC_IOStream<char*>::readUInt8() { return (uint8)*s++; }

template <> inline void SC_IOStream<char*>::writeData(char* data, size_t size) {
    memcpy(s, data, size);
    s += size;
}

template <> inline void SC_IOStream<char*>::writeUInt8(uint8 inInt) { *s++ = (inInt & 255); }
