/*
 * Copyright © 2024 Imagination Technologies Ltd.
 *
 * SPDX-License-Identifier: MIT
 */

/**
 * \file pco_print.c
 *
 * \brief PCO printing functions.
 */

#include "pco.h"
#include "pco_builder.h"
#include "pco_common.h"
#include "pco_internal.h"
#include "util/bitscan.h"
#include "util/list.h"
#include "util/macros.h"
#include "util/u_hexdump.h"

#include <assert.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

typedef struct _pco_print_state {
   FILE *fp; /** The print target file pointer. */
   pco_shader *shader; /** The shader being printed. */
   unsigned indent; /** The current printing indent. */
   bool is_grouped; /** Whether the shader uses igrps. */
   bool verbose; /** Whether to print additional info. */
} pco_print_state;

/* Forward declarations. */
static void _pco_print_cf_node(pco_print_state *state, pco_cf_node *cf_node);
static void pco_print_block_name(pco_print_state *state, pco_block *block);
static void
pco_print_func_sig(pco_print_state *state, pco_func *func, bool call);

enum color_esc {
   ESC_RESET = 0,
   ESC_BLACK,
   ESC_RED,
   ESC_GREEN,
   ESC_YELLOW,
   ESC_BLUE,
   ESC_PURPLE,
   ESC_CYAN,
   ESC_WHITE,
   _ESC_COUNT,
};

static
const char *color_esc[2][_ESC_COUNT] = {
   [0] = {
      [ESC_RESET] = "",
      [ESC_BLACK] = "",
      [ESC_RED] = "",
      [ESC_GREEN] = "",
      [ESC_YELLOW] = "",
      [ESC_BLUE] = "",
      [ESC_PURPLE] = "",
      [ESC_CYAN] = "",
      [ESC_WHITE] = "",
   },
   [1] = {
      [ESC_RESET] = "\033[0m",
      [ESC_BLACK] = "\033[0;30m",
      [ESC_RED] = "\033[0;31m",
      [ESC_GREEN] = "\033[0;32m",
      [ESC_YELLOW] = "\033[0;33m",
      [ESC_BLUE] = "\033[0;34m",
      [ESC_PURPLE] = "\033[0;35m",
      [ESC_CYAN] = "\033[0;36m",
      [ESC_WHITE] = "\033[0;37m",
   },
};

static inline void RESET(pco_print_state *state)
{
   fputs(color_esc[pco_color][ESC_RESET], state->fp);
}

static inline void BLACK(pco_print_state *state)
{
   fputs(color_esc[pco_color][ESC_BLACK], state->fp);
}

static inline void RED(pco_print_state *state)
{
   fputs(color_esc[pco_color][ESC_RED], state->fp);
}

static inline void GREEN(pco_print_state *state)
{
   fputs(color_esc[pco_color][ESC_GREEN], state->fp);
}

static inline void YELLOW(pco_print_state *state)
{
   fputs(color_esc[pco_color][ESC_YELLOW], state->fp);
}

static inline void BLUE(pco_print_state *state)
{
   fputs(color_esc[pco_color][ESC_BLUE], state->fp);
}

static inline void PURPLE(pco_print_state *state)
{
   fputs(color_esc[pco_color][ESC_PURPLE], state->fp);
}

static inline void CYAN(pco_print_state *state)
{
   fputs(color_esc[pco_color][ESC_CYAN], state->fp);
}

static inline void WHITE(pco_print_state *state)
{
   fputs(color_esc[pco_color][ESC_WHITE], state->fp);
}

inline static const char *true_false_str(bool b)
{
   return b ? "true" : "false";
}

static void
_pco_printf(pco_print_state *state, bool nl, const char *fmt, va_list args)
{
   if (nl)
      for (unsigned u = 0; u < state->indent; ++u)
         fputs("    ", state->fp);

   vfprintf(state->fp, fmt, args);
}

/**
 * \brief Formatted print.
 *
 * \param[in] state Print state.
 * \param[in] fmt Print format.
 */
PRINTFLIKE(2, 3)
static void pco_printf(pco_print_state *state, const char *fmt, ...)
{
   va_list args;
   va_start(args, fmt);
   _pco_printf(state, false, fmt, args);
   va_end(args);
}

/**
 * \brief Formatted print, with indentation.
 *
 * \param[in] state Print state.
 * \param[in] fmt Print format.
 */
