#include "fm.h"
#include "myctype.h"
#include "regex.h"

void
makeAnchorKey(int line, int pos, AnchorKey *key)
{
  int i;

  for (i = sizeof(int) ; i > 0 ; pos >>= CHAR_BIT)
    key->s[sizeof(int) + --i] = pos & ~(~0 << CHAR_BIT);

  for (i = sizeof(int) ; i > 0 ; line >>= CHAR_BIT)
    key->s[--i] = line & ~(~0 << CHAR_BIT);
}

AnchorList *
putAnchor(AnchorList *al, int i, int line, int pos, Anchor **ret)
{
  Anchor *a;
  AnchorKey *key;

  if (!al) {
    al = New(AnchorList);
    al->anchors = btri_new_node(&btri_string_tab_desc);
    al->urls = NULL;
    al->alv = NULL;
    al->nlink = al->nlink_max = 0;
  }

  a = New(Anchor);
  memset(a, 0, sizeof(*a));

  if (i == -1)
    a->link = New(AnchorLink);
  else {
    if (i < 0)
      i = al->nlink;

    if (i >= al->nlink) {
      NEW_OBJV1(&al->nlink_max, i, &al->alv, sizeof(AnchorLink), FALSE);
      memset(&al->alv[al->nlink], 0, sizeof(AnchorLink) * (i + 1 - al->nlink));
      al->nlink = i + 1;
    }

    a->link = &al->alv[i];
  }

  a->start.line = a->end.line = line;
  a->start.pos = a->end.pos = pos;
  key = NewAtom(AnchorKey);
  makeAnchorKey(line, pos, key);
  btri_search_mem(&btri_string_tab_desc, BTRI_OP_ADD | BTRI_OP_WR, key->s, sizeof(key->s), al->anchors, (void **)&a);

  if (ret)
    *ret = a;

  return al;
}

static int
hseq2linknum(SeqMap *seqmap, AnchorList *al, int hseq)
{
  int i;

  if (hseq < 0)
    i = -1;
  else {
    if (hseq < seqmap->n) {
      if ((i = seqmap->v[hseq]) < 0)
	i = seqmap->v[hseq] = al ? al->nlink : 0;
    }
    else {
      NEW_OBJV1(&seqmap->n_max, hseq, &seqmap->v, sizeof(seqmap->v[0]), TRUE);

      if (hseq > seqmap->n) {
	int j;

	for (j = seqmap->n ; j < hseq ;)
	  seqmap->v[j++] = -1;
      }

      seqmap->n = hseq + 1;
      seqmap->v[hseq] = i = al ? al->nlink : 0;
    }
  }

  return i;
}

static Anchor *
putHrefAnchor(Buffer *buf, Href *href, int line, int pos)
{
  Anchor *a;

  buf->href = putAnchor(buf->href, hseq2linknum(&buf->hseqmap, buf->href, href->hseq), line, pos, &a);
  a->hseq = href->hseq;
  a->link->href = href;

  if (buf->hmarklist && href->hseq >= 0 && href->hseq < buf->hmarklist->nmark) {
    BufferPoint po;

    po.line = line;
    po.pos = pos;

    if (bpcmp(buf->hmarklist->marks[href->hseq].origin, po) > 0)
      buf->hmarklist->marks[href->hseq].origin = po;

    if (bpcmp(buf->hmarklist->marks[href->hseq].corner, po) < 0)
      buf->hmarklist->marks[href->hseq].corner = po;
  }

  return a;
}

Anchor *
registerHref(Buffer *buf, int hseq, char *url, char *target, char *referer, int line, int pos)
{
  Href *href;

  href = New(Href);
  href->hseq = hseq;
  href->url = url;
  href->target = target;
  href->referer = referer;
  return putHrefAnchor(buf, href, line, pos);
}

