// 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* pFunc, const char* pMsg, ...)
   {
      pFunc;
      va_list args;
      va_start(args, pMsg);

      char buf[512];
#ifdef _MSC_VER
      _vsnprintf_s(buf, sizeof(buf), pMsg, args);
#else
      vsnprintf(buf, sizeof(buf), pMsg, args);
#endif

      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