PRINTFLIKE(2, 3)
static void pco_printfi(pco_print_state *state, const char *fmt, ...)
{
   va_list args;
   va_start(args, fmt);
   _pco_printf(state, true, fmt, args);
   va_end(args);
}

/**
 * \brief Returns a space if the string is not empty.
 *
 * \param[in] str String.
 * \return A space if the string is not empty, else an empty string.
 */
static inline const char *space_if_str(const char *str)
{
   return str[0] != '\0' ? " " : "";
}

/**
 * \brief Print PCO reference flags/modifiers.
 *
 * \param[in] state Print state.
 * \param[in] ref PCO reference.
 */
static void pco_print_ref_mods(pco_print_state *state, pco_ref ref)
{
   if (ref.oneminus)
      pco_printf(state, ".oneminus");
   if (ref.clamp)
      pco_printf(state, ".clamp");
   if (ref.flr)
      pco_printf(state, ".flr");
   if (ref.abs)
      pco_printf(state, ".abs");
   if (ref.neg)
      pco_printf(state, ".neg");

   u_foreach_bit (e, ref.elem) {
      pco_printf(state, ".e%u", e);
   }
}

/**
 * \brief Print PCO reference color.
 *
 * \param[in] state Print state.
 * \param[in] ref PCO reference.
 */
static void pco_print_ref_color(pco_print_state *state, pco_ref ref)
{
   switch (ref.type) {
   case PCO_REF_TYPE_NULL:
      return;

   case PCO_REF_TYPE_SSA:
   case PCO_REF_TYPE_REG:
   case PCO_REF_TYPE_IDX_REG:
      YELLOW(state);
      return;

   case PCO_REF_TYPE_IMM:
      BLUE(state);
      return;

   case PCO_REF_TYPE_IO:
   case PCO_REF_TYPE_PRED:
   case PCO_REF_TYPE_DRC:
      WHITE(state);
      return;

   default:
      break;
   }

   UNREACHABLE("");
}

/**
 * \brief Print PCO reference.
 *
 * \param[in] state Print state.
 * \param[in] ref PCO reference.
 */
static void _pco_print_ref(pco_print_state *state, pco_ref ref)
{
   pco_print_ref_color(state, ref);
   pco_printf(state, "%s", pco_ref_type_str(ref.type));

   switch (ref.type) {
   case PCO_REF_TYPE_NULL:
      break;

   case PCO_REF_TYPE_SSA:
      pco_printf(state, "%u", ref.val);
      break;

   case PCO_REF_TYPE_REG:
      pco_printf(state, "%s%u", pco_reg_class_str(ref.reg_class), ref.val);
      break;

   case PCO_REF_TYPE_IDX_REG:
      _pco_print_ref(state, pco_ref_get_idx_pointee(ref));
      pco_print_ref_color(state, ref);
      pco_printf(state, "[idx%u", ref.idx_reg.num);
      break;

   case PCO_REF_TYPE_IMM:
      assert(pco_ref_is_scalar(ref));
      switch (ref.dtype) {
      case PCO_DTYPE_ANY:
         pco_printf(state, "0x%" PRIx64, pco_ref_get_imm(ref));
         break;

      case PCO_DTYPE_UNSIGNED:
         pco_printf(state, "%" PRIu64, pco_ref_get_imm(ref));
         break;

      case PCO_DTYPE_SIGNED:
         pco_printf(state, "%" PRId64, pco_ref_get_imm(ref));
         break;

      case PCO_DTYPE_FLOAT:
         pco_printf(state, "%f", uif(pco_ref_get_imm(ref)));
         break;

      default:
         UNREACHABLE("");
      }
      pco_printf(state, "%s", pco_dtype_str(ref.dtype));
      break;

   case PCO_REF_TYPE_IO:
      assert(pco_ref_is_scalar(ref));
      pco_printf(state, "%s", pco_io_str(ref.val));
      break;

   case PCO_REF_TYPE_PRED:
      assert(pco_ref_is_scalar(ref));
      pco_printf(state, "%s", pco_pred_str(ref.val));
      break;

   case PCO_REF_TYPE_DRC:
      assert(pco_ref_is_scalar(ref));
      pco_printf(state, "%s", pco_drc_str(ref.val));
      break;

   default:
      UNREACHABLE("");
   }

   unsigned chans = pco_ref_get_chans(ref);
   if (chans > 1 && !pco_ref_is_ssa(ref))
      pco_printf(state, "..%u", ref.val + chans - 1);

   if (ref.type == PCO_REF_TYPE_IDX_REG)
      pco_printf(state, "]");

   RESET(state);

   /* Modifiers. */
   pco_print_ref_mods(state, ref);
}