AnchorList *
putNameAnchor(AnchorList *al, char *name, int line, int pos)
{
  Anchor *a;

  al = putAnchor(al, -1, line, pos, &a);
  a->hseq = -1;
  a->link->id = name;

  if (!al->urls)
    al->urls = btri_new_node(&btri_string_tab_desc);

  btri_search_str(&btri_string_tab_desc, BTRI_OP_ADD | BTRI_OP_WR, name, al->urls, (void **)&a);
  return al;
}

void
registerName(Buffer *buf, char *name, int line, int pos)
{
  buf->name = putNameAnchor(buf->name, name, line, pos);
}

static Anchor *
putImageAnchor(Buffer *buf, Image *image, int line, int pos)
{
  Anchor *a;

  buf->img = putAnchor(buf->img, hseq2linknum(&buf->iseqmap, buf->img, image->hseq), line, pos, &a);
  a->hseq = image->hseq;
  a->link->img = image;
  return a;
}

Anchor *
registerImg(Buffer *buf, int hseq, char *url, int line, int pos)
{
  Image *img;

  img = New(Image);
  img->hseq = hseq;
  img->url = url;
#ifdef USE_IMAGE
  img->ext = NULL;
  img->width = img->height = img->xoffset = img->y = img->rows = 0;
  img->map = NULL;
  img->ismap = FALSE;
  img->cache = NULL;
#endif
  return putImageAnchor(buf, img, line, pos);
}

static Anchor *
putFormAnchor(Buffer *buf, FormItemList *fi, int line, int pos)
{
  Anchor *a;

  buf->formitem = putAnchor(buf->formitem, hseq2linknum(&buf->hseqmap, buf->formitem, fi->hseq), line, pos, &a);
  a->hseq = fi->hseq;
  a->link->fi = fi;
  return a;
}

Anchor *
registerForm(Buffer *buf, int hseq, FormList *flist, struct parsed_tag *tag, int line, int pos, Phase1Env *p1env)
{
  FormItemList *fi;

  if (!(fi = formList_addInput(flist, tag, p1env)))
    return NULL;

  fi->hseq = hseq;
  return putFormAnchor(buf, fi, line, pos);
}

int
onAnchor(Anchor *a, int line, int pos)
{
  BufferPoint bp;
  bp.line = line;
  bp.pos = pos;

  if (bpcmp(bp, a->start) < 0)
    return -1;

  if (bpcmp(a->end, bp) <= 0)
    return 1;

  return 0;
}

struct retrieveAnchor_arg_st {
  int line, pos;
  Anchor *a;
};

static int
retrieveAnchor_internal(btri_desc_t *desc, void **arg1, void *arg2)
{
  Anchor *a = *arg1;
  struct retrieveAnchor_arg_st *x = arg2;

  x->a = !onAnchor(a, x->line, x->pos) ? a : NULL;
  return 1;
}

Anchor *
retrieveAnchor(AnchorList *al, int line, int pos)
{
  if (al) {
    AnchorKey key;
    Anchor *a;

    makeAnchorKey(line, pos, &key);

    if (btri_fast_search_mem(key.s, sizeof(key.s), al->anchors, (void **)&a) != bt_failure)
      return a;
    else {
      btri_key_cursor_t kcur;
      struct retrieveAnchor_arg_st x;

      kcur.base = key.s;
      kcur.n = sizeof(key.s) * CHAR_BIT;
      x.line = line;
      x.pos = pos;
      x.a = NULL;
      btri_map_max_smaller(&btri_string_tab_desc, 0, &kcur, al->anchors, retrieveAnchor_internal, &x);
      return x.a;
    }
  }
  else
    return NULL;
}

Anchor *
retrieveCurrentAnchor(Buffer *buf)
{
  if (!buf->currentLine)
    return NULL;

  return retrieveAnchor(buf->href, buf->currentLine->linenumber, buf->pos);
}

Anchor *
retrieveCurrentImg(Buffer *buf)
{
  if (!buf->currentLine)
    return NULL;

  return retrieveAnchor(buf->img, buf->currentLine->linenumber, buf->pos);
}

Anchor *
retrieveCurrentForm(Buffer *buf)
{
  if (!buf->currentLine)
    return NULL;

  return retrieveAnchor(buf->formitem, buf->currentLine->linenumber, buf->pos);
}

