// -*- c-basic-offset: 4 -*-

/*
    Rosegarden
    A sequencer and musical notation editor.
 
    This program is Copyright 2000-2007
        Guillaume Laurent   <glaurent@telegraph-road.org>,
        Chris Cannam        <cannam@all-day-breakfast.com>,
        Richard Bown        <bownie@bownie.com>
 
    The moral right of the authors to claim authorship of this work
    has been asserted.
 
    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.  See the file
    COPYING included with this distribution for more information.
*/

#include "SF2PatchExtractor.h"

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <sys/types.h>

namespace Rosegarden
{

using std::string;
using std::cerr;
using std::endl;
using std::ifstream;
using std::ios;


struct Chunk
{
    char id[4];
    u_int32_t size;

    Chunk(ifstream *, bool idOnly = false);
    bool isa(std::string s);
};

Chunk::Chunk(ifstream *file, bool idOnly)
{
    file->read((char *)this->id, 4);
    size = 0;

    if (idOnly)
        return ;

    unsigned char sz[4];
    file->read((char *)sz, 4);
    for (int i = 0; i < 4; ++i)
        size += sz[i] << (i * 8);
}

bool
Chunk::isa(string s)
{
    return string(id, 4) == s;
}

bool
SF2PatchExtractor::isSF2File(string fileName)
{
    ifstream *file = new ifstream(fileName.c_str(), ios::in | ios::binary);
    if (!file)
        throw FileNotFoundException();

    Chunk riffchunk(file);
    if (!riffchunk.isa("RIFF")) {
        file->close();
        return false;
    }

    Chunk sfbkchunk(file, true);
    if (!sfbkchunk.isa("sfbk")) {
        file->close();
        return false;
    }

    file->close();
    return true;
}

SF2PatchExtractor::Device
SF2PatchExtractor::read(string fileName)
{
    Device device;

    ifstream *file = new ifstream(fileName.c_str(), ios::in | ios::binary);
    if (!file)
        throw FileNotFoundException();

    Chunk riffchunk(file);
    if (!riffchunk.isa("RIFF")) {
        file->close();
        throw WrongFileFormatException();
    }

    Chunk sfbkchunk(file, true);
    if (!sfbkchunk.isa("sfbk")) {
        file->close();
        throw WrongFileFormatException();
    }

    while (!file->eof()) {

        Chunk chunk(file);

        if (!chunk.isa("LIST")) {
            // cerr << "Skipping " << string(chunk.id, 4) << endl;
            file->seekg(chunk.size, ios::cur);
            continue;
        }

        Chunk listchunk(file, true);
        if (!listchunk.isa("pdta")) {
            // cerr << "Skipping " << string(id, 4) << endl;
            file->seekg(chunk.size - 4, ios::cur);
            continue;
        }

        int size = chunk.size - 4;
        while (size > 0) {

            Chunk pdtachunk(file);
            size -= 8 + pdtachunk.size;
            if (file->eof()) {
                break;
            }

            if (!pdtachunk.isa("phdr")) { // preset header
                // cerr << "Skipping " << string(pdtachunk.id, 4) << endl;
                file->seekg(pdtachunk.size, ios::cur);
                continue;
            }

            int presets = pdtachunk.size / 38;
            for (int i = 0; i < presets; ++i) {

                char name[21];
                u_int16_t bank, program;

                file->read((char *)name, 20);
                name[20] = '\0';
                file->read((char *)&program, 2);
                file->read((char *)&bank, 2);

                // cerr << "Read name as " << name << endl;

                file->seekg(14, ios::cur);

                if (i == presets - 1 &&
                        bank == 255 &&
                        program == 255 &&
                        string(name) == "EOP")
                    continue;

                device[bank][program] = name;
            }
        }
    }

    file->close();
    return device;
}

}


#ifdef TEST_SF2_PATCH_EXTRACTOR

int main(int argc, char **argv)
{
    using SF2PatchExtractor;

    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " sf2filename" << std::endl;
        return 2;
    }

    try {
        SF2PatchExtractor::Device device =
            SF2PatchExtractor::read(argv[1]);

        std::cerr << "Done.  Presets are:" << std::endl;

        for (SF2PatchExtractor::Device::iterator di = device.begin();
                di != device.end(); ++di) {

            std::cerr << "Bank " << di->first << ":" << std::endl;

            for (SF2PatchExtractor::Bank::iterator bi = di->second.begin();
                    bi != di->second.end();
                    ++bi) {

                std::cerr << "Program " << bi->first << ": \"" << bi->second
                << "\"" << std::endl;
            }
        }
    } catch (SF2PatchExtractor::WrongFileFormatException) {
        std::cerr << "Wrong file format" << std::endl;
    } catch (SF2PatchExtractor::FileNotFoundException) {
        std::cerr << "File not found or couldn't be opened" << std::endl;
    }

    return 0;
}

#endif