/**
 * \brief Print PCO reference specification.
 *
 * \param[in] state Print state.
 * \param[in] ref PCO reference.
 */
static void pco_print_ref_spec(pco_print_state *state, pco_ref ref)
{
   pco_printf(state,
              "(%s%ux%u)",
              pco_dtype_str(pco_ref_get_dtype(ref)),
              pco_ref_get_bits(ref),
              pco_ref_get_chans(ref));
}

/**
 * \brief Print PCO phi source.
 *
 * \param[in] state Print state.
 * \param[in] phi_src PCO phi source.
 */
static void pco_print_phi_src(pco_print_state *state, pco_phi_src *phi_src)
{
   pco_print_block_name(state, phi_src->pred);
   pco_printf(state, ": ");
   _pco_print_ref(state, phi_src->ref);
}

/**
 * \brief Print PCO instruction modifiers.
 *
 * \param[in] state Print state.
 * \param[in] op_info The instruction op info.
 * \param[in] instr PCO instruction.
 * \param[in] print_early Whether the mods are being printed before the
 *            instruction name.
 */
static void pco_print_instr_mods(pco_print_state *state,
                                 const struct pco_op_info *op_info,
                                 pco_instr *instr,
                                 bool print_early)
{
   u_foreach_bit64 (op_mod, op_info->mods) {
      const struct pco_op_mod_info *mod_info = &pco_op_mod_info[op_mod];
      if (mod_info->print_early != print_early)
         continue;

      uint32_t val = pco_instr_get_mod(instr, op_mod);

      switch (mod_info->type) {
      case PCO_MOD_TYPE_BOOL:
         if (val && strlen(mod_info->str)) {
            if (print_early)
               pco_printf(state, "%s ", mod_info->str);
            else
               pco_printf(state, ".%s", mod_info->str);
         }
         break;

      case PCO_MOD_TYPE_UINT:
         if ((!mod_info->nzdefault || val != mod_info->nzdefault) &&
             strlen(mod_info->str)) {
            if (print_early)
               pco_printf(state, "%s%u ", mod_info->str, val);
            else
               pco_printf(state, "%s%u", mod_info->str, val);
         }
         break;

      case PCO_MOD_TYPE_ENUM:
         if (mod_info->is_bitset) {
            u_foreach_bit (bit, val) {
               pco_printf(state, ".%s", mod_info->strs[1U << bit]);
            }
         } else {
            if (strlen(mod_info->strs[val])) {
               if (print_early)
                  pco_printf(state, "%s ", mod_info->strs[val]);
               else
                  pco_printf(state, ".%s", mod_info->strs[val]);
            }
         }
         break;

      default:
         UNREACHABLE("");
      }
   }
}

/**
 * \brief Print PCO instruction.
 *
 * \param[in] state Print state.
 * \param[in] instr PCO instruction.
 */
