//  ------------------------------------------------------------------
//  GoldED+
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 1999-2000 Alexander S. Aganichev
//  ------------------------------------------------------------------
//  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, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
//  MA 02111-1307 USA
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  Message viewer class implementation.
//  ------------------------------------------------------------------

#include <golded.h>
#include <gutlos.h>

#if defined(__USE_ALLOCA__)
  #include <malloc.h>
#endif


//  ------------------------------------------------------------------

GMsgHeaderView::GMsgHeaderView() {

  alive = false;
  at_row = at_column = width = height = 0;
  border_type = gwindow::bordertype_none;
  window_color = from_color = to_color = subject_color = BLUE_|_LGREY;
  location_color = BLUE_|_LGREY;
  title_color = highlight_color = RED_|_LGREY;
  border_color = YELLOW_|_LGREY;
}


//  ------------------------------------------------------------------

GMsgHeaderView::~GMsgHeaderView() {

  Destroy();
}


//  ------------------------------------------------------------------

void GMsgHeaderView::Create() {

  alive = true;

  window.openxy(
    at_row,
    at_column,
    height,
    width,
    border_type,
    border_color,
    window_color
  );
}


//  ------------------------------------------------------------------

void GMsgHeaderView::Destroy() {

  if(alive)
    window.close();
  else
    alive = false;
}


//  ------------------------------------------------------------------

void GMsgHeaderView::Use(Area *areaptr, GMsg *msgptr) {

  area = areaptr;
  msg = msgptr;
}


//  ------------------------------------------------------------------

