/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : CSIM.c                                 */
/*                                                 */
/* Purpose: Client-side image map support.         */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 13-Nov-97: Created.                    */
/***************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "swis.h"

#include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */

#include "wimp.h"
#include "wimplib.h"
#include "event.h"

#include "svcprint.h"
#include "Global.h"
#include "FromROSLib.h"
#include "MiscDefs.h"
#include "Utils.h"

#include "Fetch.h"
#include "Redraw.h"

#include "CSIM.h"

/* Local structures */

typedef struct csim_polygon_vertex
{
  int x;
  int y;
}
csim_polygon_vertex;

/*************************************************/
/* csim_within()                                 */
/*                                               */
/* Given a token representing an A tag with      */
/* associated AREA-like information and x and y  */
/* coordinates in the same coordinate space as   */
/* the A tag, works out if those coordinates lie */
/* within the area the A tag describes.          */
/*                                               */
/* Parameters: Pointer to the token;             */
/*                                               */
/*             X coordinate;                     */
/*                                               */
/*             Y coordinate.                     */
/*                                               */
/* Returns:    1 if the coordinates lie within   */
/*             the area described by the token,  */
/*             2 if they lie within it because   */
/*             the item is the DEFAULT area, and */
/*             0 if the coordinates do not lie   */
/*             within the area.                  */
/*                                               */
/* Assumes:    The token represents an anchor    */
/*             with associated coordinate info.  */
/*************************************************/

int csim_within(HStream * t, int relx, int rely)
{
  areashape type;
  int       n;

  if (!t->coords) return 0;

  /* Find the shape type and number of coordinates */

  type = (areashape) t->coords[0];
  n    = t->coords[1];

  /* Deal with each type */

  switch (type)
  {
    /* Rectangles must have four coordinates */

    case areashape_RECT:
    {
      if (n != 4) return 0;

      return (
               relx >= t->coords[2] && /* xmin */
               rely >= t->coords[3] && /* ymin */
               relx <= t->coords[4] && /* xmax */
               rely <= t->coords[5]    /* ymax */
             );
    }
    break;

    /* Circles need 3 coordinates (x, y, r) */

    case areashape_CIRCLE:
    {
      if (n != 3) return 0;

      relx -= t->coords[2];
      rely -= t->coords[3];

      return (
               relx         * relx         +
               rely         * rely         <
               t->coords[4] * t->coords[4]
             );
    }
    break;

    /* A polygon needs at least 3 coordinate pairs, and we must */
    /* have an even number of them.                             */

    case areashape_POLY:
    {
      /* Check we've the right number of coordinates */

      if (n < 6 || n % 2) return 0;
      else
      {
        /* Adapted from clientmaps.c (S.Brodie), which was in */
        /* turn from imagemap.c, (Rob McCool).                */

        int                               crossings = 0;
        int                               xflag0;
        int                               y;

        const csim_polygon_vertex * const v0        = (csim_polygon_vertex *) (&(t->coords[2]));
        const csim_polygon_vertex * const vlast     = (csim_polygon_vertex *) (&(t->coords[n])); /* (Since coord 1 starts at array index 2) */

        const int                 *       stop      = ((int *) v0) + n;
        const int                 *       p;

        y = vlast->y;
        p = &v0->y;

        /* Does the given y coordinate lie between the first and last vertex y positions? */

        if ((y >= rely) != (*p >= rely))
        {
          /* Set xflag0 if the given x coordinate is left of the last vertex x position */

          xflag0 = vlast->x >= relx;

          /* Compare whether this against whether or not the given x coordinate is left */
          /* of the first vertex x position.                                            */

          if (xflag0 == (v0->x >= relx))
          {
            /* If xflag0 is set here, the given x coordinate is left of both the first */
            /* and last vertex x positions - increment 'crossings'. Else it is right   */
            /* of them both.                                                           */

            if (xflag0) ++crossings;
          }
          else
          {
            /* If we reach here, the given x coordinate is neither wholly to the left */
            /* of the first and last vertex x positions, or to the right; so it must  */
            /* lie between them.                                                      */

            crossings += (
                           vlast->x -
                           (
                             ((y - rely) * (v0->x - vlast->x)) / (*p - y)
                           )
                         )
                         >= relx;
          }
        }

        /* Start with y pointing to the first vertex y coordinate, */
        /* then increment p to point to the next y coordinate.     */
        /* We can thus go round the polygon comparing coordinate   */
        /* pairs (sides of the polygon) against the given coords.  */

        for (y = *p, p += 2; p < stop; y = *p, p += 2)
        {
          if (y >= rely)
          {
            /* If the given y coordinate lies left of the current vertex */
            /* y coordinate, skip 'p' along until it points to another   */
            /* y coordinate right of the given one.                      */

            while (p < stop && (*p >= rely)) p += 2;
          }
          else
          {
            /* ...Or if the given y coordinate lies to the right of the */
            /* current vertex, skip 'p' long until it points to a y     */
            /* coordinate right of the given one.                       */

            while (p < stop && (*p < rely)) p += 2;
          }

          /* If p is no longer in the vertex array, then there is no vertex */
          /* to the right of the given y coordinate and the given point     */
          /* cannot lie in the polygon.                                     */

          if (p >= stop) break;

          /* So 'p' points to the second y coordinate of a pair of vertices; */
          /* 'p - 3' thus points to the first x coordinate, and so-on. So,   */
          /* set xflag0 if the first vertex x coordinate is right of the     */
          /* given x coordinate.                                             */

          xflag0 = (*(p - 3) >= relx);

          /* Compare this against whether or not the second vertex x coordinate */
          /* is in the same position relative to (i.e. left or right of) the    */
          /* given x coordinate.                                                */

          if (xflag0 == (*(p - 1) >= relx))
          {
            /* If xflag0 is set, then, then the given x coordinate lies to the left of */
            /* both of the vertices (increment crossings). Else it's to the right.     */

            if (xflag0) ++crossings;
          }
          else
          {
            /* If we reach here, the given x coordinate is between the two vertices' */
            /* x coordinates.                                                        */

            crossings += (
                           *(p - 3) -
                           (
                             ((*(p - 2) - rely) * (*(p - 1) - *(p - 3))) / (*p - *(p - 2))
                           )
                         )
                         >= relx;
          }
        }

        return (crossings & 1);
      }
    }
    break;

    /* For DEFAULT areas, the coordinates always lie in it... */

    case areashape_DEFAULT:
    {
      return 2;
    }
    break;
  }

  return 0;
}

