/*
 * File: plain.cc
 *
 * Copyright (C) 2005-2007 Jorge Arellano Cid <jcid@dillo.org>
 * Copyright (C) 2024 Rodrigo Arias Mallo <rodarima@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 3 of the License, or
 * (at your option) any later version.
 */

/**
 * @file
 * Module for decoding a text/plain object into a dw widget.
 */

#include "msg.h"
#include "prefs.h"
#include "cache.h"
#include "bw.h"
#include "web.hh"
#include "misc.h"
#include "styleengine.hh"

#include "uicmd.hh"

#include "dw/core.hh"
#include "dw/textblock.hh"

// Dw to Textblock
#define DW2TB(dw)  ((Textblock*)dw)

using namespace dw;
using namespace dw::core;


class DilloPlain {
private:
   class PlainLinkReceiver: public dw::core::Layout::LinkReceiver {
   public:
      DilloPlain *plain;
      bool press(dw::core::Widget *widget, int link, int img, int x, int y,
                 dw::core::EventButton *event);
   };
   PlainLinkReceiver plainReceiver;

   void addLine(char *Buf, uint_t BufSize);

public:
   BrowserWindow *bw;

   Widget *dw;
   style::Style *widgetStyle;
   size_t Start_Ofs;    /* Offset of where to start reading next */
   int state;

   DilloPlain(BrowserWindow *bw);
   ~DilloPlain();

   void write(void *Buf, uint_t BufSize, int Eof);
};

/* FSM states */
enum {
   ST_SeekingEol,
   ST_Eol,
   ST_Eof
};

/*
 * Exported function with C linkage.
 */
extern "C" {
void *a_Plain_text(const char *type, void *P, CA_Callback_t *Call,void **Data);
}

/*
 * Forward declarations
 */
static void Plain_callback(int Op, CacheClient_t *Client);
void a_Plain_free(void *data);


/**
 * Diplain constructor.
 */
DilloPlain::DilloPlain(BrowserWindow *p_bw)
{
   /* Init event receiver */
   plainReceiver.plain = this;

   /* Init internal variables */
   bw = p_bw;
   dw = new Textblock (prefs.limit_text_width);
   Start_Ofs = 0;
   state = ST_SeekingEol;

   Layout *layout = (Layout*) bw->render_layout;
   // TODO (1x) No URL?
   StyleEngine styleEngine (layout, NULL, NULL, bw->zoom);

   styleEngine.startElement ("body", bw);
   styleEngine.startElement ("pre", bw);
   widgetStyle = styleEngine.wordStyle (bw);
   widgetStyle->ref ();

   /* The context menu */
   layout->connectLink (&plainReceiver);

   /* Hook destructor to the dw delete call */
   dw->setDeleteCallback(a_Plain_free, this);
}

/**
 * Free memory used by the DilloPlain class.
 */
DilloPlain::~DilloPlain()
{
   _MSG("::~DilloPlain()\n");
   widgetStyle->unref();
}

/**
 * Receive the mouse button press event
 */
bool DilloPlain::PlainLinkReceiver::press (Widget *widget, int, int, int, int,
                                           EventButton *event)
{
   _MSG("DilloPlain::PlainLinkReceiver::buttonPress\n");

   if (event->button == 3) {
      a_UIcmd_page_popup(plain->bw, FALSE, NULL);
      return true;
   }
   return false;
}

void DilloPlain::addLine(char *Buf, uint_t BufSize)
{
   int len;
   char buf[129];
   char *end = Buf + BufSize;

   if (BufSize > 0) {
      // Limit word length to avoid X11 coordinate
      // overflow with extremely long lines.
      while ((len = a_Misc_expand_tabs(&Buf, end, buf, sizeof(buf) - 1))) {
         assert ((uint_t)len < sizeof(buf));
         buf[len] = '\0';
         DW2TB(dw)->addText(buf, len, widgetStyle);
      }
   } else {
      // Add dummy word for empty lines - otherwise the parbreak is ignored.
      DW2TB(dw)->addText("", 0, widgetStyle);
   }

   DW2TB(dw)->addParbreak(0, widgetStyle);
}

/**
 * Here we parse plain text and put it into the page structure.
 * (This function is called by Plain_callback whenever there's new data)
 */
void DilloPlain::write(void *Buf, uint_t BufSize, int Eof)
{
   char *Start;
   uint_t i, len, MaxBytes;

   _MSG("DilloPlain::write Eof=%d\n", Eof);

   Start = (char*)Buf + Start_Ofs;
   MaxBytes = BufSize - Start_Ofs;
   i = len = 0;
   while ( i < MaxBytes ) {
      switch ( state ) {
      case ST_SeekingEol:
         if (Start[i] == '\n' || Start[i] == '\r')
            state = ST_Eol;
         else {
            ++i; ++len;
         }
         break;
      case ST_Eol:
         addLine(Start + i - len, len);
         if (Start[i] == '\r' && Start[i + 1] == '\n') ++i;
         if (i < MaxBytes) ++i;
         state = ST_SeekingEol;
         len = 0;
         break;
      }
   }
   Start_Ofs += i - len;
   if (Eof && len) {
      addLine(Start + i - len, len);
      Start_Ofs += len;
   }

   DW2TB(dw)->flush();
}

/**
 * Set callback function and callback data for "text/" MIME major-type.
 */
void *a_Plain_text(const char *type, void *P, CA_Callback_t *Call, void **Data)
{
   DilloWeb *web = (DilloWeb*)P;
   DilloPlain *plain = new DilloPlain(web->bw);

   *Call = (CA_Callback_t)Plain_callback;
   *Data = (void*)plain;

   return (void*)plain->dw;
}

void a_Plain_free(void *data)
{
   _MSG("a_Plain_free! %p\n", data);
   delete ((DilloPlain *)data);
}

/**
 * This function is a cache client
 */
static void Plain_callback(int Op, CacheClient_t *Client)
{
   DilloPlain *plain = (DilloPlain*)Client->CbData;

   if (Op) {
      /* Do the last line: */
      plain->write(Client->Buf, Client->BufSize, 1);
      /* remove this client from our active list */
      a_Bw_close_client(plain->bw, Client->Key);
   } else {
      plain->write(Client->Buf, Client->BufSize, 0);
   }
}

