/** @file
 * @brief Extract fields from XLSX sheet*.xml.
 */
/* Copyright (C) 2012,2013,2021 Olly Betts
 *
 * 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, see
 * <https://www.gnu.org/licenses/>.
 */

#include <config.h>

#include "xlsxparser.h"

#include <cstdlib>
#include <cstring>
#include <ctime>

using namespace std;

bool
XlsxParser::opening_tag(const string &tag)
{
    if (tag == "c") {
	// We need to distinguish <v> tags which are inside <c t="s">, as these
	// are numeric references to shared strings.
	string type;
	if (get_attribute("t", type) && type == "s") {
	    mode = MODE_C_STRING;
	} else {
	    mode = MODE_C_LITERAL;
	    if (get_attribute("s", type)) {
		unsigned long style_id = strtoul(type.c_str(), NULL, 10);
		if (date_style.find(style_id) != date_style.end()) {
		    mode = MODE_C_DATE;
		}
	    }
	}
    } else if (tag == "v") {
	if (mode == MODE_C_LITERAL) {
	    mode = MODE_V_LITERAL;
	} else if (mode == MODE_C_STRING) {
	    mode = MODE_V_STRING;
	} else if (mode == MODE_C_DATE) {
	    mode = MODE_V_DATE;
	}
    } else if (tag == "si") {
	mode = MODE_SI;
    } else if (tag == "sst") {
	string unique_count;
	if (get_attribute("uniqueCount", unique_count)) {
	    unsigned long c = strtoul(unique_count.c_str(), NULL, 10);
	    // This reserving is just a performance tweak, so don't go
	    // reserving ludicrous amounts of space just because an XML
	    // attribute told us to.
	    sst.reserve(std::min(c, 1000000ul));
	}
    } else if (tag == "workbookPr") {
	string v;
	if (get_attribute("date1904", v)) {
	    date1904 = (v == "true" || v == "1");
	}
    } else if (tag == "numFmt") {
	string formatcode;
	if (get_attribute("formatCode", formatcode)) {
	    // Heuristic for "date format" (FIXME: implement properly)
	    if (strchr(formatcode.c_str(), 'd') &&
		strchr(formatcode.c_str(), 'm') &&
		strchr(formatcode.c_str(), 'y')) {
		string v;
		if (get_attribute("numFmtId", v)) {
		    unsigned long id = strtoul(v.c_str(), NULL, 10);
		    date_format.insert(id);
		}
	    }
	}
    } else if (tag == "cellXfs") {
	mode = MODE_CELLXFS;
    } else if (tag == "xf") {
	if (mode == MODE_CELLXFS) {
	    string v;
	    if (get_attribute("numFmtId", v)) {
		unsigned long id = strtoul(v.c_str(), NULL, 10);
		if ((id >= 14 && id <= 17) ||
		    date_format.find(id) != date_format.end()) {
		    date_style.insert(style_index);
		}
	    }
	    ++style_index;
	}
    }
    return true;
}

void
XlsxParser::process_content(const string& content)
{
    switch (mode) {
	case MODE_V_DATE: {
	    // Date field.
	    unsigned long c = strtoul(content.c_str(), NULL, 10);
	    if (date1904) {
		c -= 24107;
	    } else {
		// The spec insists we treat 1900 as a leap year!
		if (c > 60) --c;
		c -= 25568;
	    }
	    time_t t = c * 86400 + 43200;
	    struct tm * tm = gmtime(&t);
	    if (tm) {
		char buf[32];
		size_t res = strftime(buf, sizeof(buf), "%Y-%m-%d", tm);
		if (res)
		    append_field(string(buf, res));
	    }
	    mode = MODE_NONE;
	    return;
	}
	case MODE_V_STRING: {
	    // Shared string use.
	    unsigned long c = strtoul(content.c_str(), NULL, 10);
	    if (c < sst.size()) {
		append_field(sst[c]);
	    }
	    mode = MODE_NONE;
	    return;
	}
	case MODE_V_LITERAL:
	    // Literal (possibly calculated) field value.
	    append_field(content);
	    mode = MODE_NONE;
	    return;
	case MODE_SI:
	    // Shared string definition.
	    sst.push_back(content);
	    mode = MODE_NONE;
	    return;
	default:
	    return;
    }
}
