// -*- mode: c++; c-basic-offset:4 -*-

// This file is part of libdap, A C++ implementation of the OPeNDAP Data
// Access Protocol.

// Copyright (c) 2011 OPeNDAP, Inc.
// Author: James Gallagher <jgallagher@opendap.org>
//
// 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
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.

#include "config.h"

#include <signal.h>
#include <unistd.h>

#include "DMR.h"
#include "D4Group.h"
#include "XMLWriter.h"
#include "D4StreamMarshaller.h"
#include "chunked_ostream.h"

#include "D4ConstraintEvaluator.h"
//#include "D4FunctionEvaluator.h"

#include "mime_util.h"	// for last_modified_time() and rfc_822_date()
#include "escaping.h"

#ifndef WIN32
#include "SignalHandler.h"
#include "EventHandler.h"
#include "AlarmHandler.h"
#endif

#include "D4ResponseBuilder.h"

const std::string CRLF = "\r\n";             // Change here, expr-test.cc/dmr-test.cc

using namespace std;
using namespace libdap;

void D4ResponseBuilder::initialize()
{
	// Set default values. Don't use the C++ constructor initialization so
	// that a subclass can have more control over this process.
	d_dataset = "";
	d_timeout = 0;

	d_default_protocol = "4.0"; // DAP_PROTOCOL_VERSION;
}

D4ResponseBuilder::~D4ResponseBuilder()
{
	// If an alarm was registered, delete it. The register code in SignalHandler
	// always deletes the old alarm handler object, so only the one returned by
	// remove_handler needs to be deleted at this point.
	delete dynamic_cast<AlarmHandler*>(SignalHandler::instance()->remove_handler(SIGALRM));
}

/** Set the dataset name, which is a string used to access the dataset
 * on the machine running the server. That is, this is typically a pathname
 * to a data file, although it doesn't have to be. This is not
 * echoed in error messages (because that would reveal server
 * storage patterns that data providers might want to hide). All WWW-style
 * escapes are replaced except for spaces.
 *
 * @brief Set the dataset pathname.
 * @param ds The pathname (or equivalent) to the dataset.
 */
void D4ResponseBuilder::set_dataset_name(const string ds)
{
	d_dataset = www2id(ds, "%", "%20");
}

//// DAP4 methods follow

/** Use values of this instance to establish a timeout alarm for the server.
 If the timeout value is zero, do nothing.
*/
void D4ResponseBuilder::establish_timeout(ostream &stream) const
{
    if (d_timeout > 0) {
        SignalHandler *sh = SignalHandler::instance();
        EventHandler *old_eh = sh->register_handler(SIGALRM, new AlarmHandler(stream));
        delete old_eh;
        alarm(d_timeout);
    }
}

void D4ResponseBuilder::remove_timeout() const
{
	alarm(0);
}

/**
 * Write the on-the-wire DMR response.
 *
 * @note There is no timeout set on this reponse.
 *
 * @param out Write to this stream object
 * @param dmr This is the DMR to write
 * @param eval Use this ConstraintEvaluator
 * @param with_mime_headers If true, include the MIME headers in the response
 */
void D4ResponseBuilder::send_dmr(ostream &out, DMR &dmr, bool with_mime_headers, bool constrained)
{
	if (with_mime_headers) set_mime_text(out, dap4_dmr, x_plain, last_modified_time(d_dataset), dmr.dap_version());

	XMLWriter xml;
	dmr.print_dap4(xml, constrained /* true == constrained */);
	out << xml.get_doc() << flush;
}

void D4ResponseBuilder::send_dap(ostream &out, DMR &dmr, bool with_mime_headers, bool constrained)
{
	try {
		// Set up the alarm.
		establish_timeout(out);

		if (dmr.response_limit() != 0 && dmr.request_size(true) > dmr.response_limit()) {
			string msg = "The Request for " + long_to_string(dmr.request_size(true) / 1024)
					+ "MB is too large; requests for this user are limited to "
					+ long_to_string(dmr.response_limit() / 1024) + "MB.";
			throw Error(msg);
		}

		if (with_mime_headers)
			set_mime_binary(out, dap4_data, x_plain, last_modified_time(d_dataset), dmr.dap_version());

	    // Write the DMR
	    XMLWriter xml;
	    dmr.print_dap4(xml, constrained);

	    // now make the chunked output stream; set the size to be at least chunk_size
	    // but make sure that the whole of the xml plus the CRLF can fit in the first
	    // chunk. (+2 for the CRLF bytes).
	    chunked_ostream cos(out, max((unsigned int)CHUNK_SIZE, xml.get_doc_size()+2));

	    // using flush means that the DMR and CRLF are in the first chunk.
	    cos << xml.get_doc() << CRLF << flush;

	    // Write the data, chunked with checksums
	    D4StreamMarshaller m(cos);
	    dmr.root()->serialize(m, dmr, constrained);

		out << flush;

		remove_timeout();
	}
	catch (...) {
		remove_timeout();
		throw;
	}
}
