/**************************************************************************
 *
 * Copyright 2010-2021 VMWare, Inc.
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sub license, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
 * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 **************************************************************************/

/**
 * Look for common topology patterns which can be converted into rectangles.
 */


#include "lp_setup_context.h"
#include "draw/draw_vbuf.h"
#include "draw/draw_vertex.h"
#include "util/u_memory.h"
#include "util/u_math.h"
#include "lp_state_fs.h"
#include "lp_state_setup.h"
#include "lp_perf.h"


/**
 * Duplicated from lp_setup_vbuf.c.
 */
typedef const float (*const_float4_ptr)[4];


static inline
const_float4_ptr get_vert(const void *vertex_buffer, int index, int stride)
{
   return (const_float4_ptr)((char *)vertex_buffer + index * stride);
}


/* Aero sends these weird zero area triangles.  Test for them here.
 */
static boolean
is_zero_area(const float (*v0)[4],
             const float (*v1)[4],
             const float (*v2)[4])
{
   /* Specialized test for v0.y == v1.y == v2.y.
    */
   return (v0[0][1] == v1[0][1] &&
           v0[0][1] == v2[0][1]);
}


/**
 * Assuming axis-aligned stretch blit (s a function of x alone, t a
 * function of y alone), create a new vertex as in:
 *
 *   vx------+
 *    |      |
 *    |      |
 *   out-----vy
 */
static void
make_vert(const float (*vx)[4],
          const float (*vy)[4],
          float (*out)[4])
{
   out[0][0] = vx[0][0];
   out[0][1] = vy[0][1];
   out[0][2] = vx[0][2];
   out[0][3] = vx[0][3];
   out[1][0] = vx[1][0];
   out[1][1] = vy[1][1];
}


/* Calculate axis-aligned interpolant for s as a function of x.
 */
static void
calc_interps(float x0, float x1,
             float s0, float s1,
             float *a, float *b)
{
   assert(x0 != x1);
   *a = (s0 - s1) / (x0 - x1);
   *b = s0 - *a * x0;
}


/* Validate axis-aligned interpolant for s and t as functions of x and
 * y respectively.
 */
static boolean
test_interps(const_float4_ptr v,
             float as, float bs,
             float at, float bt)
{
   float s = as * v[0][0] + bs;
   float t = at * v[0][1] + bt;
   return (util_is_approx(s, v[1][0], 1/4096.0) &&
           util_is_approx(t, v[1][1], 1/4096.0));
}


static void
rect(struct lp_setup_context *setup,
     const float (*v0)[4],
     const float (*v1)[4],
     const float (*v2)[4])
{
   ASSERTED int culled = LP_COUNT_GET(nr_culled_rects);

   if (0) {
      float as, bs, at, bt;
      calc_interps(v0[0][0], v2[0][0], v0[1][0], v2[1][0], &as, &bs);
      calc_interps(v0[0][1], v2[0][1], v0[1][1], v2[1][1], &at, &bt);
      assert(test_interps(v1, as, bs, at, bt));
   }

   assert(v0[0][0] == v1[0][0]);
   assert(v1[0][1] == v2[0][1]);

   lp_rect_cw(setup, v0, v1, v2, TRUE);

   assert(culled == LP_COUNT_GET(nr_culled_rects));
}


/**
 * Check this is an axis-aligned rectangle as in:
 *
 *   v3------v0
 *    |      |
 *    |      |
 *   v2------v1
 */
static boolean
test_rect(const_float4_ptr v0,
          const_float4_ptr v1,
          const_float4_ptr v2,
          const_float4_ptr v3)
{
   if (v0[0][0] != v1[0][0] ||
       v1[0][1] != v2[0][1] ||
       v2[0][0] != v3[0][0] ||
       v3[0][1] != v0[0][1])
      return FALSE;

   if (v0[0][3] != 1.0 ||
       v1[0][3] != 1.0 ||
       v2[0][3] != 1.0 ||
       v3[0][3] != 1.0)
      return FALSE;

   return TRUE;
}


