/* zenutils - Utilities for working with creative firmwares.
 * Copyright 2007 (c) Rasmus Ry <rasmus.ry{at}gmail.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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <iostream>
#include <sstream>
#include <getpot/GetPot>
#include <file.h>
#include <firmware.h>
#include <utils.h>


static const char VERSION[] = "0.1";

void print_version()
{
    std::cout
        << "firmware_make - Creates a Creative firmware archive." << std::endl
        << "Version " << VERSION << std::endl
        << "Copyright (c) 2007 Rasmus Ry" << std::endl;
}

void print_help()
{
    print_version();
    std::cout << std::endl
        << "Usage: firmware_make [command] [options]" << std::endl
        << std::endl
        << " Commands:" << std::endl
        << "  -h,--help" << std::endl
        << "      prints this message." << std::endl
        << "  -m,--makefile [file]" << std::endl
        << "      specifies the .mk file to build the firmware archive from."
        << std::endl << std::endl
        << " Options:" << std::endl
        << "  -V,--verbose" << std::endl
        << "      prints verbose messages." << std::endl
        << "  -f,--firmware [file]" << std::endl
        << "      specifies the output firmware file name" << std::endl
        << std::endl
        ;
}

dword get_tag_value(std::string tag)
{
    if (tag[0] == '0' && tag[1] == 'x')
    {
        dword val = 0;
        if (sscanf(tag.c_str(), "0x%08X", &val) == 1)
            return val;
        if (sscanf(tag.c_str(), "0x%08x", &val) == 1)
            return val;
    }
    else
    {
        return shared::swap(*(dword*)&tag[0]);
    }
    return 0;
}

bool process_child(const GetPot& mkfile, const std::string& root, int index,
                   zen::firmware_entry& entry)
{
    std::stringstream sstm;
    sstm << root << "/" << index;
    std::string var = sstm.str() + "/tag";
    std::string tag = mkfile(var.c_str(), "");
    var = sstm.str() + "/name";
    std::string name = mkfile(var.c_str(), "");
    var = sstm.str() + "/file";
    std::string file = mkfile(var.c_str(), "");

    if (file.empty() || tag.empty())
    {
        std::cerr << "Invalid file or tag for var: " << sstm.str()
                  << std::endl;
        return false;
    }

    shared::bytes buffer;
    if (!shared::read_file(file, buffer))
    {
        std::cerr << "Failed to read the file: " << file << std::endl;
        return false;
    }

    entry.get_bytes().clear();
    entry.get_header().tag = get_tag_value(tag);
    size_t contoff = entry.get_content_offset();
    if (contoff)
    {
        entry.get_bytes().resize(contoff, 0);
        if (!name.empty())
        {
            size_t endoff = entry.is_big_endian() ? 1 : 0;
            for (int i = 0; i < name.size(); ++i)
                entry.get_bytes()[i * 2 + endoff] = name[i];
        }
    }
    entry.get_bytes().insert(entry.get_bytes().end(), buffer.begin(),
                             buffer.end());

    entry.get_header().size = entry.get_bytes().size();

    return true;
}

int process_arguments(int argc, char* argv[])
{
    //--------------------------------------------------------------------
    // Parse input variables.
    //--------------------------------------------------------------------

    GetPot cl(argc, argv);
    if (cl.size() == 1 || cl.search(2, "-h", "--help"))
    {
        print_help();
        return 1;
    }

    std::string makefile;
    if (cl.search("-m") || cl.search("--makefile"))
        makefile = cl.next("");
    if (makefile.empty())
    {
        std::cerr << "Makefile must be specified." << std::endl;
        return 2;
    }

    std::string firmware;
    if (cl.search("-f") || cl.search("--firmware"))
        firmware = cl.next("");
    if (firmware.empty())
    {
        std::cerr << "Firmware must be specified." << std::endl;
        return 3;
    }

    bool verbose = false;
    if (cl.search("-V") || cl.search("--verbose"))
        verbose = true;

    GetPot mkfile(makefile.c_str());
    if (verbose)
        mkfile.print();

    bool big_endian;
    std::string endian = mkfile("endian", "little");
    if (endian == "little")
    {
        big_endian = false;
    }
    else if (endian == "big")
    {
        big_endian = true;
    }
    else
    {
        std::cerr << "Invalid value of 'endian'" << std::endl;
        return 4;
    }

    zen::firmware_archive archive(big_endian);
    int childcount = mkfile("children/count", 0);
    if (!childcount)
    {
        std::cerr << "A firmware archive must have at least one child entry."
                  << std::endl;
        return 5;
    }

    for (int i = 0; i < childcount; i++)
    {
        zen::firmware_entry entry(big_endian);
        if (!process_child(mkfile, "children", i, entry))
        {
            return 6;
        }
        archive.get_children().push_back(entry);
    }

    int neighbourcount = mkfile("neighbours/count", 0);
    for (int i = 0; i < neighbourcount; i++)
    {
        zen::firmware_entry entry(big_endian);
        if (!process_child(mkfile, "neighbours", i, entry))
        {
            return 7;
        }
        archive.get_neighbours().push_back(entry);
    }

    std::ofstream ofs;
    ofs.open(firmware.c_str(), std::ios::out|std::ios::binary|std::ios::trunc);
    if (!ofs)
    {
        std::cerr << "Failed to create the firmware file." << std::endl;
        return 8;
    }

    if (!archive.write(ofs))
    {
        std::cerr << "Failed to save the firmware archive." << std::endl;
        return 9;
    }
    ofs.close();

    size_t length = archive.calc_size();
    if (!length)
    {
        std::cerr << "Failed to determine the size of the firmware archive."
                  << std::endl;
        return 10;
    }

    int align = length % 4;
    if (align)
    {
        shared::bytes padding(4 - align, 0);
        if (!shared::write_file(firmware, padding, false, length))
        {
            std::cerr << "Failed to write padding data." << std::endl;
            return 11;
        }
    }

    return 0;
}

int main(int argc, char* argv[])
{
    try
    {
        return process_arguments(argc, argv);
    }
    catch (const std::exception& xcpt)
    {
        std::cerr << "Exception caught: " << xcpt.what() << std::endl;
        return -1;
    }
    catch (...)
    {
        std::cerr << "Unknown exception caught." << std::endl;
        return -2;
    }
    return -3;
}