void GMsgHeaderView::Paint() {

  ISub buf;
  int namewidth = CFG->disphdrnodeset.pos - CFG->disphdrnameset.pos;
  int nodewidth = CFG->disphdrdateset.pos - CFG->disphdrnodeset.pos;
  int datewidth = MinV(width - CFG->disphdrdateset.pos, CFG->disphdrdateset.len);

  #if defined(GUTLOS_FUNCS)
  g_set_ostitle_name(struplow(strtmp(area->echoid())), 0);
  #endif

  // Generate top line fields
  char buf1[16];
  if((CFG->dispareano == ALWAYS) or (CFG->dispareano and area->board()))
    sprintf(buf1, "[%u] ", area->board());
  else
    *buf1 = NUL;

#if defined(__USE_ALLOCA__)
  char *top = (char*)alloca(width+1);
#else
  __extension__ char top[width+1];
#endif
  strxmerge(top, width+1, " ", buf1, strtrim(strtmp(area->desc())), " (",
            area->isinternet() ? area->Internetaddress() : area->Aka().addr.make_string(buf),
            ") ", NULL);

  int desclen = strlen(top);

  window.printc(0, 0, border_color|ACSET, _box_table(W_BHEAD, 1));
  window.prints(0, 1, title_color, top);

  if(msg->areakludgeid)
    strxmerge(top, width+1, " ", area->echoid(), " (", msg->areakludgeid, ") ", NULL);
  else
    strxmerge(top, width+1, " ", area->echoid(), " ", NULL);

  int taglen = strlen(top);

  if((width - (desclen + taglen + 2)) > 0)
    window.fill(0, desclen+1, 0, width-(taglen+1)-1, _box_table(W_BHEAD, 1), border_color|ACSET);

  window.prints(0, width-taglen-1, title_color, top);
  window.printc(0, width-1, border_color|ACSET, _box_table(W_BHEAD, 1));

  // Generate message attributes string
  bool attrsgenerated = false;
  MakeAttrStr(buf, width-CFG->disphdrnodeset.pos, &msg->attr);
  if(*buf) {
    attrsgenerated = true;
    strsetsz(buf, width-CFG->disphdrnodeset.pos);
    window.prints(1, CFG->disphdrnodeset.pos, window_color, buf);
  }

  // Generate message number and reply links string
  char* ptr = buf;
  int list_max = msg->link.list_max();
  uint32_t* replies = (uint32_t*)throw_calloc(list_max+1, sizeof(uint32_t));
  uint32_t replyto, replynext;
  if(CFG->switches.get(disprealmsgno)) {
    ptr += sprintf(ptr, " %-5.5s: #%u [%u]", LNG->Msg, msg->msgno, area->Msgn.Count()+(msg->attr.nwm() ? 1 : 0));
    replyto = msg->link.to();
    replies[0] = msg->link.first();
    replynext = msg->link.next();
    for(int replyn=1; replyn<list_max+1; replyn++)
      replies[replyn] = msg->link.list(replyn-1);
  }
  else {
    uint active = area->Msgn.Count() + (msg->attr.nwm() ? 1 : 0);
    ptr += sprintf(ptr, " %-5.5s: %u %s %u", LNG->Msg, msg->attr.nwm() ? active : area->Msgn.ToReln(msg->msgno), LNG->of, active);
    replyto = area->Msgn.ToReln(msg->link.to());
    replies[0] = area->Msgn.ToReln(msg->link.first());
    replynext = area->Msgn.ToReln(msg->link.next());
    for(int replyn=1; replyn<list_max+1; replyn++)
      replies[replyn] = area->Msgn.ToReln(msg->link.list(replyn-1));
  }
  if(replyto)
    ptr += sprintf(ptr, " -%u", replyto);
  for(int replyn=0,plus=0; (replyn<(list_max+1)) and (not attrsgenerated or ((ptr-buf)<CFG->disphdrnodeset.pos)); replyn++)
    if(replies[replyn])
      ptr += sprintf(ptr, " %s%u", plus++?"":"+", replies[replyn]);
  if(replynext and (not attrsgenerated or ((ptr-buf)<CFG->disphdrnodeset.pos)))
    sprintf(ptr, " *%u", replynext);
  throw_free(replies);

  strsetsz(buf, attrsgenerated ? CFG->disphdrnodeset.pos : width);
  window.prints(1, 0, window_color, buf);

  // Get marks
  if(area->bookmark == msg->msgno)
    window.prints(1, 5, highlight_color, "\x11");
  if(area->Mark.Count() and area->Mark.Find(msg->msgno))
    window.prints(1, 7, highlight_color, "\x10");

  // Generate from info
  bool nodegenerated = false;
  if(not area->isinternet()) {
    if(area->isecho() or not (*msg->ifrom and (*msg->realby or *msg->iorig)))
    {
      // Generate orig node data
      if(msg->orig.net)
        msg->orig.make_string(buf);
      else
        *buf = NUL;
      nodegenerated = true;
      strsetsz(buf, nodewidth);
      window.prints(2, CFG->disphdrnodeset.pos, from_color, buf);
    }
  }

  if((not area->isecho() or area->isnewsgroup()) and *msg->ifrom and *msg->realby)
    strxmerge(buf, (namewidth+nodewidth), msg->realby, " <", msg->iorig, ">", NULL);
  else if((not area->isecho() or area->isnewsgroup()) and *msg->ifrom and *msg->iorig)
    strxcpy(buf, msg->iorig, (namewidth+nodewidth));
  else
    strxcpy(buf, msg->By(), (namewidth+nodewidth));

  strsetsz(buf, nodegenerated ? namewidth : (namewidth+nodewidth));

  window.prints(2, 0, window_color, LNG->From);
  vattr color = ((msg->foundwhere&GFIND_FROM) or msg->attr.fmu() or (msg->attr.loc() and CFG->switches.get(displocalhigh))) ? highlight_color : from_color;
  color = GetColorName(msg->By(), msg->orig, color);
  window.prints(2, CFG->disphdrnameset.pos, color, buf);

  if (datewidth > 0)
  {
    if (msg->written)
    {
      struct tm tm; ggmtime(&tm, &msg->written);
      strftimei(buf, datewidth+1, LNG->DateTimeFmt, &tm);
    }
    else
      *buf = NUL;

    strsetsz(buf, datewidth);
    window.prints(2, CFG->disphdrdateset.pos, from_color, buf);
  }

  // Generate dest node data
  nodegenerated = false;
  if(not area->isinternet()) {
    if(not (*msg->ito and (*msg->realto or *msg->idest)))
    {
      if(msg->dest.net and area->isnet()) {
        msg->dest.make_string(buf);
        if(msg->odest.net) {
          if((msg->odest.net != msg->dest.net) or (msg->odest.node != msg->dest.node)) {
            sprintf(buf+strlen(buf), " %s %u/%u", LNG->Via, msg->odest.net, msg->odest.node);
          }
        }
        nodegenerated = true;
        strsetsz(buf, nodewidth);
        window.prints(3, CFG->disphdrnodeset.pos, to_color, buf);
      }
    }
  }

  if((not area->isecho() or area->isnewsgroup()) and *msg->ito and *msg->realto)
    strxmerge(buf, (namewidth+nodewidth), msg->realto, " <", msg->idest, ">", NULL);
  else if((not area->isecho() or area->isnewsgroup()) and *msg->ito and *msg->idest)
    strxcpy(buf, msg->idest, (namewidth+nodewidth));
  else
    strxcpy(buf, msg->To(), (namewidth+nodewidth));

  strsetsz(buf, nodegenerated ? namewidth : (namewidth+nodewidth));

  window.prints(3, 0, window_color, LNG->To);
  color = ((msg->foundwhere&GFIND_TO) or msg->attr.tou()) ? highlight_color : to_color;
  { Addr zero;
    color = GetColorName(msg->To(), area->isnet() ? msg->dest : zero, color);
  }
  window.prints(3, CFG->disphdrnameset.pos, color, buf);

  if (datewidth > 0)
  {
    if (msg->arrived)
    {
      struct tm tm; ggmtime(&tm, &msg->arrived);
      strftimei(buf, datewidth+1, LNG->DateTimeFmt, &tm);
    }
    else
      *buf = NUL;

    strsetsz(buf, datewidth);
    window.prints(3, CFG->disphdrdateset.pos, to_color, buf);
  }

  // Generate subjectline
  strxcpy(buf, (msg->attr.att() or msg->attr.frq() or msg->attr.urq()) ? LNG->File : LNG->Subj, 10);
  int lngsubjlen = strlen(buf);
  window.prints(4, 0, window_color, buf);

  strxcpy(buf, msg->re, width-lngsubjlen);
  strsetsz(buf, width-lngsubjlen);
  window.prints(4, lngsubjlen, (msg->foundwhere&GFIND_SUBJECT) ? highlight_color : subject_color, buf);

  // Generate bottom line
  window.fill(5, 0, 5, width-1, _box_table(W_BHEAD, 1), border_color|ACSET);

  // Calculate attach file sizes
  if(msg->attr.att()) {
    if(CFG->dispattachsize) {
      char buf2[GMAXPATH];
      int begpos = strlen(LNG->File);
      strcpy(buf, msg->re);
      char* ptr = strtok(buf, " ");
      while(ptr) {
        if(*ptr == '^')
          ptr++;
        if((g_isalpha(*ptr) and (ptr[1] == ':')) or (ptr[0] == '\\') or (ptr[0] == '/'))
          strcpy(buf2, ptr);
        else
          sprintf(buf2, "%s%s", CFG->inboundpath, ptr);
        long sz = GetFilesize(MapPath(buf2));
        if(sz == -1)
          sprintf(buf1, " %s ", LNG->n_a);
        else {
          switch(CFG->dispattachsize) {
            case ATTACH_BYTES:
              sprintf(buf1, " %li ", sz);
              break;
            case ATTACH_KBYTES:
              sprintf(buf1, " %lik ", (sz+512L)/1024L);
              break;
          }
        }
        window.prints(5, begpos+int(ptr-buf)-1, title_color, buf1);
        ptr = strtok(NULL, " ");
      }
    }
  }

  if(CFG->dispmsgsize and msg->txt and not _in_editor) {
    uint len = strlen(msg->txt);
    switch(CFG->dispmsgsize) {
      case DISPMSGSIZE_BYTES:
        sprintf(buf1, "%u", len);
        break;
      case DISPMSGSIZE_KBYTES:
        sprintf(buf1, "%uk", (len+512)/1024);
        break;
      case DISPMSGSIZE_LINES:
        sprintf(buf1, "%u", msg->lines);
        break;
      default:
        *buf1 = NUL;
    }
    if(*buf1)
      window.prints(5, 1, title_color, buf1);
  }

  if (CFG->disphdrlocation && !_in_editor)
  {
    std::string loc;
    LookupNodeLocation(msg, loc, LOOK_CITY1);
    if (loc.length()) loc = " " + loc;

    if (AA->isnet())
    {
      std::string loc2;
      LookupNodeLocation(msg, loc2, LOOK_CITY2);
      if (loc.length()) loc += CFG->locationdelimiter + loc2;
    }

    if (loc.length())
    {
      loc += " ";
      int pos = window.width() - loc.length();
      pos = ((CFG->disphdrlocation >> 16) == TCENTER) ? pos/2 : pos-1;
      window.prints(5, pos, location_color, loc.c_str());
    }
  }
}