/**
 * Aero sends the following shape as
 *
 *  18                                                        12
 *    +-------------------------------------------------/----+
 *    |\                                      /---------    /|
 *    | \                           /---------             / |
 *    |  \                /---------                      /  |
 *    |   \     /---------                               /   |
 * vA +    +--------------------------------------------+    + vC
 *    |    | 9                                        6 |    |
 *    |   /|                                            |\   |
 *    |   ||                                            ||   |
 *    |  / |                                            | \  |
 *    |  | |                                            | |  |
 *    |  | | 3                                        0 |  \ |
 * vB + /  +--------------------------------------------+  | + vD
 *    | | /                               ---------/     \ | |
 *    |/ /                      ---------/                \ \|
 *    ||/             ---------/                           \||
 *    |/    ---------/                                      \|
 *    +----/-------------------------------------------------+
 *   1                                                        2
 *
 * and in the following decomposition:
 *   (0, 1, 2)
 *   (3, 0, 1),
 *   (6, 0, 2),
 *   (9, 3, 1),
 *   (12, 2, 6),
 *   (12, 6, 9),
 *   (18, 1, 9),
 *   (18, 9, 12),
 *
 * There's no straight-forward way to interpret the existing vertices
 * as rectangles.  Instead we convert this into four axis-aligned
 * rectangles by introducing new vertices at vA, vB, vC and vD, and
 * then drawing rectangles.
 */
static boolean
check_elts24(struct lp_setup_context *setup, const void *vb, int stride)
{
   const int count = 24;
   const int uniq[8] = { 0, 1, 2, 3, 6, 9, 12, 18 };
   const int elts[24] = {
      0, 1, 2,
      3, 0, 1,
      6, 0, 2,
      9, 3, 1,
      12, 2, 6,
      12, 6, 9,
      18, 1, 9,
      18, 9, 12
   };
   const_float4_ptr v0  = get_vert(vb, stride, 0);
   const_float4_ptr v1  = get_vert(vb, stride, 1);
   const_float4_ptr v2  = get_vert(vb, stride, 2);
   const_float4_ptr v3  = get_vert(vb, stride, 3);
   const_float4_ptr v6  = get_vert(vb, stride, 6);
   const_float4_ptr v9  = get_vert(vb, stride, 9);
   const_float4_ptr v12 = get_vert(vb, stride, 12);
   const_float4_ptr v18 = get_vert(vb, stride, 18);

   /* Could just calculate a set of interpolants and bin rectangle
    * commands for this triangle list directly.  Instead, introduce
    * some new vertices and feed to the rectangle setup code:
    */
   PIPE_ALIGN_VAR(16) float vA[2][4];
   PIPE_ALIGN_VAR(16) float vB[2][4];
   PIPE_ALIGN_VAR(16) float vC[2][4];
   PIPE_ALIGN_VAR(16) float vD[2][4];

   float as, bs;
   float at, bt;
   int i;

   if (stride != 32)
      return FALSE;

   /* Check the shape is two rectangles:
    */
   if (!test_rect(v12, v2, v1, v18))
      return FALSE;

   if (!test_rect(v6, v0, v3, v9))
      return FALSE;

   /* XXX: check one rect is inside the other?
    */

   /* Check our tesselation matches:
    */
   for (i = 0; i < count; i++) {
      if (memcmp(get_vert(vb, i, stride),
                 get_vert(vb, elts[i], stride),
                 6 * sizeof(float)) != 0)
         return FALSE;
   }

   /* Test that this is a stretch blit, meaning we should be able to
    * introduce vertices at will.
    */
   calc_interps(v0[0][0], v2[0][0], v0[1][0], v2[1][0], &as, &bs);
   calc_interps(v0[0][1], v2[0][1], v0[1][1], v2[1][1], &at, &bt);

   for (i = 0; i < ARRAY_SIZE(uniq); i++) {
      const_float4_ptr v = get_vert(vb, stride, i);
      if (!test_interps(v, as, bs, at, bt))
         return FALSE;
   }

   make_vert(v18, v9, vA);
   make_vert(v18, v3, vB);
   make_vert(v12, v9, vC);
   make_vert(v12, v3, vD);

   assert(test_interps((const_float4_ptr)vA, as, bs, at, bt));
   assert(test_interps((const_float4_ptr)vB, as, bs, at, bt));
   assert(test_interps((const_float4_ptr)vC, as, bs, at, bt));
   assert(test_interps((const_float4_ptr)vD, as, bs, at, bt));

   rect(setup,
        (const_float4_ptr)v12,
        (const_float4_ptr)vC,
        (const_float4_ptr)vA);

   rect(setup,
        (const_float4_ptr)v9,
        (const_float4_ptr)v3,
        (const_float4_ptr)vB);

   rect(setup,
        (const_float4_ptr)vD,
        (const_float4_ptr)v2,
        (const_float4_ptr)v1);

   rect(setup,
        (const_float4_ptr)vC,
        (const_float4_ptr)vD,
        (const_float4_ptr)v0);

   return TRUE;
}