static void _pco_print_instr(pco_print_state *state, pco_instr *instr)
{
   const struct pco_op_info *info = &pco_op_info[instr->op];

   if (!state->is_grouped)
      pco_printfi(state, "%04u: ", instr->index);

   /* Early mods. */
   pco_print_instr_mods(state, info, instr, true);

   if (info->type == PCO_OP_TYPE_PSEUDO)
      RED(state);
   else
      GREEN(state);
   pco_printf(state, "%s", info->str);
   RESET(state);

   /* "Late" mods. */
   pco_print_instr_mods(state, info, instr, false);

   bool printed = false;

   /* Destinations. */
   for (unsigned d = 0; d < instr->num_dests; ++d) {
      if (printed)
         pco_printf(state, ",");
      pco_printf(state, " ");
      _pco_print_ref(state, instr->dest[d]);
      printed = true;
   }

   /* Special parameters. */
   if (info->has_target_cf_node) {
      if (printed)
         pco_printf(state, ",");
      pco_printf(state, " ");

      switch (instr->target_cf_node->type) {
      case PCO_CF_NODE_TYPE_BLOCK: {
         pco_block *target_block = pco_cf_node_as_block(instr->target_cf_node);
         pco_printf(state, " ");
         pco_print_block_name(state, target_block);
         break;
      }

      case PCO_CF_NODE_TYPE_FUNC: {
         pco_func *target_func = pco_cf_node_as_func(instr->target_cf_node);
         pco_printf(state, " ");
         pco_print_func_sig(state, target_func, true);
         break;
      }

      default:
         UNREACHABLE("");
      }
      printed = true;
   } else if (!list_is_empty(&instr->phi_srcs)) {
      pco_foreach_phi_src_in_instr (phi_src, instr) {
         if (printed)
            pco_printf(state, ",");
         pco_printf(state, " ");
         pco_print_phi_src(state, phi_src);
         printed = true;
      }
   }

   /* Sources. */
   for (unsigned s = 0; s < instr->num_srcs; ++s) {
      if (printed)
         pco_printf(state, ",");
      pco_printf(state, " ");
      _pco_print_ref(state, instr->src[s]);
      printed = true;
   }
   pco_printf(state, ";");

   /* Spec for destinations. */
   if (state->verbose && !state->is_grouped && instr->num_dests) {
      pco_printf(state, " /*");

      printed = false;
      for (unsigned d = 0; d < instr->num_dests; ++d) {
         if (printed)
            pco_printf(state, ",");
         pco_printf(state, " ");

         _pco_print_ref(state, instr->dest[d]);
         pco_printf(state, ":");
         pco_print_ref_spec(state, instr->dest[d]);

         printed = true;
      }

      pco_printf(state, " */");
   }

   if (state->verbose && instr->comment)
      pco_printf(state, " /* %s */", instr->comment);
}

/**
 * \brief Print the name of a phase.
 *
 * \param[in] state Print state.
 * \param[in] alutype ALU type.
 * \param[in] phase Phase.
 */
static void _pco_print_phase(pco_print_state *state,
                             enum pco_alutype alutype,
                             enum pco_op_phase phase)
{
   switch (alutype) {
   case PCO_ALUTYPE_MAIN:
      pco_printf(state, "%s", pco_op_phase_str(phase));
      return;

   case PCO_ALUTYPE_BITWISE:
      pco_printf(state, "p%c", '0' + phase);
      return;

   case PCO_ALUTYPE_CONTROL:
      pco_printf(state, "ctrl");
      return;

   default:
      break;
   }
   UNREACHABLE("");
}

/**
 * \brief Print phases present in a PCO instruction group.
 *
 * \param[in] state Print state.
 * \param[in] igrp PCO instruction group.
 */
static void pco_print_igrp_phases(pco_print_state *state, pco_igrp *igrp)
{
   bool printed = false;
   for (enum pco_op_phase phase = 0; phase < _PCO_OP_PHASE_COUNT; ++phase) {
      if (!igrp->instrs[phase])
         continue;

      if (printed)
         pco_printf(state, ",");

      _pco_print_phase(state, igrp->hdr.alutype, phase);

      printed = true;
   }
}

/**
 * \brief Print the sources in a PCO instruction group.
 *
 * \param[in] state Print state.
 * \param[in] igrp PCO instruction group.
 * \param[in] upper Whether to print the upper sources.
 */
static void
pco_print_igrp_srcs(pco_print_state *state, pco_igrp *igrp, bool upper)
{
   unsigned offset = upper ? ROGUE_ALU_INPUT_GROUP_SIZE : 0;
   bool printed = false;
   for (unsigned u = 0; u < ROGUE_ALU_INPUT_GROUP_SIZE; ++u) {
      const pco_ref *src = &igrp->srcs.s[u + offset];
      if (pco_ref_is_null(*src))
         continue;

      if (printed)
         pco_printf(state, ", ");

      pco_printf(state, "s%u = ", u + offset);
      _pco_print_ref(state, *src);
      printed = true;
   }
}

/**
 * \brief Print the internal source selector in a PCO instruction group.
 *
 * \param[in] state Print state.
 * \param[in] igrp PCO instruction group.
 */
static void pco_print_igrp_iss(pco_print_state *state, pco_igrp *igrp)
{
   bool printed = false;
   for (unsigned u = 0; u < ROGUE_MAX_ALU_INTERNAL_SOURCES; ++u) {
      const pco_ref *iss = &igrp->iss.is[u];
      if (pco_ref_is_null(*iss))
         continue;

      if (printed)
         pco_printf(state, ", ");

      pco_printf(state, "is%u = ", u);
      _pco_print_ref(state, *iss);
      printed = true;
   }
}

