/*
 * Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
 *
 * (c) Copyright 1996 - 2001 Gary Henderson (gary.henderson@ntlworld.com) and
 *                           Jerremy Koot (jkoot@snes9x.com)
 *
 * Super FX C emulator code
 * (c) Copyright 1997 - 1999 Ivar (ivar@snes9x.com) and
 *                           Gary Henderson.
 * Super FX assembler emulator code (c) Copyright 1998 zsKnight and _Demo_.
 *
 * DSP1 emulator code (c) Copyright 1998 Ivar, _Demo_ and Gary Henderson.
 * C4 asm and some C emulation code (c) Copyright 2000 zsKnight and _Demo_.
 * C4 C code (c) Copyright 2001 Gary Henderson (gary.henderson@ntlworld.com).
 *
 * (c) Copyright 2014 - 2016 Daniel De Matteis. (UNDER NO CIRCUMSTANCE 
 * WILL COMMERCIAL RIGHTS EVER BE APPROPRIATED TO ANY PARTY)
 *
 * DOS port code contains the works of other authors. See headers in
 * individual files.
 *
 * Snes9x homepage: http://www.snes9x.com
 *
 * Permission to use, copy, modify and distribute Snes9x in both binary and
 * source form, for non-commercial purposes, is hereby granted without fee,
 * providing that this license information and copyright notice appear with
 * all copies and any derived work.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event shall the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Snes9x is freeware for PERSONAL USE only. Commercial users should
 * seek permission of the copyright holders first. Commercial use includes
 * charging money for Snes9x or software derived from Snes9x.
 *
 * The copyright holders request that bug fixes and improvements to the code
 * should be forwarded to them so everyone can benefit from the modifications
 * in future versions.
 *
 * Super NES and Super Nintendo Entertainment System are trademarks of
 * Nintendo Co., Limited and its subsidiary companies.
 */
#include <stdlib.h>

#include "snes9x.h"
#include "memmap.h"
#include "ppu.h"

struct Band
{
   uint32 Left;
   uint32 Right;
};

#undef MIN
#undef MAX
#define MIN(A,B) ((A) < (B) ? (A) : (B))
#define MAX(A,B) ((A) > (B) ? (A) : (B))
#define BAND_EMPTY(B) (B.Left >= B.Right)
#define BANDS_INTERSECT(A,B) ((A.Left >= B.Left && A.Left < B.Right) || \
               (A.Right > B.Left && A.Right <= B.Right))
#define OR_BANDS(R,A,B) {\
    R.Left = MIN(A.Left, B.Left); \
    R.Right = MAX(A.Right, B.Right);}

#define AND_BANDS(R,A,B) {\
    R.Left = MAX(A.Left, B.Left); \
    R.Right = MIN(A.Right, B.Right);}

static int IntCompare(const void* d1, const void* d2)
{
   /*
       if (*(uint32 *) d1 > *(uint32 *) d2)
      return (1);
       else
       if (*(uint32 *) d1 < *(uint32 *) d2)
      return (-1);
       return (0);
   */
   return (*(uint32*) d1 - * (uint32*) d2);
}

static int BandCompare(const void* d1, const void* d2)
{
   /*
       if (((struct Band *) d1)->Left > ((struct Band *) d2)->Left)
      return (1);
       else
       if (((struct Band *) d1)->Left < ((struct Band *) d2)->Left)
      return (-1);
       return (0);
   */
   return (((struct Band*) d1)->Left - ((struct Band*) d2)->Left);
}