boolean
lp_setup_analyse_triangles(struct lp_setup_context *setup,
                           const void *vb,
                           int stride,
                           int nr)
{
   int i;
   const boolean variant_blit = setup->fs.current.variant->blit;

   if (0) {
      debug_printf("%s %d\n", __FUNCTION__, nr);

      if (variant_blit) {
         debug_printf("  - blit variant\n");
      }

      for (i = 0; i < nr; i += 3) {
         const_float4_ptr v0 = get_vert(vb, i, stride);
         const_float4_ptr v1 = get_vert(vb, i+1, stride);
         const_float4_ptr v2 = get_vert(vb, i+2, stride);
         lp_setup_print_triangle(setup, v0, v1, v2);
      }
   }

   /* When drawing some window navigator bars, aero sends a mixed up
    * rectangle:
    *
    *    - first triangle ccw
    *    - second triangle cw
    *    - third triangle zero area.
    */
   if (nr == 9 &&
       is_zero_area(get_vert(vb, nr-1, stride),
                    get_vert(vb, nr-2, stride),
                    get_vert(vb, nr-3, stride)))
   {
      const float (*v0)[4] = get_vert(vb, 0, stride);
      const float (*v1)[4] = get_vert(vb, 1, stride);
      const float (*v2)[4] = get_vert(vb, 2, stride);
      const float (*v3)[4] = get_vert(vb, 3, stride);
      const float (*v4)[4] = get_vert(vb, 4, stride);
      const float (*v5)[4] = get_vert(vb, 5, stride);

      /*
       * [   v0,       v1,       v2   ]  [   v3,       v4,       v5   ]
       * [(X0, Y0), (X0, Y1), (X1, Y1)]  [(X1, Y0), (X1, Y1), (X0, Y0)]
       */
      if (v0[0][0] == v1[0][0] && v0[0][0] == v5[0][0] &&
          v2[0][0] == v3[0][0] && v2[0][0] == v4[0][0] &&
          v0[0][1] == v3[0][1] && v0[0][1] == v5[0][1] &&
          v1[0][1] == v2[0][1] && v1[0][1] == v4[0][1]) {

         lp_rect_cw(setup, v0, v1, v2, TRUE);
      }
      return TRUE;
   }

   /* When highlighting (?) windows, aero sends a window border
    * comprised of non-rectangular triangles, but which as a whole can
    * be decomposed into rectangles.
    *
    * Again, with a zero-area trailing triangle.
    *
    * This requires introducing a couple of new vertices, which are
    * luckily easy to compute.
    */
   if (nr == 27 &&
       variant_blit &&
       setup->setup.variant->key.inputs[0].src_index == 1 &&
       setup->setup.variant->key.inputs[0].usage_mask == 0x3 &&
       is_zero_area(get_vert(vb, nr-1, stride),
                    get_vert(vb, nr-2, stride),
                    get_vert(vb, nr-3, stride)) &&
       check_elts24(setup, vb, stride))
   {
      return TRUE;
   }

   return FALSE;
}