Anchor *
searchAnchor(AnchorList *al, char *str)
{
  Anchor *a;

  return (al && al->urls && btri_fast_search_str(str, al->urls, (void **)&a) != bt_failure) ? a : NULL;
}

Anchor *
searchURLLabel(Buffer *buf, char *url)
{
  return searchAnchor(buf->name, url);
}

#ifdef USE_NNTP
static Anchor *
_put_anchor_news(Buffer *buf, int hseq, char *p1, char *p2, int line, int pos)
{
  Str tmp;

  p1++;

  if (*(p2 - 1) == '>')
    p2--;

  tmp = Strnew_size(sizeof("news:") - 1 + p2 - p1);
  Strcat_charp_n(tmp, "news:", sizeof("news:") - 1);
  Strcat_charp_n(tmp, p1, p2 - p1);
  return registerHref(buf, hseq, tmp->ptr, NULL, NO_REFERER, line, pos);
}
#endif				/* USE_NNTP */

static Anchor *
_put_anchor_all(Buffer *buf, int hseq, char *p1, char *p2, int line, int pos)
{
  return registerHref(buf, hseq, allocStr(p1, p2 - p1), NULL, NO_REFERER, line, pos);
}

#define FIRST_MARKER_SIZE 30

static HmarkerList *
newHmarker(HmarkerList *ml, int line, int pos, int seq)
{
  if (!ml) {
    ml = New(HmarkerList);
    ml->marks = NULL;
    ml->nmark = 0;
    ml->markmax = 0;
    ml->prevhseq = ml->firstseq = ml->lastseq = -1;
  }

  if (!ml->markmax) {
    ml->markmax = FIRST_MARKER_SIZE;
    ml->marks = NewAtom_N(BufferPointLink, ml->markmax);
    bzero(ml->marks, sizeof(BufferPointLink) * ml->markmax);
  }

  if (seq >= ml->nmark) {
    ml->nmark = seq + 1;

    if (ml->nmark >= ml->markmax) {
      ml->markmax = ml->nmark * 2;
      ml->marks = New_Reuse(BufferPointLink, ml->marks, ml->markmax);
    }
  }

  ml->marks[seq].po.line = line;
  ml->marks[seq].po.pos = pos;
  ml->marks[seq].origin = ml->marks[seq].corner = ml->marks[seq].po;
  return ml;
}

static HmarkerList *
insHmarker(HmarkerList *ml, int line, int pos, int seq, int prev)
{
  int next;

  if (!ml || prev >= ml->nmark)
    prev = -1;

  ml = newHmarker(ml, line, pos, seq);

  if (prev < 0) {
    next = ml->firstseq;
    ml->firstseq = seq;
  }
  else {
    next = ml->marks[prev].next;
    ml->marks[prev].next = seq;
  }

  if (next < 0)
    ml->lastseq = seq;
  else
    ml->marks[next].prev = seq;

  ml->marks[seq].prev = prev;
  ml->marks[seq].next = next;
  return ml;
}

static int
reseq_anchor_internal(btri_desc_t *desc, void **arg1, void *arg2)
{
  Anchor *a, *a1;
  Buffer *buf;
  int nmark;

  a = *arg1;
  buf = arg2;
  nmark = buf->hmarklist ? buf->hmarklist->nmark : 0;

  if (a->hseq >= nmark) {
    Href *href;
    int j;

    href = a->link->href;
    a1 = closest_prev_anchor(buf->href, NULL, a->start.pos, a->start.line);
    a1 = closest_prev_anchor(buf->formitem, a1, a->start.pos, a->start.line);
    buf->hmarklist = insHmarker(buf->hmarklist, a->start.line, a->start.pos, a->hseq, a1 ? a1->hseq : -1);

    if (a->start.line < a->end.line) {
      int i, epos;
      Line *l;

      i = a->end.line;
      a->end.line = j = a->start.line;

      for (l = buf->firstLine ; l ; l = l->next)
	if (l->linenumber == j) {
	  epos = a->end.pos;
	  a->end.pos = l->len;

	  while ((l = l->next)) {
	    ++j;
	    a1 = registerHref(buf, a->hseq, href->url, href->target, href->referer, j, 0);

	    if (j == i) {
	      a1->end.pos = epos;
	      break;
	    }
	    else
	      a1->end.pos = l->len;
	  }

	  break;
	}
    }
  }

  return 1;
}

