// File: crn_arealist.cpp - 2D shape algebra (currently unused)
// See Copyright Notice and license at the end of inc/crnlib.h
// Ported from the PowerView DOS image viewer, a product I wrote back in 1993. Not currently used in the open source release of crnlib.
#include "crn_core.h"
#include "crn_arealist.h"

#define RECT_DEBUG

namespace crnlib {

static void area_fatal_error(const char*, const char* pMsg, ...) {
  va_list args;
  va_start(args, pMsg);

  char buf[512];
  crnlib_vsnprintf(buf, sizeof(buf), pMsg, args);

  va_end(args);

  CRNLIB_FAIL(buf);
}

static Area* delete_area(Area_List* Plist, Area* Parea) {
  Area *p, *q;

#ifdef RECT_DEBUG
  if ((Parea == Plist->Phead) || (Parea == Plist->Ptail))
    area_fatal_error("delete_area", "tried to remove head or tail");
#endif

  p = Parea->Pprev;
  q = Parea->Pnext;
  p->Pnext = q;
  q->Pprev = p;

  Parea->Pnext = Plist->Pfree;
  Parea->Pprev = NULL;
  Plist->Pfree = Parea;

  return (q);
}

static Area* alloc_area(Area_List* Plist) {
  Area* p = Plist->Pfree;

  if (p == NULL) {
    if (Plist->next_free == Plist->total_areas)
      area_fatal_error("alloc_area", "Out of areas!");

    p = Plist->Phead + Plist->next_free;
    Plist->next_free++;
  } else
    Plist->Pfree = p->Pnext;

  return (p);
}

static Area* insert_area_before(Area_List* Plist, Area* Parea,
                                int x1, int y1, int x2, int y2) {
  Area *p, *Pnew_area = alloc_area(Plist);

  p = Parea->Pprev;

  p->Pnext = Pnew_area;

  Pnew_area->Pprev = p;
  Pnew_area->Pnext = Parea;

  Parea->Pprev = Pnew_area;

  Pnew_area->x1 = x1;
  Pnew_area->y1 = y1;
  Pnew_area->x2 = x2;
  Pnew_area->y2 = y2;

  return (Pnew_area);
}

static Area* insert_area_after(Area_List* Plist, Area* Parea,
                               int x1, int y1, int x2, int y2) {
  Area *p, *Pnew_area = alloc_area(Plist);

  p = Parea->Pnext;

  p->Pprev = Pnew_area;

  Pnew_area->Pnext = p;
  Pnew_area->Pprev = Parea;

  Parea->Pnext = Pnew_area;

  Pnew_area->x1 = x1;
  Pnew_area->y1 = y1;
  Pnew_area->x2 = x2;
  Pnew_area->y2 = y2;

  return (Pnew_area);
}

void Area_List_deinit(Area_List* Pobj_base) {
  Area_List* Plist = (Area_List*)Pobj_base;

  if (!Plist)
    return;

  if (Plist->Phead) {
    crnlib_free(Plist->Phead);
    Plist->Phead = NULL;
  }

  crnlib_free(Plist);
}

Area_List* Area_List_init(int max_areas) {
  Area_List* Plist = (Area_List*)crnlib_calloc(1, sizeof(Area_List));

  Plist->total_areas = max_areas + 2;

  Plist->Phead = (Area*)crnlib_calloc(max_areas + 2, sizeof(Area));
  Plist->Ptail = Plist->Phead + 1;

  Plist->Phead->Pprev = NULL;
  Plist->Phead->Pnext = Plist->Ptail;

  Plist->Ptail->Pprev = Plist->Phead;
  Plist->Ptail->Pnext = NULL;

  Plist->Pfree = NULL;
  Plist->next_free = 2;

  return (Plist);
}

void Area_List_print(Area_List* Plist) {
  Area* Parea = Plist->Phead->Pnext;

  while (Parea != Plist->Ptail) {
    printf("%04i %04i : %04i %04i\n", Parea->x1, Parea->y1, Parea->x2, Parea->y2);

    Parea = Parea->Pnext;
  }
}

Area_List* Area_List_dup_new(Area_List* Plist,
                             int x_ofs, int y_ofs) {
  int i;
  Area_List* Pnew_list = (Area_List*)crnlib_calloc(1, sizeof(Area_List));

  Pnew_list->total_areas = Plist->total_areas;

  Pnew_list->Phead = (Area*)crnlib_malloc(sizeof(Area) * Plist->total_areas);
  Pnew_list->Ptail = Pnew_list->Phead + 1;

  Pnew_list->Pfree = (Plist->Pfree) ? ((Plist->Pfree - Plist->Phead) + Pnew_list->Phead) : NULL;

  Pnew_list->next_free = Plist->next_free;

  memcpy(Pnew_list->Phead, Plist->Phead, sizeof(Area) * Plist->total_areas);

  for (i = 0; i < Plist->total_areas; i++) {
    Pnew_list->Phead[i].Pnext = (Plist->Phead[i].Pnext == NULL) ? NULL : (Plist->Phead[i].Pnext - Plist->Phead) + Pnew_list->Phead;
    Pnew_list->Phead[i].Pprev = (Plist->Phead[i].Pprev == NULL) ? NULL : (Plist->Phead[i].Pprev - Plist->Phead) + Pnew_list->Phead;

    Pnew_list->Phead[i].x1 += x_ofs;
    Pnew_list->Phead[i].y1 += y_ofs;
    Pnew_list->Phead[i].x2 += x_ofs;
    Pnew_list->Phead[i].y2 += y_ofs;
  }

  return (Pnew_list);
}

uint Area_List_get_num(Area_List* Plist) {
  uint num = 0;

  Area* Parea = Plist->Phead->Pnext;

  while (Parea != Plist->Ptail) {
    num++;

    Parea = Parea->Pnext;
  }

  return num;
}

void Area_List_dup(Area_List* Psrc_list, Area_List* Pdst_list,
                   int x_ofs, int y_ofs) {
  int i;

  if (Psrc_list->total_areas != Pdst_list->total_areas)
    area_fatal_error("Area_List_dup", "Src and Dst total_areas must be equal!");

  Pdst_list->Pfree = (Psrc_list->Pfree) ? ((Psrc_list->Pfree - Psrc_list->Phead) + Pdst_list->Phead) : NULL;

  Pdst_list->next_free = Psrc_list->next_free;

  memcpy(Pdst_list->Phead, Psrc_list->Phead, sizeof(Area) * Psrc_list->total_areas);

  if ((x_ofs) || (y_ofs)) {
    for (i = 0; i < Psrc_list->total_areas; i++) {
      Pdst_list->Phead[i].Pnext = (Psrc_list->Phead[i].Pnext == NULL) ? NULL : (Psrc_list->Phead[i].Pnext - Psrc_list->Phead) + Pdst_list->Phead;
      Pdst_list->Phead[i].Pprev = (Psrc_list->Phead[i].Pprev == NULL) ? NULL : (Psrc_list->Phead[i].Pprev - Psrc_list->Phead) + Pdst_list->Phead;

      Pdst_list->Phead[i].x1 += x_ofs;
      Pdst_list->Phead[i].y1 += y_ofs;
      Pdst_list->Phead[i].x2 += x_ofs;
      Pdst_list->Phead[i].y2 += y_ofs;
    }
  } else {
    for (i = 0; i < Psrc_list->total_areas; i++) {
      Pdst_list->Phead[i].Pnext = (Psrc_list->Phead[i].Pnext == NULL) ? NULL : (Psrc_list->Phead[i].Pnext - Psrc_list->Phead) + Pdst_list->Phead;
      Pdst_list->Phead[i].Pprev = (Psrc_list->Phead[i].Pprev == NULL) ? NULL : (Psrc_list->Phead[i].Pprev - Psrc_list->Phead) + Pdst_list->Phead;
    }
  }
}

void Area_List_copy(
    Area_List* Psrc_list, Area_List* Pdst_list,
    int x_ofs, int y_ofs) {
  Area* Parea = Psrc_list->Phead->Pnext;

  Area_List_clear(Pdst_list);

  if ((x_ofs) || (y_ofs)) {
    Area* Pprev_area = Pdst_list->Phead;

    while (Parea != Psrc_list->Ptail) {
      //      Area *p, *Pnew_area;
      Area* Pnew_area;

      if (Pdst_list->next_free == Pdst_list->total_areas)
        area_fatal_error("Area_List_copy", "Out of areas!");

      Pnew_area = Pdst_list->Phead + Pdst_list->next_free;
      Pdst_list->next_free++;

      Pnew_area->Pprev = Pprev_area;
      Pprev_area->Pnext = Pnew_area;

      Pnew_area->x1 = Parea->x1 + x_ofs;
      Pnew_area->y1 = Parea->y1 + y_ofs;
      Pnew_area->x2 = Parea->x2 + x_ofs;
      Pnew_area->y2 = Parea->y2 + y_ofs;

      Pprev_area = Pnew_area;

      Parea = Parea->Pnext;
    }

    Pprev_area->Pnext = Pdst_list->Ptail;
  } else {
#if 0
         while (Parea != Psrc_list->Ptail)
         {
            insert_area_after(Pdst_list, Pdst_list->Phead,
               Parea->x1,
               Parea->y1,
               Parea->x2,
               Parea->y2);

            Parea = Parea->Pnext;
         }
#endif

    Area* Pprev_area = Pdst_list->Phead;

    while (Parea != Psrc_list->Ptail) {
      //      Area *p, *Pnew_area;
      Area* Pnew_area;

      if (Pdst_list->next_free == Pdst_list->total_areas)
        area_fatal_error("Area_List_copy", "Out of areas!");

      Pnew_area = Pdst_list->Phead + Pdst_list->next_free;
      Pdst_list->next_free++;

      Pnew_area->Pprev = Pprev_area;
      Pprev_area->Pnext = Pnew_area;

      Pnew_area->x1 = Parea->x1;
      Pnew_area->y1 = Parea->y1;
      Pnew_area->x2 = Parea->x2;
      Pnew_area->y2 = Parea->y2;

      Pprev_area = Pnew_area;

      Parea = Parea->Pnext;
    }

    Pprev_area->Pnext = Pdst_list->Ptail;
  }
}

void Area_List_clear(Area_List* Plist) {
  Plist->Phead->Pnext = Plist->Ptail;
  Plist->Ptail->Pprev = Plist->Phead;
  Plist->Pfree = NULL;
  Plist->next_free = 2;
}

void Area_List_set(Area_List* Plist, int x1, int y1, int x2, int y2) {
  Plist->Pfree = NULL;

  Plist->Phead[2].x1 = x1;
  Plist->Phead[2].y1 = y1;
  Plist->Phead[2].x2 = x2;
  Plist->Phead[2].y2 = y2;

  Plist->Phead[2].Pprev = Plist->Phead;
  Plist->Phead->Pnext = Plist->Phead + 2;

  Plist->Phead[2].Pnext = Plist->Ptail;
  Plist->Ptail->Pprev = Plist->Phead + 2;

  Plist->next_free = 3;
}

void Area_List_remove(Area_List* Plist,
                      int x1, int y1, int x2, int y2) {
  int l, h;
  Area* Parea = Plist->Phead->Pnext;

#ifdef RECT_DEBUG
  if ((x1 > x2) || (y1 > y2))
    area_fatal_error("area_list_remove", "invalid coords: %i %i %i %i", x1, y1, x2, y2);
#endif

  while (Parea != Plist->Ptail) {
    // Not touching
    if ((x2 < Parea->x1) || (x1 > Parea->x2) ||
        (y2 < Parea->y1) || (y1 > Parea->y2)) {
      Parea = Parea->Pnext;
      continue;
    }

    // Completely covers
    if ((x1 <= Parea->x1) && (x2 >= Parea->x2) &&
        (y1 <= Parea->y1) && (y2 >= Parea->y2)) {
      if ((x1 == Parea->x1) && (x2 == Parea->x2) &&
          (y1 == Parea->y1) && (y2 == Parea->y2)) {
        delete_area(Plist, Parea);
        return;
      }

      Parea = delete_area(Plist, Parea);

      continue;
    }

    // top
    if (y1 > Parea->y1) {
      insert_area_before(Plist, Parea,
                         Parea->x1, Parea->y1,
                         Parea->x2, y1 - 1);
    }

    // bottom
    if (y2 < Parea->y2) {
      insert_area_before(Plist, Parea,
                         Parea->x1, y2 + 1,
                         Parea->x2, Parea->y2);
    }

    l = math::maximum(y1, Parea->y1);
    h = math::minimum(y2, Parea->y2);

    // left middle
    if (x1 > Parea->x1) {
      insert_area_before(Plist, Parea,
                         Parea->x1, l,
                         x1 - 1, h);
    }

    // right middle
    if (x2 < Parea->x2) {
      insert_area_before(Plist, Parea,
                         x2 + 1, l,
                         Parea->x2, h);
    }

    // early out - we know there's nothing else to remove, as areas can
    // never overlap
    if ((x1 >= Parea->x1) && (x2 <= Parea->x2) &&
        (y1 >= Parea->y1) && (y2 <= Parea->y2)) {
      delete_area(Plist, Parea);
      return;
    }

    Parea = delete_area(Plist, Parea);
  }
}

void Area_List_insert(Area_List* Plist,
                      int x1, int y1, int x2, int y2,
                      bool combine) {
  Area* Parea = Plist->Phead->Pnext;

#ifdef RECT_DEBUG
  if ((x1 > x2) || (y1 > y2))
    area_fatal_error("Area_List_insert", "invalid coords: %i %i %i %i", x1, y1, x2, y2);
#endif

  while (Parea != Plist->Ptail) {
    // totally covers
    if ((x1 <= Parea->x1) && (x2 >= Parea->x2) &&
        (y1 <= Parea->y1) && (y2 >= Parea->y2)) {
      Parea = delete_area(Plist, Parea);
      continue;
    }

    // intersects
    if ((x2 >= Parea->x1) && (x1 <= Parea->x2) &&
        (y2 >= Parea->y1) && (y1 <= Parea->y2)) {
      int ax1, ay1, ax2, ay2;

      ax1 = Parea->x1;
      ay1 = Parea->y1;
      ax2 = Parea->x2;
      ay2 = Parea->y2;

      if (x1 < ax1)
        Area_List_insert(Plist, x1, math::maximum(y1, ay1), ax1 - 1, math::minimum(y2, ay2), combine);

      if (x2 > ax2)
        Area_List_insert(Plist, ax2 + 1, math::maximum(y1, ay1), x2, math::minimum(y2, ay2), combine);

      if (y1 < ay1)
        Area_List_insert(Plist, x1, y1, x2, ay1 - 1, combine);

      if (y2 > ay2)
        Area_List_insert(Plist, x1, ay2 + 1, x2, y2, combine);

      return;
    }

    if (combine) {
      if ((x1 == Parea->x1) && (x2 == Parea->x2)) {
        if ((y2 == Parea->y1 - 1) || (y1 == Parea->y2 + 1)) {
          delete_area(Plist, Parea);
          Area_List_insert(Plist, x1, math::minimum(y1, Parea->y1), x2, math::maximum(y2, Parea->y2), CRNLIB_TRUE);
          return;
        }
      } else if ((y1 == Parea->y1) && (y2 == Parea->y2)) {
        if ((x2 == Parea->x1 - 1) || (x1 == Parea->x2 + 1)) {
          delete_area(Plist, Parea);
          Area_List_insert(Plist, math::minimum(x1, Parea->x1), y1, math::maximum(x2, Parea->x2), y2, CRNLIB_TRUE);
          return;
        }
      }
    }

    Parea = Parea->Pnext;
  }

  insert_area_before(Plist, Parea, x1, y1, x2, y2);
}

void Area_List_intersect_area(Area_List* Plist,
                              int x1, int y1, int x2, int y2) {
  Area* Parea = Plist->Phead->Pnext;

  while (Parea != Plist->Ptail) {
    // doesn't cover
    if ((x2 < Parea->x1) || (x1 > Parea->x2) ||
        (y2 < Parea->y1) || (y1 > Parea->y2)) {
      Parea = delete_area(Plist, Parea);
      continue;
    }

    // totally covers
    if ((x1 <= Parea->x1) && (x2 >= Parea->x2) &&
        (y1 <= Parea->y1) && (y2 >= Parea->y2)) {
      Parea = Parea->Pnext;
      continue;
    }

    // Oct 21- should insert after, because deleted area will access the NEXT area!
    //    insert_area_after(Plist, Parea,
    //                      math::maximum(x1, Parea->x1),
    //                      math::maximum(y1, Parea->y1),
    //                      math::minimum(x2, Parea->x2),
    //                      math::minimum(y2, Parea->y2));

    insert_area_before(Plist, Parea,
                       math::maximum(x1, Parea->x1),
                       math::maximum(y1, Parea->y1),
                       math::minimum(x2, Parea->x2),
                       math::minimum(y2, Parea->y2));

    Parea = delete_area(Plist, Parea);
  }
}

#if 0
   void Area_List_intersect_Area_List(
                                      Area_List *Pouter_list,
                                      Area_List *Pinner_list,
                                      Area_List *Pdst_list)
   {
      Area *Parea1 = Pouter_list->Phead->Pnext;

      while (Parea1 != Pouter_list->Ptail)
      {
         Area *Parea2 = Pinner_list->Phead->Pnext;
         int x1, y1, x2, y2;

         x1 = Parea1->x1; x2 = Parea1->x2;
         y1 = Parea1->y1; y2 = Parea1->y2;

         while (Parea2 != Pinner_list->Ptail)
         {
            if ((x1 <= Parea2->x2) && (x2 >= Parea2->x1) &&
               (y1 <= Parea2->y2) && (y2 >= Parea2->y1))
            {
               insert_area_after(Pdst_list, Pdst_list->Phead,
                  math::maximum(x1, Parea2->x1),
                  math::maximum(y1, Parea2->y1),
                  math::minimum(x2, Parea2->x2),
                  math::minimum(y2, Parea2->y2));
            }

            Parea2 = Parea2->Pnext;
         }

         Parea1 = Parea1->Pnext;
      }
   }
#endif

#if 1
void Area_List_intersect_Area_List(Area_List* Pouter_list,
                                   Area_List* Pinner_list,
                                   Area_List* Pdst_list) {
  Area* Parea1 = Pouter_list->Phead->Pnext;

  while (Parea1 != Pouter_list->Ptail) {
    Area* Parea2 = Pinner_list->Phead->Pnext;
    int x1, y1, x2, y2;

    x1 = Parea1->x1;
    x2 = Parea1->x2;
    y1 = Parea1->y1;
    y2 = Parea1->y2;

    while (Parea2 != Pinner_list->Ptail) {
      if ((x1 <= Parea2->x2) && (x2 >= Parea2->x1) &&
          (y1 <= Parea2->y2) && (y2 >= Parea2->y1)) {
        int nx1, ny1, nx2, ny2;

        nx1 = math::maximum(x1, Parea2->x1);
        ny1 = math::maximum(y1, Parea2->y1);
        nx2 = math::minimum(x2, Parea2->x2);
        ny2 = math::minimum(y2, Parea2->y2);

        if (Pdst_list->Phead->Pnext == Pdst_list->Ptail) {
          insert_area_after(Pdst_list, Pdst_list->Phead,
                            nx1, ny1, nx2, ny2);
        } else {
          Area_Ptr Ptemp = Pdst_list->Phead->Pnext;
          if ((Ptemp->x1 == nx1) && (Ptemp->x2 == nx2)) {
            if (Ptemp->y1 == (ny2 + 1)) {
              Ptemp->y1 = ny1;
              goto next;
            } else if (Ptemp->y2 == (ny1 - 1)) {
              Ptemp->y2 = ny2;
              goto next;
            }
          } else if ((Ptemp->y1 == ny1) && (Ptemp->y2 == ny2)) {
            if (Ptemp->x1 == (nx2 + 1)) {
              Ptemp->x1 = nx1;
              goto next;
            } else if (Ptemp->x2 == (nx1 - 1)) {
              Ptemp->x2 = nx2;
              goto next;
            }
          }

          insert_area_after(Pdst_list, Pdst_list->Phead,
                            nx1, ny1, nx2, ny2);
        }
      }

    next:

      Parea2 = Parea2->Pnext;
    }

    Parea1 = Parea1->Pnext;
  }
}
#endif

Area_List_Ptr Area_List_create_optimal(Area_List_Ptr Plist) {
  Area_Ptr Parea = Plist->Phead->Pnext, Parea_after;
  int num = 2;
  Area_List_Ptr Pnew_list;

  while (Parea != Plist->Ptail) {
    num++;
    Parea = Parea->Pnext;
  }

  Pnew_list = Area_List_init(num);

  Parea = Plist->Phead->Pnext;

  Parea_after = Pnew_list->Phead;

  while (Parea != Plist->Ptail) {
    Parea_after = insert_area_after(Pnew_list, Parea_after,
                                    Parea->x1, Parea->y1,
                                    Parea->x2, Parea->y2);

    Parea = Parea->Pnext;
  }

  return (Pnew_list);
}

}  // namespace crnlib
