/* Copyright 2015 the SumatraPDF project authors (see AUTHORS file).
   License: Simplified BSD (see COPYING.BSD) */

class ByteReader {
    const uint8_t *d;
    size_t len;

    // Unpacks a structure from the data according to the given format
    // e.g. the format "32b2w6d" unpacks 32 Bytes, 2 16-bit Words and 6 32-bit Dwords
    bool Unpack(void *strct, size_t size, const char *format, size_t off, bool isBE) const {
        int repeat = 0;
        size_t idx = 0;
        for (const char *c = format; *c; c++) {
            if (isdigit((unsigned char)*c)) {
                repeat = atoi(c);
                for (c++; isdigit((unsigned char)*c); c++);
            }
            switch (*c) {
            case 'b':
                if (off + idx + 1 > len || idx + 1 > size)
                    return false;
                *(uint8_t *)((uint8_t *)strct + idx) = Byte(off + idx);
                idx += 1;
                break;
            case 'w':
                if (off + idx + 2 > len || idx + 2 > size)
                    return false;
                *(uint16_t *)((uint8_t *)strct + idx) = Word(off + idx, isBE);
                idx += 2;
                break;
            case 'd':
                if (off + idx + 4 > len || idx + 4 > size)
                    return false;
                *(uint32_t *)((uint8_t *)strct + idx) = DWord(off + idx, isBE);
                idx += 4;
                break;
            case 'q':
                if (off + idx + 8 > len || idx + 8 > size)
                    return false;
                *(uint64_t *)((uint8_t *)strct + idx) = QWord(off + idx, isBE);
                idx += 8;
                break;
            default:
                return false;
            }
            if (--repeat > 0)
                c--;
        }
        return idx == size;
    }

public:
    ByteReader(const char *data, size_t len) :
        d((const uint8_t *)data), len(len) { }
    ByteReader(const unsigned char *data, size_t len) :
        d((const uint8_t *)data), len(len) { }

    uint8_t Byte(size_t off) const {
        if (off < len)
            return d[off];
        return 0;
    }

    uint16_t WordLE(size_t off) const {
        if (off + 2 <= len)
            return d[off] | (d[off + 1] << 8);
        return 0;
    }
    uint16_t WordBE(size_t off) const {
        if (off + 2 <= len)
            return (d[off] << 8) | d[off + 1];
        return 0;
    }
    uint16_t Word(size_t off, bool isBE) const {
        return isBE ? WordBE(off) : WordLE(off);
    }

    uint32_t DWordLE(size_t off) const {
        if (off + 4 <= len)
            return d[off] | (d[off + 1] << 8) | (d[off + 2] << 16) | (d[off + 3] << 24);
        return 0;
    }
    uint32_t DWordBE(size_t off) const {
        if (off + 4 <= len)
            return (d[off] << 24) | (d[off + 1] << 16) | (d[off + 2] << 8) | d[off + 3];
        return 0;
    }
    uint32_t DWord(size_t off, bool isBE) const {
        return isBE ? DWordBE(off) : DWordLE(off);
    }

    uint64_t QWordLE(size_t off) const {
        if (off + 8 <= len)
            return DWordLE(off) | ((uint64_t)DWordLE(off + 4) << 32);
        return 0;
    }
    uint64_t QWordBE(size_t off) const {
        if (off + 8 <= len)
            return ((uint64_t)DWordBE(off) << 32) | DWordBE(off + 4);
        return 0;
    }
    uint64_t QWord(size_t off, bool isBE) const {
        return isBE ? QWordBE(off) : QWordLE(off);
    }

    const char *Find(size_t off, uint8_t byte) const {
        if (off >= len)
            return nullptr;
        return (const char *)memchr(d + off, byte, len - off);
    }

    bool UnpackLE(void *strct, size_t size, const char *format, size_t off=0) const {
        return Unpack(strct, size, format, off, false);
    }
    bool UnpackBE(void *strct, size_t size, const char *format, size_t off=0) const {
        return Unpack(strct, size, format, off, true);
    }
    bool Unpack(void *strct, size_t size, const char *format, bool isBE, size_t off=0) const {
        return Unpack(strct, size, format, off, isBE);
    }
};