//  ------------------------------------------------------------------

GMsgBodyView::GMsgBodyView() {

  alive = false;
  scrollbar_visible = false;
  at_row = at_column = width = height = visible_width = 0;
  border_type = gwindow::bordertype_none;
  window_color = BLACK_|_LGREY;
  highlight_color = WHITE_|_RED;
  scrollbar_color = DGREY_|_LGREY;
  border_color = YELLOW_|_LGREY;
}


//  ------------------------------------------------------------------

GMsgBodyView::~GMsgBodyView() {

  Destroy();
}


//  ------------------------------------------------------------------

void GMsgBodyView::Create() {

  alive = true;

  window.openxy(
    at_row,
    at_column,
    height,
    width,
    border_type,
    border_color,
    window_color,
    scrollbar_color
  );

  dummy_line.txt = "";
  dummy_line.type = 0;
  dummy_line.color = window_color;

  visible_width = width - (CFG->switches.get(disppagebar) ? 1 : 0);
}


//  ------------------------------------------------------------------

void GMsgBodyView::Destroy() {

  if(alive)
    window.close();
  else
    alive = false;
}


//  ------------------------------------------------------------------

void GMsgBodyView::Use(Area *areaptr, GMsg *msgptr, int startline) {

  area = areaptr;
  msg = msgptr;

  // Find the first highlighted line and go to it
  if(msg->foundwhere & GFIND_BODY) {
    for(startline=0; startline<msg->lines; startline++) {
      if(msg->line[startline]->type & GLINE_HIGH)
        break;
    }
    // Adjust position in window
    startline -= height / 3;
    if(startline < 0)
      startline = 0;
    else if((startline + height - 1) >= msg->lines)
      startline = msg->lines - height;
  }

  if(startline >= msg->lines)
    startline = msg->lines - 1;

  if(startline < 0)
    startline = 0;

  upperline = startline;
  lowerline = upperline + height - 1;
  can_pagedown = lowerline < (msg->lines-1);

  scrollbar_visible = CFG->switches.get(disppagebar) ? (msg->lines > height) : false;
  visible_width = scrollbar_visible ? width-1 : width;
  window.set_scrollbar_color(scrollbar_visible ? scrollbar_color : DEFATTR);
}


