/*
 * Mesa 3-D graphics library
 *
 * Copyright 2008 VMware, Inc.
 * Copyright (C) 2010 LunarG Inc.
 *
 * 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, sublicense,
 * 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 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 NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS 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.
 *
 * Authors:
  *   Keith Whitwell <keithw@vmware.com>
 *    Chia-I Wu <olv@lunarg.com>
 */

/* these macros are optional */
#ifndef LOCAL_VARS
#define LOCAL_VARS
#endif
#ifndef FUNC_ENTER
#define FUNC_ENTER do {} while (0)
#endif
#ifndef FUNC_EXIT
#define FUNC_EXIT do {} while (0)
#endif
#ifndef LINE_ADJ
#define LINE_ADJ(flags, a0, i0, i1, a1) LINE(flags, i0, i1)
#endif
#ifndef TRIANGLE_ADJ
#define TRIANGLE_ADJ(flags, i0, a0, i1, a1, i2, a2) TRIANGLE(flags, i0, i1, i2)
#endif

static void
FUNC(FUNC_VARS)
{
   unsigned idx[6], i;
   ushort flags;
   LOCAL_VARS

   FUNC_ENTER;

   /* prim, prim_flags, count, and last_vertex_last should have been defined */
   if (0) {
      debug_printf("%s: prim 0x%x, prim_flags 0x%x, count %d, last_vertex_last %d\n",
            __FUNCTION__, prim, prim_flags, count, last_vertex_last);
   }

   switch (prim) {
   case PIPE_PRIM_POINTS:
      for (i = 0; i < count; i++) {
         idx[0] = GET_ELT(i);
         POINT(idx[0]);
      }
      break;

   case PIPE_PRIM_LINES:
      flags = DRAW_PIPE_RESET_STIPPLE;
      for (i = 0; i + 1 < count; i += 2) {
         idx[0] = GET_ELT(i);
         idx[1] = GET_ELT(i + 1);
         LINE(flags, idx[0], idx[1]);
      }
      break;

   case PIPE_PRIM_LINE_LOOP:
   case PIPE_PRIM_LINE_STRIP:
      if (count >= 2) {
         flags = (prim_flags & DRAW_SPLIT_BEFORE) ? 0 : DRAW_PIPE_RESET_STIPPLE;
         idx[1] = GET_ELT(0);
         idx[2] = idx[1];

         for (i = 1; i < count; i++, flags = 0) {
            idx[0] = idx[1];
            idx[1] = GET_ELT(i);
            LINE(flags, idx[0], idx[1]);
         }
         /* close the loop */
         if (prim == PIPE_PRIM_LINE_LOOP && !prim_flags)
            LINE(flags, idx[1], idx[2]);
      }
      break;

   case PIPE_PRIM_TRIANGLES:
      flags = DRAW_PIPE_RESET_STIPPLE | DRAW_PIPE_EDGE_FLAG_ALL;
      for (i = 0; i + 2 < count; i += 3) {
         idx[0] = GET_ELT(i);
         idx[1] = GET_ELT(i + 1);
         idx[2] = GET_ELT(i + 2);
         TRIANGLE(flags, idx[0], idx[1], idx[2]);
      }
      break;

   case PIPE_PRIM_TRIANGLE_STRIP:
      if (count >= 3) {
         flags = DRAW_PIPE_RESET_STIPPLE | DRAW_PIPE_EDGE_FLAG_ALL;
         idx[1] = GET_ELT(0);
         idx[2] = GET_ELT(1);

         if (last_vertex_last) {
            for (i = 0; i + 2 < count; i++) {
               idx[0] = idx[1];
               idx[1] = idx[2];
               idx[2] = GET_ELT(i + 2);
               /* always emit idx[2] last */
               if (i & 1)
                  TRIANGLE(flags, idx[1], idx[0], idx[2]);
               else
                  TRIANGLE(flags, idx[0], idx[1], idx[2]);
            }
         }
         else {
            for (i = 0; i + 2 < count; i++) {
               idx[0] = idx[1];
               idx[1] = idx[2];
               idx[2] = GET_ELT(i + 2);
               /* always emit idx[0] first */
               if (i & 1)
                  TRIANGLE(flags, idx[0], idx[2], idx[1]);
               else
                  TRIANGLE(flags, idx[0], idx[1], idx[2]);
            }
         }
      }
      break;

   case PIPE_PRIM_TRIANGLE_FAN:
      if (count >= 3) {
         flags = DRAW_PIPE_RESET_STIPPLE | DRAW_PIPE_EDGE_FLAG_ALL;
         idx[0] = GET_ELT(0);
         idx[2] = GET_ELT(1);

         /* idx[0] is neither the first nor the last vertex */
         if (last_vertex_last) {
            for (i = 0; i + 2 < count; i++) {
               idx[1] = idx[2];
               idx[2] = GET_ELT(i + 2);
               /* always emit idx[2] last */
               TRIANGLE(flags, idx[0], idx[1], idx[2]);
            }
         }
         else {
            for (i = 0; i + 2 < count; i++) {
               idx[1] = idx[2];
               idx[2] = GET_ELT(i + 2);
               /* always emit idx[1] first */
               TRIANGLE(flags, idx[1], idx[2], idx[0]);
            }
         }
      }
      break;

   case PIPE_PRIM_QUADS:
      if (last_vertex_last) {
         for (i = 0; i + 3 < count; i += 4) {
            idx[0] = GET_ELT(i);
            idx[1] = GET_ELT(i + 1);
            idx[2] = GET_ELT(i + 2);
            idx[3] = GET_ELT(i + 3);

            flags = DRAW_PIPE_RESET_STIPPLE |
                    DRAW_PIPE_EDGE_FLAG_0 |
                    DRAW_PIPE_EDGE_FLAG_2;
            /* always emit idx[3] last */
            TRIANGLE(flags, idx[0], idx[1], idx[3]);

            flags = DRAW_PIPE_EDGE_FLAG_0 |
                    DRAW_PIPE_EDGE_FLAG_1;
            TRIANGLE(flags, idx[1], idx[2], idx[3]);
         }
      }
      else {
         for (i = 0; i + 3 < count; i += 4) {
            idx[0] = GET_ELT(i);
            idx[1] = GET_ELT(i + 1);
            idx[2] = GET_ELT(i + 2);
            idx[3] = GET_ELT(i + 3);

            flags = DRAW_PIPE_RESET_STIPPLE |
                    DRAW_PIPE_EDGE_FLAG_0 |
                    DRAW_PIPE_EDGE_FLAG_1;
            /* always emit idx[3] / idx[0] first */
            if (quads_flatshade_last)
               TRIANGLE(flags, idx[3], idx[0], idx[1]);
            else
               TRIANGLE(flags, idx[0], idx[1], idx[2]);

            flags = DRAW_PIPE_EDGE_FLAG_1 |
                    DRAW_PIPE_EDGE_FLAG_2;
            if (quads_flatshade_last)
               TRIANGLE(flags, idx[3], idx[1], idx[2]);
            else
               TRIANGLE(flags, idx[0], idx[2], idx[3]);
         }
      }
      break;

   case PIPE_PRIM_QUAD_STRIP:
      if (count >= 4) {
         idx[2] = GET_ELT(0);
         idx[3] = GET_ELT(1);

         if (last_vertex_last) {
            for (i = 0; i + 3 < count; i += 2) {
               idx[0] = idx[2];
               idx[1] = idx[3];
               idx[2] = GET_ELT(i + 2);
               idx[3] = GET_ELT(i + 3);

               /* always emit idx[3] last */
               flags = DRAW_PIPE_RESET_STIPPLE |
                       DRAW_PIPE_EDGE_FLAG_0 |
                       DRAW_PIPE_EDGE_FLAG_2;
               TRIANGLE(flags, idx[2], idx[0], idx[3]);

               flags = DRAW_PIPE_EDGE_FLAG_0 |
                       DRAW_PIPE_EDGE_FLAG_1;
               TRIANGLE(flags, idx[0], idx[1], idx[3]);
            }
         }
         else {
            for (i = 0; i + 3 < count; i += 2) {
               idx[0] = idx[2];
               idx[1] = idx[3];
               idx[2] = GET_ELT(i + 2);
               idx[3] = GET_ELT(i + 3);

               flags = DRAW_PIPE_RESET_STIPPLE |
                       DRAW_PIPE_EDGE_FLAG_0 |
                       DRAW_PIPE_EDGE_FLAG_1;
               /* always emit idx[3] / idx[0 first */
               if (quads_flatshade_last)
                  TRIANGLE(flags, idx[3], idx[2], idx[0]);
               else
                  TRIANGLE(flags, idx[0], idx[3], idx[2]);

               flags = DRAW_PIPE_EDGE_FLAG_1 |
                       DRAW_PIPE_EDGE_FLAG_2;
               if (quads_flatshade_last)
                  TRIANGLE(flags, idx[3], idx[0], idx[1]);
               else
                  TRIANGLE(flags, idx[0], idx[1], idx[3]);
            }
         }
      }
      break;

   case PIPE_PRIM_POLYGON:
      if (count >= 3) {
         ushort edge_next, edge_finish;

         if (last_vertex_last) {
            flags = (DRAW_PIPE_RESET_STIPPLE |
                     DRAW_PIPE_EDGE_FLAG_0);
            if (!(prim_flags & DRAW_SPLIT_BEFORE))
               flags |= DRAW_PIPE_EDGE_FLAG_2;

            edge_next = DRAW_PIPE_EDGE_FLAG_0;
            edge_finish =
               (prim_flags & DRAW_SPLIT_AFTER) ? 0 : DRAW_PIPE_EDGE_FLAG_1;
         }
         else {
            flags = (DRAW_PIPE_RESET_STIPPLE |
                     DRAW_PIPE_EDGE_FLAG_1);
            if (!(prim_flags & DRAW_SPLIT_BEFORE))
               flags |= DRAW_PIPE_EDGE_FLAG_0;

            edge_next = DRAW_PIPE_EDGE_FLAG_1;
            edge_finish =
               (prim_flags & DRAW_SPLIT_AFTER) ? 0 : DRAW_PIPE_EDGE_FLAG_2;
         }

         idx[0] = GET_ELT(0);
         idx[2] = GET_ELT(1);

         for (i = 0; i + 2 < count; i++, flags = edge_next) {
            idx[1] = idx[2];
            idx[2] = GET_ELT(i + 2);

            if (i + 3 == count)
               flags |= edge_finish;

            /* idx[0] is both the first and the last vertex */
            if (last_vertex_last)
               TRIANGLE(flags, idx[1], idx[2], idx[0]);
            else
               TRIANGLE(flags, idx[0], idx[1], idx[2]);
         }
      }
      break;

   case PIPE_PRIM_LINES_ADJACENCY:
      flags = DRAW_PIPE_RESET_STIPPLE;
      for (i = 0; i + 3 < count; i += 4) {
         idx[0] = GET_ELT(i);
         idx[1] = GET_ELT(i + 1);
         idx[2] = GET_ELT(i + 2);
         idx[3] = GET_ELT(i + 3);
         LINE_ADJ(flags, idx[0], idx[1], idx[2], idx[3]);
      }
      break;

   case PIPE_PRIM_LINE_STRIP_ADJACENCY:
      if (count >= 4) {
         flags = (prim_flags & DRAW_SPLIT_BEFORE) ? 0 : DRAW_PIPE_RESET_STIPPLE;
         idx[1] = GET_ELT(0);
         idx[2] = GET_ELT(1);
         idx[3] = GET_ELT(2);

         for (i = 1; i + 2 < count; i++, flags = 0) {
            idx[0] = idx[1];
            idx[1] = idx[2];
            idx[2] = idx[3];
            idx[3] = GET_ELT(i + 2);
            LINE_ADJ(flags, idx[0], idx[1], idx[2], idx[3]);
         }
      }
      break;

   case PIPE_PRIM_TRIANGLES_ADJACENCY:
      flags = DRAW_PIPE_RESET_STIPPLE | DRAW_PIPE_EDGE_FLAG_ALL;
      for (i = 0; i + 5 < count; i += 6) {
         idx[0] = GET_ELT(i);
         idx[1] = GET_ELT(i + 1);
         idx[2] = GET_ELT(i + 2);
         idx[3] = GET_ELT(i + 3);
         idx[4] = GET_ELT(i + 4);
         idx[5] = GET_ELT(i + 5);
         TRIANGLE_ADJ(flags, idx[0], idx[1], idx[2], idx[3], idx[4], idx[5]);
      }
      break;

   case PIPE_PRIM_TRIANGLE_STRIP_ADJACENCY:
      if (count >= 6) {
         flags = DRAW_PIPE_RESET_STIPPLE | DRAW_PIPE_EDGE_FLAG_ALL;
         idx[0] = GET_ELT(1);
         idx[2] = GET_ELT(0);
         idx[4] = GET_ELT(2);
         idx[3] = GET_ELT(4);

         /*
          * The vertices of the i-th triangle are stored in
          * idx[0,2,4] = { 2*i, 2*i+2, 2*i+4 };
          *
          * The adjacent vertices are stored in
          * idx[1,3,5] = { 2*i-2, 2*i+6, 2*i+3 }.
          *
          * However, there are two exceptions:
          *
          * For the first triangle, idx[1] = 1;
          * For the  last triangle, idx[3] = 2*i+5.
          */
         if (last_vertex_last) {
            for (i = 0; i + 5 < count; i += 2) {
               idx[1] = idx[0];

               idx[0] = idx[2];
               idx[2] = idx[4];
               idx[4] = idx[3];

               idx[3] = GET_ELT(i + ((i + 7 < count) ? 6 : 5));
               idx[5] = GET_ELT(i + 3);

               /*
                * alternate the first two vertices (idx[0] and idx[2]) and the
                * corresponding adjacent vertices (idx[3] and idx[5]) to have
                * the correct orientation
                */
               if (i & 2) {
                  TRIANGLE_ADJ(flags,
                        idx[2], idx[1], idx[0], idx[5], idx[4], idx[3]);
               }
               else {
                  TRIANGLE_ADJ(flags,
                        idx[0], idx[1], idx[2], idx[3], idx[4], idx[5]);
               }
            }
         }
         else {
            for (i = 0; i + 5 < count; i += 2) {
               idx[1] = idx[0];

               idx[0] = idx[2];
               idx[2] = idx[4];
               idx[4] = idx[3];

               idx[3] = GET_ELT(i + ((i + 7 < count) ? 6 : 5));
               idx[5] = GET_ELT(i + 3);

               /*
                * alternate the last two vertices (idx[2] and idx[4]) and the
                * corresponding adjacent vertices (idx[1] and idx[5]) to have
                * the correct orientation
                */
               if (i & 2) {
                  TRIANGLE_ADJ(flags,
                        idx[0], idx[5], idx[4], idx[3], idx[2], idx[1]);
               }
               else {
                  TRIANGLE_ADJ(flags,
                        idx[0], idx[1], idx[2], idx[3], idx[4], idx[5]);
               }
            }
         }
      }
      break;

   default:
      assert(0);
      break;
   }

   FUNC_EXIT;
}

#undef LOCAL_VARS
#undef FUNC_ENTER
#undef FUNC_EXIT
#undef LINE_ADJ
#undef TRIANGLE_ADJ

#undef FUNC
#undef FUNC_VARS
#undef GET_ELT
#undef POINT
#undef LINE
#undef TRIANGLE