static void
reseq_anchor(Buffer *buf)
{
  if (!buf->href)
    return;

  btri_map(&btri_string_tab_desc, buf->href->anchors, reseq_anchor_internal, buf);
}

static int
reAnchorPos(Buffer *buf, int nmark_new, Line *match_beg, Line **p_match_end, char *p1, char **p_p2,
	    Anchor *(*anchorproc)(Buffer *, int, char *, char *, int, int))
{
  Line *match_end, *l;
  char *p2;
  Str url;
  int spos, epos, i, j;
  Anchor *a;

  match_end = *p_match_end;
  p2 = *p_p2;
  spos = p1 - match_beg->lineBuf;

  if ((epos = p2 - match_end->lineBuf) < 0 || epos > match_end->len + 1) {
    match_end = match_end->prev;
    epos = p2 - match_end->lineBuf;
  }
  else if (!epos && match_end != match_beg) {
    do {
      match_end = match_end->prev;
      epos = match_end->len;
    } while (!epos && match_end != match_beg);

    p2 = match_end->lineBuf + epos;
  }

  url = match_beg == match_end ? NULL : Strnew();

  for (l = match_beg, i = spos ;; i = 0, l = l->next) {
    for (j = (l == match_end ? epos : l->len) ; i < j ; ++i)
      if (l->propBuf[i] & (PE_ANCHOR | PE_FORM))
	goto _next;
      else if (url)
	Strcat_char(url, l->lineBuf[i]);

    if (l == match_end)
      break;
  }

  if (url)
    a = anchorproc(buf, nmark_new++, url->ptr, &url->ptr[url->length], match_beg->linenumber, spos);
  else
    a = anchorproc(buf, nmark_new++, &match_beg->lineBuf[spos], &match_beg->lineBuf[epos], match_beg->linenumber, spos);

  a->end.line = match_end->linenumber;
  a->end.pos = epos;

  for (l = match_beg, i = spos ;; i = 0, l = l->next) {
    for (j = l == match_end ? epos : l->len ; i < j ; ++i)
      l->propBuf[i] |= PE_ANCHOR;

    if (l == match_end)
      break;
  }
_next:
  *p_match_end = match_end;
  *p_p2 = p2;
  return nmark_new;
}

void
reAnchorWord(Buffer *buf, Line *sl, int spos, Line *el, int epos)
{
  char *p;
  int nmark, nmark_new;

  p = &el->lineBuf[epos];
  nmark = buf->hmarklist ? buf->hmarklist->nmark : 0;
  nmark_new = reAnchorPos(buf, nmark, sl, &el, &sl->lineBuf[spos], &p, _put_anchor_all);

  if (nmark_new > nmark)
    reseq_anchor(buf);
}

