// Copyright (c) 1994, 1995 James Clark
// See the file COPYING for copying permission.

#include "config.h"
#include "Event.h"
#include "MessageEventHandler.h"
#include "SgmlsEventHandler.h"
#include "RastEventHandler.h"
#include "OutputCharStream.h"
#include "Boolean.h"
#include "NsgmlsMessages.h"
#include "MessageArg.h"
#include "ErrnoMessageArg.h"
#include "ParserApp.h"
#include "sptchar.h"
#include "macros.h"

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>

#ifdef SP_NAMESPACE
using namespace SP_NAMESPACE;
#endif

class NsgmlsApp : public ParserApp {
public:
  NsgmlsApp();
  int processArguments(int argc, AppChar **argv);
  ErrorCountEventHandler *makeEventHandler();
  void processOption(AppChar opt, const AppChar *arg);
  void allLinkTypesActivated();
private:
  Boolean suppressOutput_;
  Boolean prologOnly_;
  unsigned outputFlags_;
  String<AppChar> rastFile_;
  const AppChar *rastOption_;
  Boolean batchMode_;
};

SP_DEFINE_APP(NsgmlsApp)

class PrologMessageEventHandler : public MessageEventHandler {
public:
  PrologMessageEventHandler(Messenger *messenger);
  void endProlog(EndPrologEvent *);
};

class XRastEventHandler : public RastEventHandler {
public:
  XRastEventHandler(SgmlParser *,
		    const NsgmlsApp::AppChar *filename,
		    const StringC &filenameStr,
		    const OutputCodingSystem *,
		    CmdLineApp *,
		    Messenger *messenger);
  ~XRastEventHandler();
  void message(MessageEvent *);
  void truncateOutput();
  void allLinkTypesActivated();
private:
  Messenger *messenger_;
  // file_ must come before os_ so it gets inited first
  FileOutputByteStream file_;
  EncodeOutputCharStream os_;
  const NsgmlsApp::AppChar *filename_;
  const StringC filenameStr_;
  CmdLineApp *app_;
};

NsgmlsApp::NsgmlsApp()
: suppressOutput_(0),
  batchMode_(0),
  prologOnly_(0),
  outputFlags_(0),
  rastOption_(0)
{
  registerOption('B', SP_T("batch-mode"), NsgmlsMessages::BHelp);
  registerOption('o', SP_T("option"), NsgmlsMessages::option,
                 NsgmlsMessages::oHelp);
  registerOption('p', SP_T("only-prolog"), NsgmlsMessages::pHelp);
  registerOption('s', SP_T("no-output"), NsgmlsMessages::sHelp);
  registerOption('t', SP_T("rast-file"), NsgmlsMessages::file,
                 NsgmlsMessages::tHelp);
  // FIXME treat these as aliases
  registerOption('d', 0, NsgmlsMessages::dHelp);
  registerOption('l', 0, NsgmlsMessages::lHelp);
  registerOption('m', 0, NsgmlsMessages::mHelp);
  registerOption('r', 0, NsgmlsMessages::rHelp);
  registerOption('u', 0, NsgmlsMessages::uHelp);
  registerInfo(NsgmlsMessages::info1);
  registerInfo(NsgmlsMessages::info2);
  registerInfo(NsgmlsMessages::info3);
  registerInfo(NsgmlsMessages::info4);
  registerInfo(NsgmlsMessages::info5);
  registerInfo(NsgmlsMessages::info6);
  registerInfo(NsgmlsMessages::info7);
  registerInfo(NsgmlsMessages::info8);
}