//  ------------------------------------------------------------------

void GMsgBodyView::PaintLine(int row, Line *line) {

  // Calculate effective coordinates for vputs
  int vrow = gwin.active->srow + row;
  uint llen = line->txt.length();

  vattr color = (line->type & GLINE_HIGH) ? highlight_color : line->color;

  // Trim line if it longer than should be. This actually happens in very rare
  // cases, but always when hex dump displayed.
  if(llen > visible_width) {
    llen = visible_width;
    line->txt.erase(llen);
  }

  // Print it
  if(not SearchHighlight(line, vrow, visible_width, highlight_color)) {
    if(line->type & GLINE_ORIG and strneql(line->txt.c_str(), " * Origin: ", 11)) {
      prints(vrow, 0, color, " * Origin: ");
      StyleCodeHighlight(line->txt.c_str()+11, vrow, 11, not AA->attr().hex() and AA->adat->hidestylies, color);
    }
    else
      StyleCodeHighlight(line->txt.c_str(), vrow, 0, not AA->attr().hex() and AA->adat->hidestylies, color);
    int tlen = strlen(line->txt.c_str());
    printns(vrow, tlen, color, "", visible_width-tlen);
  }
  else
    printns(vrow, 0, color, line->txt.c_str(), visible_width);
}


//  ------------------------------------------------------------------

