/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2014 Franklin Wei, Benjamin Brown
 * Copyright (C) 2004 Gregory Montoir
 *
 * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ***************************************************************************/

#include "plugin.h"
#include "bank.h"
#include "file.h"
#include "resource.h"

void bank_create(struct Bank* b, const char *dataDir)
{
    b->_dataDir = dataDir;
}

bool bank_read(struct Bank* b, const struct MemEntry *me, uint8_t *buf) {

    bool ret = false;
    char bankName[10];
    rb->snprintf(bankName, 10, "bank%02x", me->bankId);
    File f;
    file_create(&f, false);
    if (!file_open(&f, bankName, b->_dataDir, "rb"))
        error("bank_read() unable to open '%s' in dir '%s'", bankName, b->_dataDir);

    file_seek(&f, me->bankOffset);

    /* Depending if the resource is packed or not we */
    /* can read directly or unpack it. */
    if (me->packedSize == me->size) {
        file_read(&f, buf, me->packedSize);
        ret = true;
    } else {
        file_read(&f, buf, me->packedSize);
        b->_startBuf = buf;
        b->_iBuf = buf + me->packedSize - 4;
        ret = bank_unpack(b);
    }
    file_close(&f);
    return ret;
}

void bank_decUnk1(struct Bank* b, uint8_t numChunks, uint8_t addCount) {
    uint16_t count = bank_getCode(b, numChunks) + addCount + 1;
    debug(DBG_BANK, "bank_decUnk1(%d, %d) count=%d", numChunks, addCount, count);
    b->_unpCtx.datasize -= count;
    while (count--) {
        assert(b->_oBuf >= b->_iBuf && b->_oBuf >= b->_startBuf);
        *b->_oBuf = (uint8_t)bank_getCode(b, 8);
        --b->_oBuf;
    }
}

/*
  Note from fab: This look like run-length encoding.
*/
void bank_decUnk2(struct Bank* b, uint8_t numChunks) {
    uint16_t i = bank_getCode(b, numChunks);
    uint16_t count = b->_unpCtx.size + 1;
    debug(DBG_BANK, "bank_decUnk2(%d) i=%d count=%d", numChunks, i, count);
    b->_unpCtx.datasize -= count;
    while (count--) {
        assert(b->_oBuf >= b->_iBuf && b->_oBuf >= b->_startBuf);
        *b->_oBuf = *(b->_oBuf + i);
        --b->_oBuf;
    }
}

/*
  Most resource in the banks are compacted.
*/
bool bank_unpack(struct Bank* b) {
    b->_unpCtx.size = 0;
    b->_unpCtx.datasize = READ_BE_UINT32(b->_iBuf);
    b->_iBuf -= 4;
    b->_oBuf = b->_startBuf + b->_unpCtx.datasize - 1;
    b->_unpCtx.crc = READ_BE_UINT32(b->_iBuf);
    b->_iBuf -= 4;
    b->_unpCtx.chk = READ_BE_UINT32(b->_iBuf);
    b->_iBuf -= 4;
    b->_unpCtx.crc ^= b->_unpCtx.chk;
    do {
        if (!bank_nextChunk(b)) {
            b->_unpCtx.size = 1;
            if (!bank_nextChunk(b)) {
                bank_decUnk1(b, 3, 0);
            } else {
                bank_decUnk2(b, 8);
            }
        } else {
            uint16_t c = bank_getCode(b, 2);
            if (c == 3) {
                bank_decUnk1(b, 8, 8);
            } else {
                if (c < 2) {
                    b->_unpCtx.size = c + 2;
                    bank_decUnk2(b, c + 9);
                } else {
                    b->_unpCtx.size = bank_getCode(b, 8);
                    bank_decUnk2(b, 12);
                }
            }
        }
    } while (b->_unpCtx.datasize > 0);
    return (b->_unpCtx.crc == 0);
}

uint16_t bank_getCode(struct Bank* b, uint8_t numChunks) {
    uint16_t c = 0;
    while (numChunks--) {
        c <<= 1;
        if (bank_nextChunk(b)) {
            c |= 1;
        }
    }
    return c;
}

bool bank_nextChunk(struct Bank* b) {
    bool CF = bank_rcr(b, false);
    if (b->_unpCtx.chk == 0) {
        assert(b->_iBuf >= b->_startBuf);
        b->_unpCtx.chk = READ_BE_UINT32(b->_iBuf);
        b->_iBuf -= 4;
        b->_unpCtx.crc ^= b->_unpCtx.chk;
        CF = bank_rcr(b, true);
    }
    return CF;
}

bool bank_rcr(struct Bank* b, bool CF) {
    bool rCF = (b->_unpCtx.chk & 1);
    b->_unpCtx.chk >>= 1;
    if (CF) b->_unpCtx.chk |= 0x80000000;
    return rCF;
}