/**
 * \brief Print the dests in a PCO instruction group.
 *
 * \param[in] state Print state.
 * \param[in] igrp PCO instruction group.
 */
static void pco_print_igrp_dests(pco_print_state *state, pco_igrp *igrp)
{
   bool printed = false;
   for (unsigned u = 0; u < ROGUE_MAX_ALU_OUTPUTS; ++u) {
      const pco_ref *dest = &igrp->dests.w[u];
      if (pco_ref_is_null(*dest))
         continue;

      if (printed)
         pco_printf(state, ", ");

      pco_printf(state, "w%u = ", u);
      _pco_print_ref(state, *dest);
      printed = true;
   }
}

/**
 * \brief Print PCO instruction group.
 *
 * \param[in] state Print state.
 * \param[in] igrp PCO instruction group.
 */
static void _pco_print_igrp(pco_print_state *state, pco_igrp *igrp)
{
   bool printed = false;

   pco_printfi(state,
               "%04u:%s%s { ",
               igrp->index,
               space_if_str(pco_cc_str(igrp->hdr.cc)),
               pco_cc_str(igrp->hdr.cc));

   if (state->verbose) {
      unsigned padding_size =
         igrp->enc.len.word_padding + igrp->enc.len.align_padding;
      unsigned unpadded_size = igrp->enc.len.total - padding_size;

      pco_printf(state, "/* @ 0x%08x [", igrp->enc.offset);
      pco_print_igrp_phases(state, igrp);
      pco_printf(state,
                 "] len: %u, pad: %u, total: %u, da: %u",
                 unpadded_size,
                 padding_size,
                 igrp->enc.len.total,
                 igrp->hdr.da);

      if (igrp->hdr.w0p)
         pco_printf(state, ", w0p");

      if (igrp->hdr.w1p)
         pco_printf(state, ", w1p");

      pco_printf(state, " */\n");
      ++state->indent;

      pco_printfi(state,
                  "type %s /* hdr bytes: %u */\n",
                  pco_alutype_str(igrp->hdr.alutype),
                  igrp->enc.len.hdr);
   }

   if (igrp->hdr.alutype != PCO_ALUTYPE_CONTROL && igrp->hdr.rpt > 1) {
      if (state->verbose)
         pco_printfi(state, "repeat %u\n", igrp->hdr.rpt);
      else
         pco_printf(state, "repeat %u ", igrp->hdr.rpt);

      printed = true;
   }

   if (igrp->enc.len.lower_srcs) {
      if (state->verbose)
         pco_printfi(state, "%s", "");

      if (!pco_igrp_srcs_unset(igrp, false)) {
         if (!state->verbose && printed)
            pco_printf(state, ", ");

         pco_print_igrp_srcs(state, igrp, false);

         if (state->verbose)
            pco_printf(state, " ");
      }

      if (state->verbose)
         pco_printf(state,
                    "/* lo src bytes: %u */\n",
                    igrp->enc.len.lower_srcs);

      printed = true;
   }

   if (igrp->enc.len.upper_srcs) {
      if (state->verbose)
         pco_printfi(state, "%s", "");

      if (!pco_igrp_srcs_unset(igrp, true)) {
         if (!state->verbose && printed)
            pco_printf(state, ", ");

         pco_print_igrp_srcs(state, igrp, true);

         if (state->verbose)
            pco_printf(state, " ");
      }

      if (state->verbose)
         pco_printf(state,
                    "/* up src bytes: %u */\n",
                    igrp->enc.len.upper_srcs);

      printed = true;
   }

   if (igrp->enc.len.iss) {
      if (state->verbose)
         pco_printfi(state, "%s", "");

      if (!pco_igrp_iss_unset(igrp)) {
         if (!state->verbose && printed)
            pco_printf(state, ", ");

         pco_print_igrp_iss(state, igrp);

         if (state->verbose)
            pco_printf(state, " ");
      }

      if (state->verbose)
         pco_printf(state, "/* iss bytes: %u */\n", igrp->enc.len.iss);

      printed = true;
   }

   for (enum pco_op_phase phase = 0; phase < _PCO_OP_PHASE_COUNT; ++phase) {
      if (!igrp->instrs[phase])
         continue;

      if (state->verbose)
         pco_printfi(state, "%s", "");
      else if (printed)
         pco_printf(state, " ");

      _pco_print_phase(state, igrp->hdr.alutype, phase);
      pco_printf(state, ": ");
      _pco_print_instr(state, igrp->instrs[phase]);

      if (state->verbose) {
         pco_printf(state, " /* ");
         _pco_print_phase(state, igrp->hdr.alutype, phase);
         pco_printf(state, " bytes: %u */\n", igrp->enc.len.instrs[phase]);
      }

      printed = true;
   }

   if (igrp->enc.len.dests) {
      if (state->verbose)
         pco_printfi(state, "%s", "");

      if (!pco_igrp_dests_unset(igrp)) {
         if (!state->verbose && printed)
            pco_printf(state, " ");

         pco_print_igrp_dests(state, igrp);

         if (state->verbose)
            pco_printf(state, " ");
      }

      if (state->verbose)
         pco_printf(state, "/* dest bytes: %u */\n", igrp->enc.len.dests);

      printed = true;
   }

   if (state->verbose)
      --state->indent;
   else
      pco_printf(state, " ");

   if (state->verbose)
      pco_printfi(state, "}");
   else
      pco_printf(state, "}");

   if (igrp->hdr.olchk)
      pco_printf(state, ".olchk");

   if (igrp->hdr.alutype != PCO_ALUTYPE_CONTROL) {
      if (igrp->hdr.atom)
         pco_printf(state, ".atom");

      if (igrp->hdr.end)
         pco_printf(state, ".end");
   }

   if (state->verbose && igrp->comment)
      pco_printf(state, " /* %s */", igrp->comment);

   pco_printf(state, "\n");
}