/* search regexp and register them as anchors */
/* returns error message if any               */
static char *
reAnchorAny(Buffer *buf, char *re, Anchor *(*anchorproc)(Buffer *, int, char *, char *, int, int))
{
  Line *l, *match_beg, *match_end;
  char *error, *p, *ep, *mb, *p1, *p2;
  int pos, i, nmark, nmark_new;
  int how, res;
#ifdef MANY_CHARSET
  int e;
  mb_wchar_t wc;
#endif
  BufferView *v;

  if (!re || !*re || !(l = MarkAllPages ? buf->firstLine : buf->topLine) || !(v = buf->view))
    return NULL;

  if ((error = regexCompile(re, 1)))
    return error;

  nmark = nmark_new = buf->hmarklist ? buf->hmarklist->nmark : 0;
  how = RE_FLAG_BOF;
  pos = 0;

  for (;; how &= ~RE_FLAG_REUSE) {
    for (match_beg = match_end = l, i = pos, mb = NULL ;; how |= RE_FLAG_REUSE, how &= ~RE_FLAG_BOF) {
      if (l->next && !l->next->real_linenumber)
	how &= ~RE_FLAG_EOL;
      else
	how |= RE_FLAG_EOL;

      p = l->lineBuf + i;
      ep = l->lineBuf + l->len;
      res = regexSearch(&p, &ep, how | (l->next ? 0 : RE_FLAG_EOF));

      if (!mb)
	mb = p;

      if (!res || ep < l->lineBuf + l->len || !l->next)
	break;

      match_end = l = l->next;
      i = 0;
    }

    matchedPosition(&p1, &p2);

    if (p1 && p2) {
      nmark_new = reAnchorPos(buf, nmark_new, match_beg, &match_end, p1, &p2, anchorproc);
      l = match_end;
      pos = p2 - l->lineBuf;
    }
    else {
      l = match_beg;

      if ((pos = mb - l->lineBuf) < l->len) {
#ifdef MANY_CHARSET
	e = mb_mem_to_wchar_internal(&l->lineBuf[pos], l->len - pos, wc);
	pos += e > 0 ? e : 1;
#else
	++pos;
#ifdef JP_CHARSET
	if (l->propBuf[pos] & PC_KANJI2)
	  ++pos;
#endif
#endif
      }

      if (pos >= l->len) {
	if (!(l = l->next))
	  break;

	pos = 0;
      }
    }

    if (!MarkAllPages && l->linenumber - buf->topLine->linenumber > v->height)
      break;
  }

  if (nmark_new > nmark)
    reseq_anchor(buf);

  return NULL;
}

char *
reAnchor(Buffer * buf, char *re)
{
    return reAnchorAny(buf, re, _put_anchor_all);
}

#ifdef USE_NNTP
char *
reAnchorNews(Buffer * buf, char *re)
{
    return reAnchorAny(buf, re, _put_anchor_news);
}
#endif				/* USE_NNTP */

HmarkerList *
putHmarker(HmarkerList *ml, int line, int pos, int seq)
{
  int last;

  ml = newHmarker(ml, line, pos, seq);

  if ((last = ml->lastseq) < 0)
    ml->firstseq = seq;
  else
    ml->marks[last].next = seq;

  ml->marks[seq].prev = last;
  ml->marks[seq].next = -1;
  ml->lastseq = seq;
  return ml;
}

void
reseqHmarker(HmarkerList *ml)
{
  if (ml) {
    int seq;

    for (seq = 0 ; seq < ml->nmark ; ++seq) {
      ml->marks[seq].prev = seq - 1;
      ml->marks[seq].next = seq + 1;
    }

    if (seq > 0)
      ml->firstseq = 0;
    else
      ml->firstseq = -1;

    if ((ml->lastseq = --seq) >= 0)
      ml->marks[seq].next = -1;
  }
}

static int
closest_next_or_prev_anchor_internal(btri_desc_t *desc, void **arg1, void *arg2)
{
  Anchor *a = *arg1;

  if (a->hseq < 0)
    return 0;
  else {
    *(Anchor **)arg2 = a;
    return 1;
  }
}

Anchor *
closest_next_anchor(AnchorList *a, Anchor *an, int x, int y)
{
  if (a) {
    AnchorKey key;
    btri_key_cursor_t kcur;
    Anchor *temp = NULL;

    makeAnchorKey(y, x, &key);
    kcur.base = key.s;
    kcur.n = sizeof(key.s) * CHAR_BIT;
    btri_map_min_larger(&btri_string_tab_desc, 0, &kcur, a->anchors, closest_next_or_prev_anchor_internal, (void *)&temp);

    if (!an || (temp && bpcmp(temp->start, an->start) < 0))
      an = temp;
  }

  return an;
}