void NsgmlsApp::processOption(AppChar opt, const AppChar *arg)
{
  switch (opt) {
  case 'B':
    batchMode_ = 1;
    break;
  case 'd':
    // warn about duplicate entity declarations
    options_.warnDuplicateEntity = 1;
    break;
  case 'l':
    // output L commands
    outputFlags_ |= SgmlsEventHandler::outputLine;
    break;
  case 'm':
    processOption(SP_T('c'), arg);
    break;
  case 'o':
    {
      static struct {
	// Qualifier works around CodeWarrior bug
	const CmdLineApp::AppChar *name;
	unsigned flag;
      } outputOptions[] = {
	{ SP_T("line"), SgmlsEventHandler::outputLine },
	{ SP_T("entity"), SgmlsEventHandler::outputEntity },
	{ SP_T("id"), SgmlsEventHandler::outputId },
	{ SP_T("included"), SgmlsEventHandler::outputIncluded },
	{ SP_T("notation-sysid"), SgmlsEventHandler::outputNotationSysid },
	{ SP_T("nonsgml"), SgmlsEventHandler::outputNonSgml },
	{ SP_T("empty"), SgmlsEventHandler::outputEmpty },
	{ SP_T("data-attribute"), SgmlsEventHandler::outputDataAtt },
      };
      Boolean found = 0;
      for (size_t i = 0; i < SIZEOF(outputOptions); i++)
	if (tcscmp(arg, outputOptions[i].name) == 0) {
	  outputFlags_ |= outputOptions[i].flag;
	  found = 1;
	  break;
	}
      if (!found)
	message(NsgmlsMessages::unknownOutputOption,
		StringMessageArg(convertInput(arg)));
    }
    break;
  case 'p':
    prologOnly_ = 1;
    break;
  case 'r':
    // warn about defaulted entity reference
    options_.warnDefaultEntityReference = 1;
    break;
  case 's':
    suppressOutput_ = 1;
    break;
  case 't':
    rastOption_ = arg;
    break;
  case 'u':
    // warn about undefined elements
    options_.warnUndefinedElement = 1;
    break;
  default:
    ParserApp::processOption(opt, arg);
    break;
  }
}

int NsgmlsApp::processArguments(int argc, AppChar **argv)
{
  if (batchMode_) {
    int ret = 0;
    for (int i = 0; i < argc; i++) {
      if (rastOption_) {
	rastFile_.assign(rastOption_, tcslen(rastOption_));
	rastFile_.append(argv[i], tcslen(argv[i]));
	rastFile_ += SP_T('\0');
      }
      int tem = ParserApp::processArguments(1, argv + i);
      if (tem > ret)
	ret = tem;
    }
    return ret;
  }
  else
    return ParserApp::processArguments(argc, argv);
}

void NsgmlsApp::allLinkTypesActivated()
{
  if (!rastOption_)
    ParserApp::allLinkTypesActivated();
}

ErrorCountEventHandler *NsgmlsApp::makeEventHandler()
{
  if (prologOnly_)
    return new PrologMessageEventHandler(this);
  else if (rastOption_) {
    const AppChar *s = batchMode_ ? rastFile_.data() : rastOption_;
    return new XRastEventHandler(&parser_, s, convertInput(s),
				 outputCodingSystem_, this, this);
  }
  else if (suppressOutput_)
    return new MessageEventHandler(this, &parser_);
  else
    return new SgmlsEventHandler(&parser_,
				 makeStdOut(),
				 this,
				 outputFlags_);
}

PrologMessageEventHandler::PrologMessageEventHandler(Messenger *messenger)
: MessageEventHandler(messenger)
{
}

void PrologMessageEventHandler::endProlog(EndPrologEvent *event)
{
  cancel();
  delete event;
}

XRastEventHandler::XRastEventHandler(SgmlParser *parser,
				     const NsgmlsApp::AppChar *filename,
				     const StringC &filenameStr,
				     const OutputCodingSystem *codingSystem,
				     CmdLineApp *app,
				     Messenger *messenger)
: RastEventHandler(parser, messenger),
  messenger_(messenger),
  filename_(filename),
  filenameStr_(filenameStr),
  app_(app)
{
  errno = 0;
  if (!file_.open(filename)) {
    messenger->message(CmdLineApp::openFileErrorMessage(),
		       StringMessageArg(filenameStr),
		       ErrnoMessageArg(errno));
    exit(1);
  }
  os_.open(&file_, codingSystem);
  setOutputStream(&os_);
}

XRastEventHandler::~XRastEventHandler()
{
  end();
}

void XRastEventHandler::truncateOutput()
{
  os_.flush();
  errno = 0;
  if (!file_.close())
    messenger_->message(CmdLineApp::closeFileErrorMessage(),
			StringMessageArg(filenameStr_),
			ErrnoMessageArg(errno));
  errno = 0;
  if (!file_.open(filename_)) {
    messenger_->message(CmdLineApp::openFileErrorMessage(),
			StringMessageArg(filenameStr_),
			ErrnoMessageArg(errno));
    exit(1);
  }
}

void XRastEventHandler::message(MessageEvent *event)
{
  messenger_->dispatchMessage(event->message());
  ErrorCountEventHandler::message(event);
}
