/**
 * \file jinf.cpp
 *
 * Qpid asynchronous store plugin library
 *
 * This file contains the code for the mrg::journal::jinf class.
 *
 * See jinf.hpp comments for details of this class.
 *
 * \author Kim van der Riet
 *
 * Copyright (c) 2007, 2008, 2009 Red Hat, Inc.
 *
 * This file is part of the Qpid async store library msgstore.so.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 * USA
 *
 * The GNU Lesser General Public License is available in the file COPYING.
 */

#include "jrnl/jinf.hpp"

#include <cstdlib>
#include <cstring>
#include <ctime>
#include <fstream>
#include "jrnl/file_hdr.hpp"
#include "jrnl/jcntl.hpp"
#include "jrnl/jerrno.hpp"
#include "jrnl/lp_map.hpp"
#include <sstream>
#include <sys/stat.h>

namespace mrg
{
namespace journal
{

jinf::jinf(const std::string& jinf_filename, bool validate_flag):
        _jver(0),
        _filename(jinf_filename),
        _num_jfiles(0),
        _ae(false),
        _ae_max_jfiles(0),
        _jfsize_sblks(0),
        _sblk_size_dblks(0),
        _dblk_size(0),
        _wcache_pgsize_sblks(0),
        _wcache_num_pages(0),
        _rcache_pgsize_sblks(0),
        _rcache_num_pages(0),
        _tm_ptr(0),
        _valid_flag(false),
        _analyzed_flag(false),
        _initial_owi(false),
        _frot(false)
{
    read(_filename);
    if (validate_flag)
        validate();
}

jinf::jinf(const std::string& jid, const std::string& jdir, const std::string& base_filename, const u_int16_t num_jfiles,
        const bool auto_expand, const u_int16_t ae_max_jfiles, const u_int32_t jfsize_sblks,
        const u_int32_t wcache_pgsize_sblks, const u_int16_t wcache_num_pages, const timespec& ts):
        _jver(RHM_JDAT_VERSION),
        _jid(jid),
        _jdir(jdir),
        _base_filename(base_filename),
        _ts(ts),
        _num_jfiles(num_jfiles),
        _ae(auto_expand),
        _ae_max_jfiles(ae_max_jfiles),
        _jfsize_sblks(jfsize_sblks),
        _sblk_size_dblks(JRNL_SBLK_SIZE),
        _dblk_size(JRNL_DBLK_SIZE),
        _wcache_pgsize_sblks(wcache_pgsize_sblks),
        _wcache_num_pages(wcache_num_pages),
        _rcache_pgsize_sblks(JRNL_RMGR_PAGE_SIZE),
        _rcache_num_pages(JRNL_RMGR_PAGES),
        _tm_ptr(std::localtime(&ts.tv_sec)),
        _valid_flag(false),
        _analyzed_flag(false),
        _initial_owi(false)
{
    set_filename();
}

jinf::~jinf()
{}

void
jinf::validate()
{
    bool err = false;
    std::ostringstream oss;
    if (_jver != RHM_JDAT_VERSION)
    {
        oss << "File \"" << _filename << "\": ";
        oss << "RHM_JDAT_VERSION mismatch: found=" << (int)_jver;
        oss << "; required=" << RHM_JDAT_VERSION << std::endl;
        err = true;
    }
    if (_num_jfiles < JRNL_MIN_NUM_FILES)
    {
        oss << "File \"" << _filename << "\": ";
        oss << "Number of journal files too small: found=" << _num_jfiles;
        oss << "; minimum=" << JRNL_MIN_NUM_FILES << std::endl;
        err = true;
    }
    if (_num_jfiles > JRNL_MAX_NUM_FILES)
    {
        oss << "File \"" << _filename << "\": ";
        oss << "Number of journal files too large: found=" << _num_jfiles;
        oss << "; maximum=" << JRNL_MAX_NUM_FILES << std::endl;
        err = true;
    }
    if (_ae)
    {
        if (_ae_max_jfiles < _num_jfiles)
        {
            oss << "File \"" << _filename << "\": ";
            oss << "Number of journal files exceeds auto-expansion limit: found=" << _num_jfiles;
            oss << "; maximum=" << _ae_max_jfiles;
            err = true;
        }
        if (_ae_max_jfiles > JRNL_MAX_NUM_FILES)
        {
            oss << "File \"" << _filename << "\": ";
            oss << "Auto-expansion file limit too large: found=" << _ae_max_jfiles;
            oss << "; maximum=" << JRNL_MAX_NUM_FILES;
            err = true;
        }
    }
    if (_jfsize_sblks < JRNL_MIN_FILE_SIZE)
    {
        oss << "File \"" << _filename << "\": ";
        oss << "Journal file size too small: found=" << _jfsize_sblks;
        oss << "; minimum=" << JRNL_MIN_FILE_SIZE << " (sblks)" << std::endl;
        err = true;
    }
    if (_sblk_size_dblks != JRNL_SBLK_SIZE)
    {
        oss << "File \"" << _filename << "\": ";
        oss << "JRNL_SBLK_SIZE mismatch: found=" << _sblk_size_dblks;
        oss << "; required=" << JRNL_SBLK_SIZE << std::endl;
        err = true;
    }
    if (_dblk_size != JRNL_DBLK_SIZE)
    {
        oss << "File \"" << _filename << "\": ";
        oss << "JRNL_DBLK_SIZE mismatch: found=" << _dblk_size;
        oss << "; required=" << JRNL_DBLK_SIZE << std::endl;
        err = true;
    }
    if (err)
        throw jexception(jerrno::JERR_JINF_CVALIDFAIL, oss.str(), "jinf", "validate");
    _valid_flag = true;
}

void
jinf::analyze()
{
    lp_map early_map;   // map for all owi flags same as pfid 0
    lp_map late_map;    // map for all owi flags opposite to pfid 0
    bool late_latch = false; // latch for owi switchover

    if (!_valid_flag)
        validate();
    bool done = false;
    for (u_int16_t pfid=0; pfid<_num_jfiles && !done; pfid++)
    {
        std::ostringstream oss;
        if (_jdir.at(_jdir.size() - 1) == '/')
            oss << _jdir << _base_filename << ".";
        else
            oss << _jdir << "/" << _base_filename << ".";
        oss << std::setw(4) << std::setfill('0') << std::hex << pfid;
        oss << "." << JRNL_DATA_EXTENSION;

        // Check size of each file is consistent and expected
        u_int32_t fsize = get_filesize(oss.str());
        if (fsize != (_jfsize_sblks + 1) * _sblk_size_dblks * _dblk_size)
        {
            std::ostringstream oss1;
            oss1 << "File \"" << oss.str() << "\": size=" << fsize << "; expected=" << ((_jfsize_sblks + 1) * _sblk_size_dblks * _dblk_size);
            throw jexception(jerrno::JERR_JINF_BADFILESIZE, oss1.str(), "jinf", "analyze");
        }

        std::ifstream jifs(oss.str().c_str());
        if (!jifs.good())
            throw jexception(jerrno::JERR__FILEIO, oss.str(), "jinf", "analyze");
        file_hdr fhdr;
        jifs.read((char*)&fhdr, sizeof(fhdr));
        if (fhdr._magic != RHM_JDAT_FILE_MAGIC) // No file header
        {
            if (fhdr._magic != 0)
                throw jexception(jerrno::JERR_JINF_INVALIDFHDR, oss.str(), "jinf", "analyze");
            if (!pfid) // pfid 0 == lid 0 cannot be empty
                throw jexception(jerrno::JERR_JINF_JDATEMPTY, oss.str(), "jinf", "analyze");
            _frot = true;
            done = true;
        }
        else
        {
            assert(pfid == fhdr._pfid);
            if (pfid == 0)
            {
                _initial_owi = fhdr.get_owi();
                early_map.insert(fhdr._lfid, pfid);
            }
            else
            {
                if (_initial_owi == fhdr.get_owi())
                {
                    early_map.insert(fhdr._lfid, pfid);
                    if (late_latch && (!_ae || _num_jfiles == JRNL_MIN_NUM_FILES))
                        throw jexception(jerrno::JERR_JINF_OWIBAD, oss.str(), "jinf", "analyze");
                }
                else
                {
                    late_map.insert(fhdr._lfid, pfid);
                    late_latch = true;
                }
            }
        }
        jifs.close();
    } // for (pfid)

    // If this is not the first rotation, all files should be in either early or late maps
    if (!_frot) assert(early_map.size() + late_map.size() == _num_jfiles);

    _pfid_list.clear();
    late_map.get_pfid_list(_pfid_list);
    early_map.get_pfid_list(_pfid_list);

    // Check OWI consistency
//    for (u_int16_t lfid=0; lfid<_num_jfiles && !done; lfid++)
//    {
//        throw jexception(jerrno::JERR_JINF_OWIBAD, oss.str(), "jinf", "analyze");
//    }

    _analyzed_flag = true;
}

void
jinf::write()
{
    std::ostringstream oss;
    oss << _jdir << "/" << _base_filename << "." << JRNL_INFO_EXTENSION;
    std::ofstream of(oss.str().c_str(), std::ofstream::out | std::ofstream::trunc);
    if (!of.good())
        throw jexception(jerrno::JERR__FILEIO, oss.str(), "jinf", "write");
    of << xml_str();
    of.close();
}

u_int16_t
jinf::incr_num_jfiles()
{
    if (_num_jfiles >= JRNL_MAX_NUM_FILES)
        throw jexception(jerrno::JERR_JINF_TOOMANYFILES, "jinf", "incr_num_jfiles");
    return ++_num_jfiles;
}

u_int16_t
jinf::get_first_pfid()
{
    if (!_analyzed_flag)
        analyze();
    return *_pfid_list.begin();
}

u_int16_t
jinf::get_last_pfid()
{
    if (!_analyzed_flag)
        analyze();
    return *_pfid_list.rbegin();
}

jinf::pfid_list&
jinf::get_pfid_list()
{
    if (!_analyzed_flag)
        analyze();
    return _pfid_list;
}

void
jinf::get_normalized_pfid_list(pfid_list& pfid_list)
{
    if (!_analyzed_flag)
        analyze();
    pfid_list.clear();
    u_int16_t s = _pfid_list.size();
    u_int16_t iz = 0; // index of 0 value
    while (_pfid_list[iz] && iz < s)
        iz++;
    assert(_pfid_list[iz] == 0);
    for (u_int16_t i = iz; i < iz + s; i++)
        pfid_list.push_back(_pfid_list[i % s]);
    assert(pfid_list[0] == 0);
    assert(pfid_list.size() == s);
}

bool
jinf::get_initial_owi()
{
    if (!_analyzed_flag)
        analyze();
    return _initial_owi;
}

bool
jinf::get_frot()
{
    if (!_analyzed_flag)
        analyze();
    return _frot;
}

std::string
jinf::to_string() const
{
    std::ostringstream oss;
    oss << std::setfill('0');
    oss << "Journal ID \"" << _jid << "\" initialized " << (_tm_ptr->tm_year + 1900) << "/";
    oss << std::setw(2) << (_tm_ptr->tm_mon + 1) << "/" << std::setw(2) << _tm_ptr->tm_mday << " ";
    oss << std::setw(2) << _tm_ptr->tm_hour << ":" << std::setw(2) << _tm_ptr->tm_min << ":";
    oss << std::setw(2) << _tm_ptr->tm_sec << "." << std::setw(9) << _ts.tv_nsec << ":" << std::endl;
    oss << "  Journal directory: \"" << _jdir << "\"" << std::endl;
    oss << "  Journal base filename: \"" << _base_filename << "\"" << std::endl;
    oss << "  Journal version: " << (unsigned)_jver << std::endl;
    oss << "  Number of journal files: " << _num_jfiles << std::endl;
// TODO: Uncomment these lines when auto-expand is enabled.
//    oss << "  Auto-expand mode: " << (_ae ? "enabled" : "disabled") << std::endl;
//    if (_ae) oss << "  Max. number of journal files (in auto-expand mode): " << _ae_max_jfiles << std::endl;
    oss << "  Journal file size: " << _jfsize_sblks << " sblks" << std::endl;
    oss << "  Softblock size (JRNL_SBLK_SIZE): " << _sblk_size_dblks << " dblks" << std::endl;
    oss << "  Datablock size (JRNL_DBLK_SIZE): " << _dblk_size << " bytes" << std::endl;
    oss << "  Write page size: " << _wcache_pgsize_sblks << " sblks" << std::endl;
    oss << "  Number of write pages: " << _wcache_num_pages << std::endl;
    oss << "  Read page size (JRNL_RMGR_PAGE_SIZE): " << _rcache_pgsize_sblks << " sblks" << std::endl;
    oss << "  Number of read pages (JRNL_RMGR_PAGES): " << _rcache_num_pages << std::endl;
    return oss.str();
}

std::string
jinf::xml_str() const
{
    // TODO: This is *not* an XML writer, rather for simplicity, it uses literals. I'm sure a more elegant way can be
    // found to do this using the real thing...

    std::ostringstream oss;
    oss << std::setfill('0');
    oss << "<?xml version=\"1.0\" ?>" << std::endl;
    oss << "<jrnl>" << std::endl;
    oss << "  <journal_version value=\"" << (unsigned)_jver << "\" />" << std::endl;
    oss << "  <journal_id>" << std::endl;
    oss << "    <id_string value=\"" << _jid << "\" />" << std::endl;
    oss << "    <directory value=\"" << _jdir << "\" />" << std::endl;
    oss << "    <base_filename value=\"" << _base_filename << "\" />" << std::endl;
    oss << "  </journal_id>" << std::endl;
    oss << "  <creation_time>" << std::endl;
    oss << "    <seconds value=\"" << _ts.tv_sec << "\" />" << std::endl;
    oss << "    <nanoseconds value=\"" << _ts.tv_nsec << "\" />" << std::endl;
    oss << "    <string value=\"" << (_tm_ptr->tm_year + 1900) << "/";
    oss << std::setw(2) << (_tm_ptr->tm_mon + 1) << "/" << std::setw(2) << _tm_ptr->tm_mday << " ";
    oss << std::setw(2) << _tm_ptr->tm_hour << ":" << std::setw(2) << _tm_ptr->tm_min << ":";
    oss << std::setw(2) << _tm_ptr->tm_sec << "." << std::setw(9) << _ts.tv_nsec;
    oss << "\" />" << std::endl;
    oss << "  </creation_time>" << std::endl;
    oss << "  <journal_file_geometry>" << std::endl;
    oss << "    <number_jrnl_files value=\"" << _num_jfiles << "\" />" << std::endl;
    oss << "    <auto_expand value=\"" << (_ae ? "true" : "false") << "\" />" << std::endl;
    if (_ae) oss << "    <auto_expand_max_jrnl_files value=\"" << _ae_max_jfiles << "\" />" << std::endl;
    oss << "    <jrnl_file_size_sblks value=\"" << _jfsize_sblks << "\" />" << std::endl;
    oss << "    <JRNL_SBLK_SIZE value=\"" << _sblk_size_dblks << "\" />" << std::endl;
    oss << "    <JRNL_DBLK_SIZE value=\"" << _dblk_size << "\" />" << std::endl;
    oss << "  </journal_file_geometry>" << std::endl;
    oss << "  <cache_geometry>" << std::endl;
    oss << "    <wcache_pgsize_sblks value=\"" << _wcache_pgsize_sblks << "\" />" << std::endl;
    oss << "    <wcache_num_pages value=\"" << _wcache_num_pages << "\" />" << std::endl;
    oss << "    <JRNL_RMGR_PAGE_SIZE value=\"" << _rcache_pgsize_sblks << "\" />" << std::endl;
    oss << "    <JRNL_RMGR_PAGES value=\"" << _rcache_num_pages << "\" />" << std::endl;
    oss << "  </cache_geometry>" << std::endl;
    oss << "</jrnl>" << std::endl;
    return oss.str();
}

void
jinf::set_filename()
{
    std::ostringstream oss;
    oss << _jdir << "/" << _base_filename << "." << JRNL_INFO_EXTENSION;
    _filename = oss.str().c_str();
}

void
jinf::read(const std::string& jinf_filename)
{
    // TODO: This is *not* an XML reader, rather for simplicity, it is a brute-force line reader which relies on string
    // recognition. It relies on the format of xml_str() above; it will not handle a XML restructuring.
    // *** Can it be replaced cheaply by a real XML reader? Should it be, or is this sufficient? ***

    char buff[1024]; // limit of line input length
    std::ifstream jinfs(jinf_filename.c_str());
    if (!jinfs.good())
        throw jexception(jerrno::JERR__FILEIO, jinf_filename.c_str(), "jinf", "read");
    u_int32_t charcnt = 0;
    while (jinfs.good())
    {
        jinfs.getline(buff, 1023);
        charcnt += std::strlen(buff);
        if (std::strstr(buff, "journal_version"))
            _jver = u_int16_value(buff);
        else if(std::strstr(buff, "id_string"))
            string_value(_jid, buff);
        else if(std::strstr(buff, "directory"))
            string_value(_jdir, buff);
        else if(std::strstr(buff, "base_filename"))
            string_value(_base_filename, buff);
        else if(std::strstr(buff, "number_jrnl_files"))
            _num_jfiles = u_int16_value(buff);
        else if(std::strstr(buff, "auto_expand_max_jrnl_files"))
            _ae_max_jfiles = u_int16_value(buff);
        else if(std::strstr(buff, "auto_expand"))
            _ae = bool_value(buff);
        else if(std::strstr(buff, "jrnl_file_size_sblks"))
            _jfsize_sblks = u_int32_value(buff);
        else if(std::strstr(buff, "JRNL_SBLK_SIZE"))
            _sblk_size_dblks = u_int16_value(buff);
        else if(std::strstr(buff, "JRNL_DBLK_SIZE"))
            _dblk_size = u_int32_value(buff);
        else if(std::strstr(buff, "wcache_pgsize_sblks"))
            _wcache_pgsize_sblks = u_int32_value(buff);
        else if(std::strstr(buff, "wcache_num_pages"))
            _wcache_num_pages = u_int32_value(buff);
        else if(std::strstr(buff, "JRNL_RMGR_PAGE_SIZE"))
            _rcache_pgsize_sblks = u_int32_value(buff);
        else if(std::strstr(buff, "JRNL_RMGR_PAGES"))
            _rcache_num_pages = u_int32_value(buff);
        else if(std::strstr(buff, "nanoseconds"))
            _ts.tv_nsec = u_int32_value(buff);
        else if(std::strstr(buff, "seconds"))
        {
            _ts.tv_sec = u_int32_value(buff);
            _tm_ptr = std::localtime(&_ts.tv_sec);
        }
    }
    jinfs.close();
    if (charcnt == 0)
        throw jexception(jerrno::JERR_JINF_ZEROLENFILE, jinf_filename.c_str(), "jinf", "read");
}

bool
jinf::bool_value(char* line) const
{
    return std::strcmp(find_value(line), "true") == 0;
}

u_int16_t
jinf::u_int16_value(char* line) const
{
    return std::atoi(find_value(line));
}

u_int32_t
jinf::u_int32_value(char* line) const
{
    return std::atol(find_value(line));
}

std::string&
jinf::string_value(std::string& str, char* line) const
{
    str.assign(find_value(line));
    return str;
}

char*
jinf::find_value(char* line) const
{
    const char* target1_str = "value=\"";
    int target2_char = '\"';
    char* t1 = std::strstr(line, target1_str);
    if (t1 == 0)
    {
        std::ostringstream oss;
        oss << "File \"" << _filename << "\": line=" << line;
        throw jexception(jerrno::JERR_JINF_NOVALUESTR, oss.str(), "jinf", "find_value");
    }
    t1 += std::strlen(target1_str);

    char* t2 = std::strchr(t1, target2_char);
    if (t2 == 0)
    {
        std::ostringstream oss;
        oss << "File \"" << _filename << "\": line=" << line;
        throw jexception(jerrno::JERR_JINF_BADVALUESTR, oss.str(), "jinf", "find_value");
    }
    *t2 = '\0';
    return t1;
}

u_int32_t
jinf::get_filesize(const std::string& file_name) const
{
    struct stat s;
    if (::stat(file_name.c_str(), &s))
    {
        std::ostringstream oss;
        oss << "stat: file=\"" << file_name << "\"" << FORMAT_SYSERR(errno);
        throw jexception(jerrno::JERR_JINF_STAT, oss.str(), "jinf", "get_filesize");
    }
    if (!S_ISREG(s.st_mode)) // not a regular file,
    {
        std::ostringstream oss;
        oss << "File \"" << file_name << "\" is not a regular file: mode=0x" << std::hex << s.st_mode;
        throw jexception(jerrno::JERR_JINF_NOTREGFILE, oss.str(), "jinf", "get_filesize");
    }
    return u_int32_t(s.st_size);
}

} // namespace journal
} // namespace mrg