Anchor *
closest_prev_anchor(AnchorList *a, Anchor *an, int x, int y)
{
  if (a) {
    AnchorKey key;
    btri_key_cursor_t kcur;
    Anchor *temp = NULL;

    makeAnchorKey(y, x, &key);
    kcur.base = key.s;
    kcur.n = sizeof(key.s) * CHAR_BIT;
    btri_map_max_smaller(&btri_string_tab_desc, 0, &kcur, a->anchors, closest_next_or_prev_anchor_internal, (void *)&temp);
  
    if (!an || (temp && bpcmp(temp->start, an->start) > 0))
      an = temp;
  }

  return an;
}

#ifdef USE_IMAGE
void
addMultirowsImage1(Buffer *buf, Anchor *a_img)
{
  int j, k, col, ecol, pos, epos;
  Image *img;
  Anchor *a_href, *a_form, *a;
  Href *href;
  FormItemList *fi;
  Line *l, *ls;

  img = a_img->link->img;

  if (a_img->hseq < 0 || !img || img->rows <= 1)
    return;

  for (l = buf->lastLine; l != NULL; l = l->prev)
    if (l->linenumber == img->y)
      break;

  if (!l)
    return;

  ls = l;

  do {
    if (ls->linenumber == a_img->start.line)
      break;
  } while ((ls = ls->next));

  if (!ls)
    return;

  if ((a_href = retrieveAnchor(buf->href, a_img->start.line, a_img->start.pos)))
    href = a_href->link->href;
  else
    href = NULL;

  if ((a_form = retrieveAnchor(buf->formitem, a_img->start.line, a_img->start.pos)))
    fi = a_form->link->fi;
  else
    fi = NULL;

  col = COLPOS(ls, a_img->start.pos);
  ecol = COLPOS(ls, a_img->end.pos);

  for (j = 0 ; l && j < img->rows ; l = l->next, j++) {
    if (l->linenumber == a_img->start.line)
      continue;

    pos = columnPos(l, col);
    epos = columnPos(l, ecol);

    a = putImageAnchor(buf, img, l->linenumber, pos);
    a->end.pos = epos;

    for (k = pos ; k < a->end.pos ; k++)
      l->propBuf[k] |= PE_IMAGE;

    if (href) {
      a = putHrefAnchor(buf, href, l->linenumber, pos);
      a->end.pos = pos + ecol - col;

      for (k = pos; k < a->end.pos; k++)
	l->propBuf[k] |= PE_ANCHOR;
    }

    if (fi) {
      a = putFormAnchor(buf, fi, l->linenumber, pos);
      a->end.pos = pos + ecol - col;

      for (k = pos; k < a->end.pos; k++)
	l->propBuf[k] |= PE_FORM;
    }
  }
}
#endif

void
addMultirowsForm1(Buffer *buf, Anchor *a_found)
{
  Anchor *a;
  int j, k, col, ecol, pos;
  FormItemList *fi;
  Line *l, *ls;

  fi = a_found->link->fi;

  if (a_found->hseq < 0 || fi->rows <= 1)
    return;

  for (l = buf->lastLine ; l ; l = l->prev)
    if (l->linenumber == fi->y)
      break;

  if (!l)
    return;

  ls = l;

  do {
    if (ls->linenumber == a_found->start.line)
      break;
  } while ((ls = ls->next));

  if (!ls)
    return;

  col = COLPOS(ls, a_found->start.pos);
  ecol = COLPOS(ls, a_found->end.pos);

  for (j = 0 ; l && j < fi->rows ; l = l->next, j++) {
    pos = columnPos(l, col);

    if (j == 0) {
      buf->hmarklist->marks[a_found->hseq].po.line = l->linenumber;
      buf->hmarklist->marks[a_found->hseq].po.pos = pos;
    }

    if (a_found->start.line == l->linenumber)
      continue;

    a = putFormAnchor(buf, fi, l->linenumber, pos);
    a->end.pos = pos + ecol - col;
    l->lineBuf[pos - 1] = '[';
    l->lineBuf[a->end.pos] = ']';

    for (k = pos; k < a->end.pos; k++)
      l->propBuf[k] |= PE_FORM;
  }
}