/**
 * \brief Print PCO block name.
 *
 * \param[in] state Print state.
 * \param[in] block PCO block.
 */
static void pco_print_block_name(pco_print_state *state, pco_block *block)
{
   pco_printf(state, "B%u", block->index);
}

/**
 * \brief Print PCO block.
 *
 * \param[in] state Print state.
 * \param[in] block PCO block.
 */
static void pco_print_block(pco_print_state *state, pco_block *block)
{
   pco_printfi(state, "block ");
   pco_print_block_name(state, block);
   pco_printfi(state, ":\n");
   ++state->indent;

   if (state->is_grouped) {
      pco_foreach_igrp_in_block (igrp, block) {
         _pco_print_igrp(state, igrp);
      }
   } else {
      pco_foreach_instr_in_block (instr, block) {
         _pco_print_instr(state, instr);
         pco_printf(state, "\n");
      }
   }

   --state->indent;
}

/**
 * \brief Print PCO if name.
 *
 * \param[in] state Print state.
 * \param[in] pif PCO if.
 */
static void pco_print_if_name(pco_print_state *state, pco_if *pif)
{
   pco_printf(state, "I%u", pif->index);
}

/**
 * \brief Print PCO if.
 *
 * \param[in] state Print state.
 * \param[in] pif PCO if.
 */
static void pco_print_if(pco_print_state *state, pco_if *pif)
{
   pco_printfi(state, "if ");
   pco_print_if_name(state, pif);
   pco_printfi(state, " (");
   _pco_print_ref(state, pif->cond);
   pco_printf(state, ") {\n");
   ++state->indent;

   pco_foreach_cf_node_in_if_then (cf_node, pif) {
      _pco_print_cf_node(state, cf_node);
   }

   --state->indent;
   if (list_is_empty(&pif->else_body)) {
      pco_printf(state, "}\n");
      return;
   }

   pco_printf(state, "} else {\n");
   ++state->indent;

   pco_foreach_cf_node_in_if_else (cf_node, pif) {
      _pco_print_cf_node(state, cf_node);
   }

   --state->indent;
   pco_printf(state, "}\n");
}

/**
 * \brief Print PCO loop name.
 *
 * \param[in] state Print state.
 * \param[in] loop PCO loop.
 */
static void pco_print_loop_name(pco_print_state *state, pco_loop *loop)
{
   pco_printf(state, "L%u", loop->index);
}

/**
 * \brief Print PCO loop.
 *
 * \param[in] state Print state.
 * \param[in] loop PCO loop.
 */