/*************************************************/
/* csim_return_info                              */
/*                                               */
/* The high level interface. Given a browser,    */
/* token and relative coordinates from the top   */
/* left of the image the token represents,       */
/* returns a URL and target - the token must     */
/* represent an Object, which is an image with   */
/* image map details attached.                   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the map;              */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing an Object, which is  */
/*             an image with an attached client  */
/*             side map;                         */
/*                                               */
/*             X offset from the top left of the */
/*             image in pixels to look up in the */
/*             map;                              */
/*                                               */
/*             Similarly, Y offset (positive     */
/*             downwards);                       */
/*                                               */
/*             Pointer to a char *, in which a   */
/*             pointer to the URL to fetch will  */
/*             be placed;                        */
/*                                               */
/*             Pointer to a char *, in which a   */
/*             pointer to a target window name   */
/*             for the fetch will be placed;     */
/*                                               */
/*             Pointer to a char *, in which a   */
/*             pointer to alternative text       */
/*             to display in place of the        */
/*             URL will be placed.               */
/*                                               */
/* Returns:    See parameters; note that any of  */
/*             the values returned may be NULL.  */
/*             If the URL is NULL, then the      */
/*             token didn't represent an image   */
/*             map or the coordinates didn't lie */
/*             within a region the map defines.  */
/*                                               */
/* Assumes:    Any of the char * pointers may be */
/*             NULL.                             */
/*************************************************/

void csim_return_info(browser_data * b, HStream * t, int relx, int rely,
                      char ** url, char ** target, char ** alt)
{
  HStream * item;
  char    * defurl    = NULL;
  char    * deftarget = NULL;
  char    * defalt    = NULL;

  /* Start with everything pointing nowhere */

  if (url)    *url    = NULL;
  if (target) *target = NULL;
  if (alt)    *alt    = NULL;

  /* Initial sanity checks */

  if (!b || !t) return;

  if (!(t->type & TYPE_ISCLIENTMAP)) return;

  /* Now start looking at what area the coordinates are over */

  if (ISOBJECT(t) & HtmlOBJECTshapes(t)) item = HtmlOBJECTstream(t);
  else                                   item = HtmlOBJECTmapstream(t);

  if (!item) return;

  /* Go through the Object's token stream */

  while (item)
  {
    /* Is this an A tag with associated URL? */

    if (ISLINK(item))
    {
      int result = csim_within(item, relx, rely);

      if (result == 1)
      {
        /* Found it (remember, if areas overlap, we */
        /* are supposed to take the first).         */

        if (url)    *url    = item->anchor;
        if (target) *target = item->target;
        if (alt)    *alt    = item->text;

        return;
      }

      if (result == 2)
      {
        /* Found the default details, to use if all else fails */

        defurl    = item->anchor;
        deftarget = item->target;
        defalt    = item->text;
      }
    }

    /* Try the next HStream structure */

    item = item->next;
  }

  /* Finished - return whatever defaults were specified in */
  /* the map, or NULL if there were none.                  */

  if (url)    *url    = defurl;
  if (target) *target = deftarget;
  if (alt)    *alt    = defalt;

  return;
}