void ComputeClipWindow(bool8_32 invert, int w, int wok, ClipData* pClip)
{
   pClip->Count[w] = 0;

   if (!Settings.DisableGraphicWindows)
   {
      if (pClip->Count [5] || wok)
      {
         struct Band Win1[3];
         struct Band Win2[3];
         uint32 Window1Enabled = 0;
         uint32 Window2Enabled = 0;
         if (wok)
         {
            if (PPU.ClipWindow1Enable [w])
            {
               if (!PPU.ClipWindow1Inside [w])
               {
                  Win1[0].Left = PPU.Window1Left;
                  Win1[0].Right = PPU.Window1Right + 1;
                  Window1Enabled = 1;
               }
               else
               {
                  if (PPU.Window1Left <= PPU.Window1Right)
                  {
                     if (PPU.Window1Left > 0)
                     {
                        Win1[0].Left = 0;
                        Win1[0].Right = PPU.Window1Left;
                        Window1Enabled = 1;
                     }
                     if (PPU.Window1Right < 255)
                     {
                        Win1[Window1Enabled].Left = PPU.Window1Right + 1;
                        Win1[Window1Enabled++].Right = 256;
                     }
                     if (Window1Enabled == 0)
                     {
                        Win1[0].Left = 1;
                        Win1[0].Right = 0;
                        Window1Enabled = 1;
                     }
                  }
                  else
                  {
                     // 'outside' a window with no range -
                     // appears to be the whole screen.
                     Win1[0].Left = 0;
                     Win1[0].Right = 256;
                     Window1Enabled = 1;
                  }
               }
            }
            if (PPU.ClipWindow2Enable [w])
            {
               if (!PPU.ClipWindow2Inside [w])
               {
                  Win2[0].Left = PPU.Window2Left;
                  Win2[0].Right = PPU.Window2Right + 1;
                  Window2Enabled = 1;
               }
               else
               {
                  if (PPU.Window2Left <= PPU.Window2Right)
                  {
                     if (PPU.Window2Left > 0)
                     {
                        Win2[0].Left = 0;
                        Win2[0].Right = PPU.Window2Left;
                        Window2Enabled = 1;
                     }
                     if (PPU.Window2Right < 255)
                     {
                        Win2[Window2Enabled].Left = PPU.Window2Right + 1;
                        Win2[Window2Enabled++].Right = 256;
                     }
                     if (Window2Enabled == 0)
                     {
                        Win2[0].Left = 1;
                        Win2[0].Right = 0;
                        Window2Enabled = 1;
                     }
                  }
                  else
                  {
                     Win2[0].Left = 0;
                     Win2[0].Right = 256;
                     Window2Enabled = 1;
                  }
               }
            }
         }
         if (Window1Enabled && Window2Enabled)
         {
            // Overlap logic
            //
            // Each window will be in one of three states:
            // 1. <no range> (Left > Right. One band)
            // 2. |    ----------------             | (Left >= 0, Right <= 255, Left <= Right. One band)
            // 3. |------------           ----------| (Left1 == 0, Right1 < Left2; Left2 > Right1, Right2 == 255. Two bands)

            struct Band Bands [6];
            int B = 0;
            switch (PPU.ClipWindowOverlapLogic [w] ^ 1)
            {
            case CLIP_OR:
               if (Window1Enabled == 1)
               {
                  if (BAND_EMPTY(Win1[0]))
                  {
                     B = Window2Enabled;
                     memmove(Bands, Win2,
                             sizeof(Win2[0]) * Window2Enabled);
                  }
                  else
                  {
                     if (Window2Enabled == 1)
                     {
                        if (BAND_EMPTY(Win2[0]))
                           Bands[B++] = Win1[0];
                        else
                        {
                           if (BANDS_INTERSECT(Win1[0], Win2[0]))
                           {
                              OR_BANDS(Bands[0], Win1[0], Win2[0])
                              B = 1;
                           }
                           else
                           {
                              Bands[B++] = Win1[0];
                              Bands[B++] = Win2[0];
                           }
                        }
                     }
                     else
                     {
                        if (BANDS_INTERSECT(Win1[0], Win2[0]))
                        {
                           OR_BANDS(Bands[0], Win1[0], Win2[0])
                           if (BANDS_INTERSECT(Win1[0], Win2[1]))
                              OR_BANDS(Bands[1], Win1[0], Win2[1])
                              else
                                 Bands[1] = Win2[1];
                           B = 1;
                           if (BANDS_INTERSECT(Bands[0], Bands[1]))
                              OR_BANDS(Bands[0], Bands[0], Bands[1])
                              else
                                 B = 2;
                        }
                        else if (BANDS_INTERSECT(Win1[0], Win2[1]))
                        {
                           Bands[B++] = Win2[0];
                           OR_BANDS(Bands[B], Win1[0], Win2[1]);
                           B++;
                        }
                        else
                        {
                           Bands[0] = Win2[0];
                           Bands[1] = Win1[0];
                           Bands[2] = Win2[1];
                           B = 3;
                        }
                     }
                  }
               }
               else if (Window2Enabled == 1)
               {
                  if (BAND_EMPTY(Win2[0]))
                  {
                     // Window 2 defines an empty range - just
                     // use window 1 as the clipping (which
                     // could also be empty).
                     B = Window1Enabled;
                     memmove(Bands, Win1,
                             sizeof(Win1[0]) * Window1Enabled);
                  }
                  else
                  {
                     // Window 1 has two bands and Window 2 has one.
                     // Neither is an empty region.
                     if (BANDS_INTERSECT(Win2[0], Win1[0]))
                     {
                        OR_BANDS(Bands[0], Win2[0], Win1[0])
                        if (BANDS_INTERSECT(Win2[0], Win1[1]))
                           OR_BANDS(Bands[1], Win2[0], Win1[1])
                           else
                              Bands[1] = Win1[1];
                        B = 1;
                        if (BANDS_INTERSECT(Bands[0], Bands[1]))
                           OR_BANDS(Bands[0], Bands[0], Bands[1])
                           else
                              B = 2;
                     }
                     else if (BANDS_INTERSECT(Win2[0], Win1[1]))
                     {
                        Bands[B++] = Win1[0];
                        OR_BANDS(Bands[B], Win2[0], Win1[1]);
                        B++;
                     }
                     else
                     {
                        Bands[0] = Win1[0];
                        Bands[1] = Win2[0];
                        Bands[2] = Win1[1];
                        B = 3;
                     }
                  }
               }
               else
               {
                  // Both windows have two bands
                  OR_BANDS(Bands[0], Win1[0], Win2[0]);
                  OR_BANDS(Bands[1], Win1[1], Win2[1]);
                  B = 1;
                  if (BANDS_INTERSECT(Bands[0], Bands[1]))
                     OR_BANDS(Bands[0], Bands[0], Bands[1])
                     else
                        B = 2;
               }
               break;

            case CLIP_AND:
               if (Window1Enabled == 1)
               {
                  // Window 1 has one band
                  if (BAND_EMPTY(Win1[0]))
                     Bands [B++] = Win1[0];
                  else if (Window2Enabled == 1)
                  {
                     if (BAND_EMPTY(Win2[0]))
                        Bands [B++] = Win2[0];
                     else
                     {
                        AND_BANDS(Bands[0], Win1[0], Win2[0]);
                        B = 1;
                     }
                  }
                  else
                  {
                     AND_BANDS(Bands[0], Win1[0], Win2[0]);
                     AND_BANDS(Bands[1], Win1[0], Win2[1]);
                     B = 2;
                  }
               }
               else if (Window2Enabled == 1)
               {
                  if (BAND_EMPTY(Win2[0]))
                     Bands[B++] = Win2[0];
                  else
                  {
                     // Window 1 has two bands.
                     AND_BANDS(Bands[0], Win1[0], Win2[0]);
                     AND_BANDS(Bands[1], Win1[1], Win2[0]);
                     B = 2;
                  }
               }
               else
               {
                  // Both windows have two bands.
                  AND_BANDS(Bands[0], Win1[0], Win2[0]);
                  AND_BANDS(Bands[1], Win1[1], Win2[1]);
                  B = 2;
                  if (BANDS_INTERSECT(Win1[0], Win2[1]))
                  {
                     AND_BANDS(Bands[2], Win1[0], Win2[1]);
                     B = 3;
                  }
                  else if (BANDS_INTERSECT(Win1[1], Win2[0]))
                  {
                     AND_BANDS(Bands[2], Win1[1], Win2[0]);
                     B = 3;
                  }
               }
               break;
            case CLIP_XNOR:
               invert = !invert;
            // Fall...

            case CLIP_XOR:
               if (Window1Enabled == 1 && BAND_EMPTY(Win1[0]))
               {
                  B = Window2Enabled;
                  memmove(Bands, Win2,
                          sizeof(Win2[0]) * Window2Enabled);
               }
               else if (Window2Enabled == 1 && BAND_EMPTY(Win2[0]))
               {
                  B = Window1Enabled;
                  memmove(Bands, Win1,
                          sizeof(Win1[0]) * Window1Enabled);
               }
               else
               {
                  uint32 p = 0;
                  uint32 points [10];
                  uint32 i;

                  invert = !invert;
                  // Build an array of points (window edges)
                  points [p++] = 0;
                  for (i = 0; i < Window1Enabled; i++)
                  {
                     points [p++] = Win1[i].Left;
                     points [p++] = Win1[i].Right;
                  }
                  for (i = 0; i < Window2Enabled; i++)
                  {
                     points [p++] = Win2[i].Left;
                     points [p++] = Win2[i].Right;
                  }
                  points [p++] = 256;
                  // Sort them
                  qsort((void*) points, p, sizeof(points [0]),
                        IntCompare);
                  for (i = 0; i < p; i += 2)
                  {
                     if (points [i] == points [i + 1])
                        continue;
                     Bands [B].Left = points [i];
                     while (i + 2 < p &&
                            points [i + 1] == points [i + 2])
                        i += 2;
                     Bands [B++].Right = points [i + 1];
                  }
               }
               break;
            }
            if (invert)
            {
               int b;
               int j = 0;
               int empty_band_count = 0;

               // First remove all empty bands from the list.
               for (b = 0; b < B; b++)
               {
                  if (!BAND_EMPTY(Bands[b]))
                  {
                     if (b != j)
                        Bands[j] = Bands[b];
                     j++;
                  }
                  else
                     empty_band_count++;
               }

               if (j > 0)
               {
                  if (j == 1)
                  {
                     j = 0;
                     // Easy case to deal with, so special case it.

                     if (Bands[0].Left > 0)
                     {
                        pClip->Left[j][w] = 0;
                        pClip->Right[j++][w] = Bands[0].Left + 1;
                     }
                     if (Bands[0].Right < 256)
                     {
                        pClip->Left[j][w] = Bands[0].Right;
                        pClip->Right[j++][w] = 256;
                     }
                     if (j == 0)
                     {
                        pClip->Left[j][w] = 1;
                        pClip->Right[j++][w] = 0;
                     }
                  }
                  else
                  {
                     // Now sort the bands into order
                     B = j;
                     qsort((void*) Bands, B,
                           sizeof(Bands [0]), BandCompare);

                     // Now invert the area the bands cover
                     j = 0;
                     for (b = 0; b < B; b++)
                     {
                        if (b == 0 && Bands[b].Left > 0)
                        {
                           pClip->Left[j][w] = 0;
                           pClip->Right[j++][w] = Bands[b].Left + 1;
                        }
                        else if (b == B - 1 && Bands[b].Right < 256)
                        {
                           pClip->Left[j][w] = Bands[b].Right;
                           pClip->Right[j++][w] = 256;
                        }
                        if (b < B - 1)
                        {
                           pClip->Left[j][w] = Bands[b].Right;
                           pClip->Right[j++][w] = Bands[b + 1].Left + 1;
                        }
                     }
                  }
               }
               else
               {
                  // Inverting a window that consisted of only
                  // empty bands is the whole width of the screen.
                  // Needed for Mario Kart's rear-view mirror display.
                  if (empty_band_count)
                  {
                     pClip->Left[j][w] = 0;
                     pClip->Right[j][w] = 256;
                     j++;
                  }
               }
               pClip->Count[w] = j;
            }
            else
            {
               int j;
               for (j = 0; j < B; j++)
               {
                  pClip->Left[j][w] = Bands[j].Left;
                  pClip->Right[j][w] = Bands[j].Right;
               }
               pClip->Count [w] = B;
            }
         }
         else
         {
            // Only one window enabled so no need to perform
            // complex overlap logic...

            if (Window1Enabled)
            {
               if (invert)
               {
                  int j = 0;

                  if (Window1Enabled == 1)
                  {
                     if (Win1[0].Left <= Win1[0].Right)
                     {
                        if (Win1[0].Left > 0)
                        {
                           pClip->Left[0][w] = 0;
                           pClip->Right[0][w] = Win1[0].Left;
                           j = 1;
                        }
                        if (Win1[0].Right < 256)
                        {
                           pClip->Left[j][w] = Win1[0].Right;
                           pClip->Right[j++][w] = 256;
                        }
                        if (j == 0)
                        {
                           pClip->Left[0][w] = 1;
                           pClip->Right[0][w] = 0;
                           j = 1;
                        }
                     }
                     else
                     {
                        pClip->Left[0][w] = 0;
                        pClip->Right[0][w] = 256;
                        j = 1;
                     }
                  }
                  else
                  {
                     pClip->Left [0][w] = Win1[0].Right;
                     pClip->Right[0][w] = Win1[1].Left;
                     j = 1;
                  }
                  pClip->Count [w] = j;
               }
               else
               {
                  uint32 j;
                  for (j = 0; j < Window1Enabled; j++)
                  {
                     pClip->Left [j][w] = Win1[j].Left;
                     pClip->Right [j][w] = Win1[j].Right;
                  }
                  pClip->Count [w] = Window1Enabled;
               }
            }
            else if (Window2Enabled)
            {
               if (invert)
               {
                  int j = 0;
                  if (Window2Enabled == 1)
                  {
                     if (Win2[0].Left <= Win2[0].Right)
                     {
                        if (Win2[0].Left > 0)
                        {
                           pClip->Left[0][w] = 0;
                           pClip->Right[0][w] = Win2[0].Left;
                           j = 1;
                        }
                        if (Win2[0].Right < 256)
                        {
                           pClip->Left[j][w] = Win2[0].Right;
                           pClip->Right[j++][w] = 256;
                        }
                        if (j == 0)
                        {
                           pClip->Left[0][w] = 1;
                           pClip->Right[0][w] = 0;
                           j = 1;
                        }
                     }
                     else
                     {
                        pClip->Left[0][w] = 0;
                        pClip->Right[0][w] = 256;
                        j = 1;
                     }
                  }
                  else
                  {
                     pClip->Left [0][w] = Win2[0].Right;
                     pClip->Right[0][w] = Win2[1].Left + 1;
                     j = 1;
                  }
                  pClip->Count [w] = j;
               }
               else
               {
                  uint32 j;
                  for (j = 0; j < Window2Enabled; j++)
                  {
                     pClip->Left [j][w] = Win2[j].Left;
                     pClip->Right [j][w] = Win2[j].Right;
                  }
                  pClip->Count [w] = Window2Enabled;
               }
            }
         }

         if ((w != 5) && (pClip->Count [5]))
         {
            //if
            //{
            // Colour window enabled. Set the
            // clip windows for all remaining backgrounds to be
            // the same as the colour window.
            if (pClip->Count [w] == 0)
            {
               pClip->Count [w] = pClip->Count [5];
               int i;
               for (i = pClip->Count[w] - 1; i >= 0 ; i--)
               {
                  pClip->Left [i][w] = pClip->Left [i][5];
                  pClip->Right [i][w] = pClip->Right [i][5];
               }
            }
            else
            {
               // Intersect the colour window with the bg's
               // own clip window.
               int i, j;
               //for (i = 0; i < pClip->Count [w]; i++)
               for (i = pClip->Count [w] - 1; i >= 0 ; i--)
               {
                  //for (j = 0; j < pClip->Count [5]; j++)
                  for (j = pClip->Count [5] - 1; j >= 0 ; j--)
                  {
                     if ((pClip->Left[i][w] >= pClip->Left[j][5] && pClip->Left[i][w] < pClip->Right[j][5])
                           || (pClip->Left[j][5] >= pClip->Left[i][w] && pClip->Left[j][5] < pClip->Right[i][w]))
                     {
                        // Found an intersection!
                        pClip->Left[i][w] = MAX(pClip->Left[i][w], pClip->Left[j][5]);
                        pClip->Right[i][w] = MIN(pClip->Right[i][w], pClip->Right[j][5]);
                        goto Clip_ok;
                     }
                  }
                  // no intersection, nullify it
                  pClip->Left[i][w] = 1;
                  pClip->Right[i][w] = 0;
Clip_ok:
                  j = 0; // dummy statement
               }
            }
            //}
         }
      } // if (w == 5 | ...
   } // if (!Settings.DisableGraphicWindows)
}