static void pco_print_loop(pco_print_state *state, pco_loop *loop)
{
   pco_printfi(state, "loop ");
   pco_print_loop_name(state, loop);
   pco_printfi(state, " {\n");
   ++state->indent;

   pco_foreach_cf_node_in_loop (cf_node, loop) {
      _pco_print_cf_node(state, cf_node);
   }

   --state->indent;
   pco_printf(state, "}\n");
}

/**
 * \brief Print PCO function signature.
 *
 * \param[in] state Print state.
 * \param[in] func PCO function.
 * \param[in] call Whether the signature is for a function call/reference.
 */
static void
pco_print_func_sig(pco_print_state *state, pco_func *func, bool call)
{
   if (!call) {
      switch (func->type) {
      case PCO_FUNC_TYPE_CALLABLE:
         break;

      case PCO_FUNC_TYPE_PREAMBLE:
         pco_printf(state, " PREAMBLE");
         break;

      case PCO_FUNC_TYPE_ENTRYPOINT:
         pco_printf(state, " ENTRY");
         break;

      case PCO_FUNC_TYPE_PHASE_CHANGE:
         pco_printf(state, " PHASE CHANGE");
         break;

      default:
         UNREACHABLE("");
      }
   }

   if (func->name)
      pco_printf(state, " %s", func->name);
   else
      pco_printf(state, " _%u", func->index);

   pco_printf(state, "(");

   if (!call) {
      /* TODO: Function parameter support. */
      assert(func->num_params == 0 && func->params == NULL);
      if (!func->num_params)
         pco_printf(state, "void");
   }

   pco_printf(state, ")");
}

/**
 * \brief Print PCO function.
 *
 * \param[in] state Print state.
 * \param[in] func PCO function.
 */
static void pco_print_func(pco_print_state *state, pco_func *func)
{
   pco_printfi(state, "func");
   pco_print_func_sig(state, func, false);
   if (state->is_grouped)
      pco_printf(state, " /* temps: %u */", func->temps);
   pco_printf(state, "\n");

   pco_printfi(state, "{\n");

   pco_foreach_cf_node_in_func (cf_node, func) {
      _pco_print_cf_node(state, cf_node);
   }

   pco_printfi(state, "}\n");
}

/**
 * \brief Print PCO control flow node.
 *
 * \param[in] state Print state.
 * \param[in] cf_node PCO control flow node.
 */
static void _pco_print_cf_node(pco_print_state *state, pco_cf_node *cf_node)
{
   switch (cf_node->type) {
   case PCO_CF_NODE_TYPE_BLOCK:
      return pco_print_block(state, pco_cf_node_as_block(cf_node));

   case PCO_CF_NODE_TYPE_IF:
      return pco_print_if(state, pco_cf_node_as_if(cf_node));

   case PCO_CF_NODE_TYPE_LOOP:
      return pco_print_loop(state, pco_cf_node_as_loop(cf_node));

   case PCO_CF_NODE_TYPE_FUNC:
      return pco_print_func(state, pco_cf_node_as_func(cf_node));

   default:
      break;
   }

   UNREACHABLE("");
}

/**
 * \brief Print PCO shader info.
 *
 * \param[in] state Print state.
 * \param[in] shader PCO shader.
 */
static void _pco_print_shader_info(pco_print_state *state, pco_shader *shader)
{
   if (shader->name)
      pco_printfi(state, "name: \"%s\"\n", shader->name);
   pco_printfi(state, "stage: %s\n", gl_shader_stage_name(shader->stage));
   pco_printfi(state, "internal: %s\n", true_false_str(shader->is_internal));
   /* TODO: more info/stats, e.g. temps/other regs used, etc.? */
}

/**
 * \brief Print PCO shader.
 *
 * \param[in] shader PCO shader.
 * \param[in] fp Print target file pointer.
 * \param[in] when When the printing is being performed.
 */
void pco_print_shader(pco_shader *shader, FILE *fp, const char *when)
{
   pco_print_state state = {
      .fp = fp,
      .shader = shader,
      .indent = 0,
      .is_grouped = shader->is_grouped,
      .verbose = PCO_DEBUG_PRINT(VERBOSE),
   };

   if (when)
      fprintf(fp, "shader ir %s:\n", when);
   else
      fputs("shader ir:\n", fp);

   _pco_print_shader_info(&state, shader);

   pco_foreach_func_in_shader (func, shader) {
      pco_print_func(&state, func);
   }
}