/*************************************************/
/* csim_highlight_region()                       */
/*                                               */
/* Draw a border around whatever region the      */
/* map_x and map_y fields of a browser_data      */
/* structure indicate.                           */
/*                                               */
/* Parameters: Pointer to the browser_data       */
/*             struct holding info on the token  */
/*             representing the map and the      */
/*             offset x and y coordinates        */
/*             indicating what part of the map   */
/*             is to be highlighted;             */
/*                                               */
/*             Colour to use for the region      */
/*             highlight (see redraw_set_colour  */
/*             in Redraw.c for the format of     */
/*             the colour number);               */
/*                                               */
/*             Left hand x coordinate (screen    */
/*             coords) that the image is plotted */
/*             at;                               */
/*                                               */
/*             Similarly the top y coordinate    */
/*             that the image is plotted at.     */
/*************************************************/

void csim_highlight_region(browser_data * b, int colour, int left_x, int top_y)
{
  HStream * t;
  HStream * item;

  /* Initial sanity checks */

  if (!b) return;

  t = b->pointer_over;

  if (!t || !(t->type & TYPE_ISCLIENTMAP)) return;

  /* Now start looking at what area the coordinates are over */

  if (ISOBJECT(t) & HtmlOBJECTshapes(t)) item = HtmlOBJECTstream(t);
  else                                   item = HtmlOBJECTmapstream(t);

  if (!item) return;

  /* Go through the Object's token stream */

  while (item)
  {
    /* Is this an A tag with associated URL? */

    if (ISLINK(item))
    {
      if (csim_within(item, b->map_x, b->map_y))
      {
        /* Found it (remember, if areas overlap, we */
        /* are supposed to take the first).         */

        areashape type;
        int       n;

        if (!item->coords) return;

        /* Find the shape type and number of coordinates */

        type = (areashape) item->coords[0];
        n    = item->coords[1];

        /* Deal with each type */

        switch (type)
        {
          case areashape_RECT:
          {
            BBox rect;

            if (n != 4) return;

            rect.xmin = left_x + item->coords[2] * 2; /* 1 'web pixel' = 2 OS units */
            rect.ymin = top_y  - item->coords[5] * 2;
            rect.xmax = left_x + item->coords[4] * 2;
            rect.ymax = top_y  - item->coords[3] * 2;

            redraw_border_around_box(&rect, colour);
          }
          break;

          case areashape_CIRCLE:
          {
            if (n != 3) return;

            redraw_set_colour(colour);

            /* Draw the main circle around the outside     */
            /* of the area, then 'blur' around a 1 OS unit */
            /* centre point to get a thicker outline.      */

            bbc_circle(left_x + item->coords[2] * 2, /* 1 'web pixel' = 2 OS units */
                       top_y  - item->coords[3] * 2,
                       item->coords[4] * 2 + 2);

            bbc_circle(left_x + item->coords[2] * 2 + 1,
                       top_y  - item->coords[3] * 2 + 1,
                       item->coords[4] * 2 + 2);

            bbc_circle(left_x + item->coords[2] * 2 + 1,
                       top_y  - item->coords[3] * 2 - 1,
                       item->coords[4] * 2 + 2);

            bbc_circle(left_x + item->coords[2] * 2 - 1,
                       top_y  - item->coords[3] * 2 + 1,
                       item->coords[4] * 2 + 2);

            bbc_circle(left_x + item->coords[2] * 2 - 1,
                       top_y  - item->coords[3] * 2 - 1,
                       item->coords[4] * 2 + 2);
          }
          break;

          case areashape_POLY:
          {
            int coord;
            int vx, vy;
            int lx, ly;

            if (n < 6 || n % 2) return;

            redraw_set_colour(colour);

            lx = left_x + item->coords[2] * 2; /* 1 'web pixel' = 2 OS units */
            ly = top_y  - item->coords[3] * 2;

            for (
                  coord = 2,
                  vx    = left_x + item->coords[coord + 2] * 2,
                  vy    = top_y  - item->coords[coord + 3] * 2;

                  coord < n;

                  coord += 2,
                  vx    = left_x + item->coords[coord + 2] * 2,
                  vy    = top_y  - item->coords[coord + 3] * 2
                )
            {
              /* Draw the outline at the exact border coordinates  */
              /* and 'blur' 1 OS unit in NE/NW/SE/SW directions to */
              /* make the border thicker.                          */

              bbc_move(lx,     ly);
              bbc_draw(vx,     vy);
              bbc_move(lx + 1, ly + 1);
              bbc_draw(vx + 1, vy + 1);
              bbc_move(lx + 1, ly - 1);
              bbc_draw(vx + 1, vy - 1);
              bbc_move(lx - 1, ly + 1);
              bbc_draw(vx - 1, vy + 1);
              bbc_move(lx - 1, ly - 1);
              bbc_draw(vx - 1, vy - 1);

              lx = vx, ly = vy;
            }
          }
          break;
        }

        /* Finished */

        return;
      }
    }

    /* Try the next HStream structure */

    item = item->next;
  }

  /* Finished */

  return;
}