/*
 * steghide 0.5.1 - a steganography program
 * Copyright (C) 1999-2003 Stefan Hetzl <shetzl@chello.at>
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

#include <cstdio>

#include "BinaryIO.h"
#include "common.h"
#include "error.h"

void BinaryIO::init() {
    setName("");
    setStream(NULL);
    set_open(false);
}

BinaryIO::BinaryIO(void) { init(); }

BinaryIO::BinaryIO(const std::string &fn, MODE m) {
    init();
    open(fn, m);
}

BinaryIO::~BinaryIO(void) {
    if (is_open()) {
        close();
    }
}

bool BinaryIO::Fileexists(const std::string &fn) const {
    bool retval = false;
    FILE *fd = fopen(fn.c_str(), "r");
    if (fd != NULL) {
        retval = true;
        fclose(fd);
    }
    return retval;
}

void BinaryIO::checkForce(const std::string &fn) const {
    if (!Args.Force.getValue()) {
        if (Fileexists(fn)) {
            Question q(_("the file \"%s\" does already exist. overwrite ?"), fn.c_str());
            q.printMessage();
            if (!q.getAnswer()) {
                throw SteghideError(_("did not write to file \"%s\"."), fn.c_str());
            }
        }
    }
}

void BinaryIO::open(const std::string &fn, MODE m) {
    if (fn == "") {
        switch (m) {
        case READ: {
            setStream(stdin);
            break;
        }

        case WRITE: {
            setStream(stdout);
            break;
        }

        default: {
            myassert(0);
            break;
        }
        }
    } else {
        FILE *s = NULL;
        const char *cmode;

        switch (m) {
        case READ: {
            cmode = "rb";
            break;
        }

        case WRITE: {
            checkForce(fn);
            cmode = "wb";
            break;
        }

        default: {
            myassert(0);
            break;
        }
        }

        if ((s = fopen(fn.c_str(), cmode)) == NULL) {
            throw SteghideError(_("could not open the file \"%s\"."), fn.c_str());
        }

        setStream(s);
    }
    setName(fn);
    setMode(m);
    set_open(true);
}

bool BinaryIO::eof(void) const {
    int c = fgetc(getStream());
    bool retval = feof(getStream());
    ungetc(c, getStream());
    return retval;
}

void BinaryIO::close(void) {
    myassert(is_open());

    if (getName() != "") {
        if (fclose(getStream()) == EOF) {
            throw SteghideError(_("could not close the file \"%s\"."), getName().c_str());
        }
    }

    setName("");
    setStream(NULL);
    set_open(false);
}

BYTE BinaryIO::read8(void) {
    myassert(getMode() == READ);
    myassert(is_open());

    int c = EOF;
    if ((c = fgetc(getStream())) == EOF) {
        throw BinaryInputError(getName(), getStream());
    }

    return (BYTE)c;
}

UWORD16 BinaryIO::read16_le(void) {
    myassert(getMode() == READ);
    myassert(is_open());

    int bytes[2];
    for (int i = 0; i < 2; i++) {
        if ((bytes[i] = fgetc(getStream())) == EOF) {
            throw BinaryInputError(getName(), getStream());
        }
    }

    return ((bytes[1] << 8) | bytes[0]);
}

UWORD16 BinaryIO::read16_be(void) {
    myassert(getMode() == READ);
    myassert(is_open());

    int bytes[2];
    for (int i = 0; i < 2; i++) {
        if ((bytes[i] = fgetc(getStream())) == EOF) {
            throw BinaryInputError(getName(), getStream());
        }
    }

    return ((bytes[0] << 8) | bytes[1]);
}

UWORD32 BinaryIO::read32_le(void) {
    myassert(getMode() == READ);
    myassert(is_open());

    int bytes[4];
    for (int i = 0; i < 4; i++) {
        if ((bytes[i] = fgetc(getStream())) == EOF) {
            throw BinaryInputError(getName(), getStream());
        }
    }

    return ((bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0]);
}

UWORD32 BinaryIO::read32_be(void) {
    myassert(getMode() == READ);
    myassert(is_open());

    int bytes[4];
    for (int i = 0; i < 4; i++) {
        if ((bytes[i] = fgetc(getStream())) == EOF) {
            throw BinaryInputError(getName(), getStream());
        }
    }

    return ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]);
}

UWORD32 BinaryIO::read_le(unsigned short n) {
    myassert(getMode() == READ);
    myassert(is_open());
    myassert(n <= 4);

    UWORD32 retval = 0;
    for (unsigned short i = 0; i < n; i++) {
        int byte = EOF;
        if ((byte = fgetc(getStream())) == EOF) {
            throw BinaryInputError(getName(), getStream());
        }
        retval |= (((BYTE)byte) << (8 * i));
    }

    return retval;
}

std::string BinaryIO::readstring(unsigned int len) {
    char retval[len + 1];
    for (unsigned int i = 0; i < len; i++) {
        retval[i] = (char)read8();
    }
    retval[len] = '\0';
    return std::string(retval);
}

void BinaryIO::write8(BYTE val) {
    myassert(getMode() == WRITE);
    myassert(is_open());

    if (fputc((int)val, getStream()) == EOF) {
        throw BinaryOutputError(getName());
    }
}

void BinaryIO::write16_le(UWORD16 val) {
    myassert(getMode() == WRITE);
    myassert(is_open());

    for (int i = 0; i <= 1; i++) {
        if (fputc((val >> (8 * i)) & 0xFF, getStream()) == EOF) {
            throw BinaryOutputError(getName());
        }
    }
}

void BinaryIO::write16_be(UWORD16 val) {
    myassert(getMode() == WRITE);
    myassert(is_open());

    for (int i = 1; i >= 0; i--) {
        if (fputc((val >> (8 * i)) & 0xFF, getStream()) == EOF) {
            throw BinaryOutputError(getName());
        }
    }
}

void BinaryIO::write32_le(UWORD32 val) {
    myassert(getMode() == WRITE);
    myassert(is_open());

    for (int i = 0; i <= 3; i++) {
        if (fputc((val >> (8 * i)) & 0xFF, getStream()) == EOF) {
            throw BinaryOutputError(getName());
        }
    }
}

void BinaryIO::write32_be(UWORD32 val) {
    myassert(getMode() == WRITE);
    myassert(is_open());

    for (int i = 3; i >= 0; i--) {
        if (fputc((val >> (8 * i)) & 0xFF, getStream()) == EOF) {
            throw BinaryOutputError(getName());
        }
    }
}

void BinaryIO::write_le(UWORD32 val, unsigned short n) {
    myassert(getMode() == WRITE);
    myassert(is_open());
    myassert(n <= 4);

    for (short i = 0; i < n; i++) {
        if (fputc((val >> (8 * i)) & 0xFF, getStream()) == EOF) {
            throw BinaryOutputError(getName());
        }
    }
}

void BinaryIO::writestring(const std::string &s) {
    if (fputs(s.c_str(), getStream()) == EOF) {
        throw BinaryOutputError(getName());
    }
}
