/**************************************************************************
 * 
 * Copyright 2008 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.
 * 
 **************************************************************************/

/**
 * TGSI program transformation utility.
 *
 * Authors:  Brian Paul
 */

#include "util/u_debug.h"
#include "util/log.h"

#include "tgsi_transform.h"

/**
 * Increments the next-token index if the tgsi_build_* succeeded, or extends the
 * token array and returns true to request a re-emit of the tgsi_build_* by the
 * caller.
 */
static bool
need_re_emit(struct tgsi_transform_context *ctx, uint32_t emitted, struct tgsi_header orig_header)
{
   if (emitted > 0) {
      ctx->ti += emitted;
      return false;
   } else {
      uint32_t new_len = ctx->max_tokens_out * 2;
      if (new_len < ctx->max_tokens_out) {
         ctx->fail = true;
         return false;
      }

      struct tgsi_token *new_tokens = tgsi_alloc_tokens(new_len);
      if (!new_tokens) {
         ctx->fail = true;
         return false;
      }
      memcpy(new_tokens, ctx->tokens_out, sizeof(struct tgsi_token) * ctx->ti);

      tgsi_free_tokens(ctx->tokens_out);
      ctx->tokens_out = new_tokens;
      ctx->max_tokens_out = new_len;

      /* Point the header at the resized tokens. */
      ctx->header = (struct tgsi_header *)new_tokens;
      /* The failing emit may have incremented header/body size, reset it to its state before our attempt. */
      *ctx->header = orig_header;

      return true;
   }
}

static void
emit_instruction(struct tgsi_transform_context *ctx,
                 const struct tgsi_full_instruction *inst)
{
   uint32_t emitted;
   struct tgsi_header orig_header = *ctx->header;

   do {
      emitted = tgsi_build_full_instruction(inst,
                                            ctx->tokens_out + ctx->ti,
                                            ctx->header,
                                            ctx->max_tokens_out - ctx->ti);
   } while (need_re_emit(ctx, emitted, orig_header));
}


static void
emit_declaration(struct tgsi_transform_context *ctx,
                 const struct tgsi_full_declaration *decl)
{
   uint32_t emitted;
   struct tgsi_header orig_header = *ctx->header;

   do {
      emitted = tgsi_build_full_declaration(decl,
                                            ctx->tokens_out + ctx->ti,
                                            ctx->header,
                                            ctx->max_tokens_out - ctx->ti);
   } while (need_re_emit(ctx, emitted, orig_header));
}


static void
emit_immediate(struct tgsi_transform_context *ctx,
               const struct tgsi_full_immediate *imm)
{
   uint32_t emitted;
   struct tgsi_header orig_header = *ctx->header;

   do {
      emitted = tgsi_build_full_immediate(imm,
                                          ctx->tokens_out + ctx->ti,
                                          ctx->header,
                                          ctx->max_tokens_out - ctx->ti);
   } while (need_re_emit(ctx, emitted, orig_header));
}


static void
emit_property(struct tgsi_transform_context *ctx,
              const struct tgsi_full_property *prop)
{
   uint32_t emitted;
   struct tgsi_header orig_header = *ctx->header;

   do {
      emitted = tgsi_build_full_property(prop,
                                         ctx->tokens_out + ctx->ti,
                                         ctx->header,
                                         ctx->max_tokens_out - ctx->ti);
   } while (need_re_emit(ctx, emitted, orig_header));
}


/**
 * Apply user-defined transformations to the input shader to produce
 * the output shader.
 * For example, a register search-and-replace operation could be applied
 * by defining a transform_instruction() callback that examined and changed
 * the instruction src/dest regs.
 *
 * \return new tgsi tokens, or NULL on failure
 */
struct tgsi_token *
tgsi_transform_shader(const struct tgsi_token *tokens_in,
                      uint initial_tokens_len,
                      struct tgsi_transform_context *ctx)
{
   boolean first_instruction = TRUE;
   boolean epilog_emitted = FALSE;
   int cond_stack = 0;
   int call_stack = 0;

   /* input shader */
   struct tgsi_parse_context parse;

   /* output shader */
   struct tgsi_processor *processor;

   /* Always include space for the header. */
   initial_tokens_len = MAX2(initial_tokens_len, 2);

   /**
    ** callback context init
    **/
   ctx->emit_instruction = emit_instruction;
   ctx->emit_declaration = emit_declaration;
   ctx->emit_immediate = emit_immediate;
   ctx->emit_property = emit_property;
   ctx->tokens_out = tgsi_alloc_tokens(initial_tokens_len);
   ctx->max_tokens_out = initial_tokens_len;
   ctx->fail = false;

   if (!ctx->tokens_out) {
      mesa_loge("failed to allocate %d tokens\n", initial_tokens_len);
      return NULL;
   }

   /**
    ** Setup to begin parsing input shader
    **/
   if (tgsi_parse_init( &parse, tokens_in ) != TGSI_PARSE_OK) {
      debug_printf("tgsi_parse_init() failed in tgsi_transform_shader()!\n");
      return NULL;
   }
   ctx->processor = parse.FullHeader.Processor.Processor;