void GMsgBodyView::Paint() {

  window.activate_quick();

  Line* dummy_index = NULL;
  Line** line_index = msg->line ? (msg->line + upperline) : &dummy_index;

  for(int row=0; row<height; row++)
    PaintLine(row, *line_index ? *line_index++ : &dummy_line);

  UpdateScrollbar();
}


//  ------------------------------------------------------------------

int GMsgBodyView::Top(int redraw) {

  if(redraw or (msg->lines > height)) {
    can_pagedown = true;
    upperline = 0;
    lowerline = height - 1;
    Paint();
    return true;
  }

  return false;
}


//  ------------------------------------------------------------------

int GMsgBodyView::Bottom() {

  if(msg->lines > height) {
    if(msg->lines <= height) {
      can_pagedown = true;
      upperline = 0;
    }
    else {
      can_pagedown = false;
      upperline = msg->lines - height;
    }
    lowerline = upperline + height - 1;
    Paint();
    return true;
  }

  return false;
}


//  ------------------------------------------------------------------

int GMsgBodyView::PageUp() {

  if(msg->lines > height) {
    can_pagedown = true;
    if(upperline) {
      upperline -= height - 1;
      if(upperline < 0)
        upperline = 0;
      lowerline = upperline + height - 1;
      Paint();
      return true;
    }
  }

  return false;
}


//  ------------------------------------------------------------------

int GMsgBodyView::PageDown() {

  if(msg->lines > height) {
    if(can_pagedown) {
      if(lowerline < msg->lines-1) {
        lowerline += height - 1;
        if(lowerline >= msg->lines-1)
          can_pagedown = false;
        upperline = lowerline - height + 1;
        Paint();
        return true;
      }
    }
  }

  return false;
}


//  ------------------------------------------------------------------

int GMsgBodyView::LineUp() {

  if(msg->lines > height) {
    if(upperline) {
      window.scroll_down();
      upperline--;
      lowerline--;
      #ifdef GOLD_MOUSE
      gmou.HideCursor();
      #endif
      PaintLine(0, msg->line[upperline]);
      #ifdef GOLD_MOUSE
      gmou.ShowCursor();
      #endif
      can_pagedown = lowerline < (msg->lines-1);
      UpdateScrollbar();
      return true;
    }
  }
  return false;
}


//  ------------------------------------------------------------------

int GMsgBodyView::LineDown() {

  if(msg->lines > height) {
    if(lowerline < (msg->lines-1)) {
      window.scroll_up();
      upperline++;
      lowerline++;
      #ifdef GOLD_MOUSE
      gmou.HideCursor();
      #endif
      PaintLine(height-1, msg->line[lowerline]);
      #ifdef GOLD_MOUSE
      gmou.ShowCursor();
      #endif
      can_pagedown = lowerline < (msg->lines-1);
      UpdateScrollbar();
      return true;
    }
  }

  return false;
}


//  ------------------------------------------------------------------

int GMsgBodyView::Continue() {

  if(can_pagedown)
    return PageDown();

  return false;
}


//  ------------------------------------------------------------------

int GMsgBodyView::ThumbTrack(int pos) {

  return ThumbPosition(pos);
}


//  ------------------------------------------------------------------

int GMsgBodyView::ThumbPosition(int pos) {

  upperline = pos;
  lowerline = upperline + height - 1;
  can_pagedown = lowerline < (msg->lines-1);
  Paint();
  return true;
}


//  ------------------------------------------------------------------

void GMsgBodyView::UpdateScrollbar() {

  if(CFG->switches.get(disppagebar) and scrollbar_visible)
    window.vscrollbar(msg->lines, msg->lines-height, upperline);
}


//  ------------------------------------------------------------------

void GMsgBodyView::prints(int wrow, int wcol, vattr atr, const char* str)
{
  vputs(wrow, wcol, atr, str);
}


//  ------------------------------------------------------------------

void GMsgBodyView::printns(int wrow, int wcol, vattr atr, const char* str, uint len)
{
  vputns(wrow, wcol, atr, str, len);
}


//  ------------------------------------------------------------------