void ComputeClipWindows()
{
   // Main screen
   // - Colour window
   if ((GFX.r2130_s & 0xc0) == 0xc0)
   {
      // The whole of the main screen is switched off,
      // completely clip everything.
      int i;
      for (i = 0; i < 6; i++)
      {
         IPPU.Clip [0].Count [i] = 1;
         IPPU.Clip [0].Left [0][i] = 1;
         IPPU.Clip [0].Right [0][i] = 0;
      }
   }
   else if (GFX.r2130_s & 0xc0) ComputeClipWindow(((GFX.r2130_s & 0xc0) == 0x40), 5, 1, &IPPU.Clip [0]);
   else IPPU.Clip[0].Count[5] = 0;
   // - Objs
   ComputeClipWindow(FALSE, 4, (GFX.r212c_s & GFX.r212e_s & (1 << 4)), &IPPU.Clip [0]);
   // - Backgrounds
   ComputeClipWindow(FALSE, 3, (GFX.r212c_s & GFX.r212e_s & (1 << 3)), &IPPU.Clip [0]);
   ComputeClipWindow(FALSE, 2, (GFX.r212c_s & GFX.r212e_s & (1 << 2)), &IPPU.Clip [0]);
   ComputeClipWindow(FALSE, 1, (GFX.r212c_s & GFX.r212e_s & (1 << 1)), &IPPU.Clip [0]);
   ComputeClipWindow(FALSE, 0, (GFX.r212c_s & GFX.r212e_s & (1 << 0)), &IPPU.Clip [0]);

   // Sub screen
   // - Colour window
   if ((GFX.r2130_s & 0x30) == 0x30)
   {
      // The sub-screen is switched off, completely
      // clip everything.
      int i;
      for (i = 0; i < 6; i++)
      {
         IPPU.Clip [1].Count [i] = 1;
         IPPU.Clip [1].Left [0][i] = 1;
         IPPU.Clip [1].Right [0][i] = 0;
      }
   }
   else if (GFX.r2130_s & 0x30) ComputeClipWindow(((GFX.r2130_s & 0x30) == 0x10), 5, 1, &IPPU.Clip [1]);
   else IPPU.Clip[1].Count[5] = 0;
   // - Objs
   ComputeClipWindow(FALSE, 4, (GFX.r212d_s & GFX.r212f_s & (1 << 4)), &IPPU.Clip [1]);
   // - Backgrounds
   ComputeClipWindow(FALSE, 3, (GFX.r212d_s & GFX.r212f_s & (1 << 3)), &IPPU.Clip [1]);
   ComputeClipWindow(FALSE, 2, (GFX.r212d_s & GFX.r212f_s & (1 << 2)), &IPPU.Clip [1]);
   ComputeClipWindow(FALSE, 1, (GFX.r212d_s & GFX.r212f_s & (1 << 1)), &IPPU.Clip [1]);
   ComputeClipWindow(FALSE, 0, (GFX.r212d_s & GFX.r212f_s & (1 << 0)), &IPPU.Clip [1]);

   PPU.RecomputeClipWindows = FALSE;
}