   /**
    **  Setup output shader
    **/
   ctx->header = (struct tgsi_header *)ctx->tokens_out;
   *ctx->header = tgsi_build_header();

   processor = (struct tgsi_processor *) (ctx->tokens_out + 1);
   *processor = tgsi_build_processor( ctx->processor, ctx->header );

   ctx->ti = 2;


   /**
    ** Loop over incoming program tokens/instructions
    */
   while( !tgsi_parse_end_of_tokens( &parse ) ) {

      tgsi_parse_token( &parse );

      switch( parse.FullToken.Token.Type ) {
      case TGSI_TOKEN_TYPE_INSTRUCTION:
         {
            struct tgsi_full_instruction *fullinst
               = &parse.FullToken.FullInstruction;
            enum tgsi_opcode opcode = fullinst->Instruction.Opcode;

            if (first_instruction && ctx->prolog) {
               ctx->prolog(ctx);
            }

            /*
             * XXX Note: we handle the case of ret in main.
             * However, the output redirections done by transform
             * have their limits with control flow and will generally
             * not work correctly. e.g.
             * if (cond) {
             *    oColor = x;
             *    ret;
             * }
             * oColor = y;
             * end;
             * If the color output is redirected to a temp and modified
             * by a transform, this will not work (the oColor assignment
             * in the conditional will never make it to the actual output).
             */
            if ((opcode == TGSI_OPCODE_END || opcode == TGSI_OPCODE_RET) &&
                 call_stack == 0 && ctx->epilog && !epilog_emitted) {
               if (opcode == TGSI_OPCODE_RET && cond_stack != 0) {
                  assert(!"transform ignoring RET in main");
               } else {
                  assert(cond_stack == 0);
                  /* Emit caller's epilog */
                  ctx->epilog(ctx);
                  epilog_emitted = TRUE;
               }
               /* Emit END (or RET) */
               ctx->emit_instruction(ctx, fullinst);
            }
            else {
               switch (opcode) {
               case TGSI_OPCODE_IF:
               case TGSI_OPCODE_UIF:
               case TGSI_OPCODE_SWITCH:
               case TGSI_OPCODE_BGNLOOP:
                  cond_stack++;
                  break;
               case TGSI_OPCODE_CAL:
                  call_stack++;
                  break;
               case TGSI_OPCODE_ENDIF:
               case TGSI_OPCODE_ENDSWITCH:
               case TGSI_OPCODE_ENDLOOP:
                  assert(cond_stack > 0);
                  cond_stack--;
                  break;
               case TGSI_OPCODE_ENDSUB:
                  assert(call_stack > 0);
                  call_stack--;
                  break;
               case TGSI_OPCODE_BGNSUB:
               case TGSI_OPCODE_RET:
               default:
                  break;
               }
               if (ctx->transform_instruction)
                  ctx->transform_instruction(ctx, fullinst);
               else
                  ctx->emit_instruction(ctx, fullinst);
            }

            first_instruction = FALSE;
         }
         break;

      case TGSI_TOKEN_TYPE_DECLARATION:
         {
            struct tgsi_full_declaration *fulldecl
               = &parse.FullToken.FullDeclaration;

            if (ctx->transform_declaration)
               ctx->transform_declaration(ctx, fulldecl);
            else
               ctx->emit_declaration(ctx, fulldecl);
         }
         break;

      case TGSI_TOKEN_TYPE_IMMEDIATE:
         {
            struct tgsi_full_immediate *fullimm
               = &parse.FullToken.FullImmediate;

            if (ctx->transform_immediate)
               ctx->transform_immediate(ctx, fullimm);
            else
               ctx->emit_immediate(ctx, fullimm);
         }
         break;
      case TGSI_TOKEN_TYPE_PROPERTY:
         {
            struct tgsi_full_property *fullprop
               = &parse.FullToken.FullProperty;

            if (ctx->transform_property)
               ctx->transform_property(ctx, fullprop);
            else
               ctx->emit_property(ctx, fullprop);
         }
         break;

      default:
         assert( 0 );
      }
   }
   assert(call_stack == 0);

   tgsi_parse_free (&parse);

   if (ctx->fail) {
      tgsi_free_tokens(ctx->tokens_out);
      return NULL;
   }

   return ctx->tokens_out;
}


#include "tgsi_text.h"

extern int tgsi_transform_foo( struct tgsi_token *tokens_out,
                               uint max_tokens_out );

/* This function exists only so that tgsi_text_translate() doesn't get
 * magic-ed out of the libtgsi.a archive by the build system.  Don't
 * remove unless you know this has been fixed - check on mingw/scons
 * builds as well.
 */
int
tgsi_transform_foo( struct tgsi_token *tokens_out,
                    uint max_tokens_out )
{
   const char *text = 
      "FRAG\n"
      "DCL IN[0], COLOR, CONSTANT\n"
      "DCL OUT[0], COLOR\n"
      "  0: MOV OUT[0], IN[0]\n"
      "  1: END";
        
   return tgsi_text_translate( text,
                               tokens_out,
                               max_tokens_out );
}