/**
 * \brief Print PCO shader binary.
 *
 * \param[in] shader PCO shader.
 * \param[in] fp Print target file pointer.
 * \param[in] when When the printing is being performed.
 */
void pco_print_binary(pco_shader *shader, FILE *fp, const char *when)
{
   pco_print_state state = {
      .fp = fp,
      .shader = shader,
      .indent = 0,
      .is_grouped = shader->is_grouped,
      .verbose = PCO_DEBUG_PRINT(VERBOSE),
   };

   if (when)
      fprintf(fp, "shader binary %s:\n", when);
   else
      fputs("shader binary:", fp);

   _pco_print_shader_info(&state, shader);

   return u_hexdump(fp,
                    pco_shader_binary_data(shader),
                    pco_shader_binary_size(shader),
                    false);
}

/**
 * \brief Print PCO reference (wrapper).
 *
 * \param[in] shader PCO shader.
 * \param[in] ref PCO reference.
 */
void pco_print_ref(pco_shader *shader, pco_ref ref)
{
   pco_print_state state = {
      .fp = stdout,
      .shader = shader,
      .indent = 0,
      .is_grouped = shader->is_grouped,
      .verbose = false,
   };
   return _pco_print_ref(&state, ref);
}

/**
 * \brief Print PCO instruction (wrapper).
 *
 * \param[in] shader PCO shader.
 * \param[in] instr PCO instruction.
 */
void pco_print_instr(pco_shader *shader, pco_instr *instr)
{
   pco_print_state state = {
      .fp = stdout,
      .shader = shader,
      .indent = 0,
      .is_grouped = shader->is_grouped,
      .verbose = false,
   };
   return _pco_print_instr(&state, instr);
}

/**
 * \brief Print PCO instruction group (wrapper).
 *
 * \param[in] shader PCO shader.
 * \param[in] igrp PCO instruction group.
 */
void pco_print_igrp(pco_shader *shader, pco_igrp *igrp)
{
   pco_print_state state = {
      .fp = stdout,
      .shader = shader,
      .indent = 0,
      .is_grouped = shader->is_grouped,
      .verbose = false,
   };
   return _pco_print_igrp(&state, igrp);
}

/**
 * \brief Print PCO control flow node name (wrapper).
 *
 * \param[in] shader PCO shader.
 * \param[in] cf_node PCO control flow node.
 */
void pco_print_cf_node_name(pco_shader *shader, pco_cf_node *cf_node)
{
   pco_print_state state = {
      .fp = stdout,
      .shader = shader,
      .indent = 0,
      .is_grouped = shader->is_grouped,
      .verbose = false,
   };

   switch (cf_node->type) {
   case PCO_CF_NODE_TYPE_BLOCK:
      pco_printf(&state, "block ");
      return pco_print_block_name(&state, pco_cf_node_as_block(cf_node));

   case PCO_CF_NODE_TYPE_IF:
      pco_printf(&state, "if ");
      return pco_print_if_name(&state, pco_cf_node_as_if(cf_node));

   case PCO_CF_NODE_TYPE_LOOP:
      pco_printf(&state, "loop ");
      return pco_print_loop_name(&state, pco_cf_node_as_loop(cf_node));

   case PCO_CF_NODE_TYPE_FUNC:
      pco_printf(&state, "func");
      return pco_print_func_sig(&state, pco_cf_node_as_func(cf_node), true);

   default:
      break;
   }

   UNREACHABLE("");
}

/**
 * \brief Print PCO shader info (wrapper).
 *
 * \param[in] shader PCO shader.
 */
void pco_print_shader_info(pco_shader *shader)
{
   pco_print_state state = {
      .fp = stdout,
      .shader = shader,
      .indent = 0,
      .is_grouped = shader->is_grouped,
      .verbose = false,
   };
   return _pco_print_shader_info(&state, shader);
}

/**
 * \brief Print the name of a phase(wrapper).
 *
 * \param[in] shader PCO shader.
 * \param[in] alutype ALU type.
 * \param[in] phase Phase.
 */
void pco_print_phase(pco_shader *shader,
                     enum pco_alutype alutype,
                     enum pco_op_phase phase)
{
   pco_print_state state = {
      .fp = stdout,
      .shader = shader,
      .indent = 0,
      .is_grouped = shader->is_grouped,
      .verbose = false,
   };

   return _pco_print_phase(&state, alutype, phase);
}
