/**
 * @file combine_tools.cpp
 *
 * @author  Guy Maurel
 * @license GPL v2+
 * extract from combine.cpp
 */

#include "combine_tools.h"

#include "unc_ctype.h"
#include "uncrustify.h"


bool can_be_full_param(Chunk *start, Chunk *end)
{
   LOG_FUNC_ENTRY();

   LOG_FMT(LFPARAM, "%s(%d): start->Text() is '%s', type is %s\n",
           __func__, __LINE__, start->Text(), get_token_name(start->GetType()));
   LOG_FMT(LFPARAM, "%s(%d): end->Text()   is '%s', type is %s\n",
           __func__, __LINE__, end->Text(), get_token_name(end->GetType()));

   int   word_count     = 0;
   int   type_count     = 0;
   Chunk *pc            = Chunk::NullChunkPtr;
   Chunk *first_word    = Chunk::NullChunkPtr;
   bool  first_word_set = false;

   for (pc = start;
        pc->IsNotNullChunk() && pc != end;
        pc = pc->GetNextNcNnl(E_Scope::PREPROC))
   {
      LOG_FMT(LFPARAM, "%s(%d): pc->Text() is '%s', type is %s\n",
              __func__, __LINE__, pc->Text(), get_token_name(pc->GetType()));

      if (  pc->Is(CT_QUALIFIER)
         || pc->Is(CT_STRUCT)
         || pc->Is(CT_ENUM)
         || pc->Is(CT_UNION)
         || pc->Is(CT_TYPENAME))
      {
         LOG_FMT(LFPARAM, "%s(%d): <== %s! (yes)\n",
                 __func__, __LINE__, get_token_name(pc->GetType()));
         return(true);
      }

      if (  pc->Is(CT_WORD)
         || pc->Is(CT_TYPE))
      {
         ++word_count;

         if (!first_word_set)
         {
            first_word     = pc;
            first_word_set = true;
         }

         if (pc->Is(CT_TYPE))
         {
            ++type_count;
         }
      }
      else if (  pc->Is(CT_MEMBER)
              || pc->Is(CT_DC_MEMBER))
      {
         if (word_count > 0)
         {
            --word_count;
         }
      }
      else if (  pc != start
              && pc->IsPointerOperator())
      {
         // chunk is OK
      }
      else if (pc->Is(CT_ASSIGN))
      {
         // chunk is OK (default values)
         break;
      }
      else if (pc->Is(CT_ANGLE_OPEN))
      {
         LOG_FMT(LFPARAM, "%s(%d): <== template\n",
                 __func__, __LINE__);

         return(true);
      }
      else if (pc->Is(CT_ELLIPSIS))
      {
         LOG_FMT(LFPARAM, "%s(%d): <== ellipsis\n",
                 __func__, __LINE__);

         return(true);
      }
      else if (  word_count == 0
              && pc->Is(CT_PAREN_OPEN))
      {
         // Check for old-school func proto param '(type)'
         Chunk *tmp1 = pc->GetClosingParen(E_Scope::PREPROC);

         if (tmp1->IsNullChunk())
         {
            return(false);
         }
         Chunk *tmp2 = tmp1->GetNextNcNnl(E_Scope::PREPROC);

         if (tmp2->IsNullChunk())
         {
            return(false);
         }

         if (  tmp2->Is(CT_COMMA)
            || tmp2->IsParenClose())
         {
            do
            {
               pc = pc->GetNextNcNnl(E_Scope::PREPROC);

               if (pc->IsNullChunk())
               {
                  return(false);
               }
               LOG_FMT(LFPARAM, "%s(%d): pc->Text() is '%s', type is %s\n",
                       __func__, __LINE__, pc->Text(), get_token_name(pc->GetType()));
            } while (pc != tmp1);

            // reset some vars to allow [] after parens
            word_count = 1;
            type_count = 1;
         }
         else
         {
            LOG_FMT(LFPARAM, "%s(%d): <== '%s' not fcn type!\n",
                    __func__, __LINE__, get_token_name(pc->GetType()));
            return(false);
         }
      }
      else if (  (  word_count == 1
                 || (word_count == type_count))
              && pc->Is(CT_PAREN_OPEN))
      {
         // Check for func proto param 'void (*name)' or 'void (*name)(params)' or 'void (^name)(params)'
         // <name> can be optional
         Chunk *tmp1 = pc->GetNextNcNnl(E_Scope::PREPROC);

         if (tmp1->IsNullChunk())
         {
            return(false);
         }
         Chunk *tmp2 = tmp1->GetNextNcNnl(E_Scope::PREPROC);

         if (tmp2->Is(CT_QUALIFIER))
         {
            // tmp2 is the "nullable" qualifier in this case:
            // void (^nullable name)(params)
            // skip the qualifier
            tmp2 = tmp2->GetNextNcNnl(E_Scope::PREPROC);
         }

         if (tmp2->IsNullChunk())
         {
            return(false);
         }
         Chunk *tmp3 = (tmp2->IsString(")")) ? tmp2 : tmp2->GetNextNcNnl(E_Scope::PREPROC);

         if (tmp3->IsNullChunk())
         {
            return(false);
         }

         if (  !tmp3->IsString(")")
            || !(  tmp1->IsString("*")
                || tmp1->IsString("^")) // Issue #2656
            || !(  tmp2->GetType() == CT_WORD
                || tmp2->IsString(")")))
         {
            LOG_FMT(LFPARAM, "%s(%d): <== '%s' not fcn type!\n",
                    __func__, __LINE__, get_token_name(pc->GetType()));
            return(false);
         }
         LOG_FMT(LFPARAM, "%s(%d): <skip fcn type>\n",
                 __func__, __LINE__);

         tmp1 = tmp3->GetNextNcNnl(E_Scope::PREPROC);

         if (tmp1->IsNullChunk())
         {
            return(false);
         }

         if (tmp1->IsString("("))
         {
            tmp3 = tmp1->GetClosingParen(E_Scope::PREPROC);
         }
         pc = tmp3;
         LOG_FMT(LFPARAM, "%s(%d): pc->Text() is '%s', type is %s\n",
                 __func__, __LINE__, pc->Text(), get_token_name(pc->GetType()));

         // reset some vars to allow [] after parens
         word_count = 1;
         type_count = 1;
      }
      else if (pc->Is(CT_TSQUARE))
      {
         // ignore it
      }
      else if (  word_count == 1
              && pc->Is(CT_SQUARE_OPEN))
      {
         // skip over any array stuff
         pc = pc->GetClosingParen(E_Scope::PREPROC);
         LOG_FMT(LFPARAM, "%s(%d): pc->Text() is '%s', type is %s\n",
                 __func__, __LINE__, pc->Text(), get_token_name(pc->GetType()));
      }
      else if (  word_count == 2
              && pc->Is(CT_SQUARE_OPEN))
      {
         // Bug #671: is it such as: bool foo[FOO_MAX]
         pc = pc->GetClosingParen(E_Scope::PREPROC);
         LOG_FMT(LFPARAM, "%s(%d): pc->Text() is '%s', type is %s\n",
                 __func__, __LINE__, pc->Text(), get_token_name(pc->GetType()));
      }
      else if (  word_count == 1
              && language_is_set(LANG_CPP)
              && pc->IsString("&&"))
      {
         // ignore possible 'move' operator
      }
      else
      {
         LOG_FMT(LFPARAM, "%s(%d): <== type is %s, no way!, type count is %d, word count is %d\n",
                 __func__, __LINE__, get_token_name(pc->GetType()), type_count, word_count);
         return(false);
      }
      LOG_FMT(LFPARAM, "%s(%d): pc->Text() is '%s', type is %s\n",
              __func__, __LINE__, pc->Text(), get_token_name(pc->GetType()));
   }

   Chunk *last = pc->GetPrevNcNnlNi();   // Issue #2279

   LOG_FMT(LFPARAM, "%s(%d): last->Text() is '%s', type is %s\n",
           __func__, __LINE__, last->Text(), get_token_name(last->GetType()));

   if (last->IsPointerOperator())
   {
      LOG_FMT(LFPARAM, "%s(%d): <== type is %s, sure!\n",
              __func__, __LINE__, get_token_name(last->GetType()));
      return(true);
   }

   if (  word_count < 2
      && type_count < 1
      && start->GetBraceLevel() > 0)
   {
      LOG_FMT(LFPARAM, "%s(%d): !MVP!\n",
              __func__, __LINE__);
      // Oh, joy, we are in Most Vexing Parse territory
      Chunk *brace =
         start->GetPrevType(CT_BRACE_OPEN, start->GetBraceLevel() - 1);

      if (brace->IsNotNullChunk())
      {
         LOG_FMT(LFPARAM, "%s(%d): (matching %s brace at orig line %zu, orig col is %zu)",
                 __func__, __LINE__,
                 get_token_name(brace->GetParentType()), brace->GetOrigLine(), brace->GetOrigCol());
      }

      if (  brace->IsNotNullChunk()
         && (  brace->GetParentType() == CT_CLASS
            || brace->GetParentType() == CT_STRUCT))
      {
         // A Most Vexing Parse variable declaration cannot occur in the body
         // of a struct/class, so we probably have a function prototype
         LOG_FMT(LFPARAM, "%s(%d): <== type is %s, Likely!\n",
                 __func__, __LINE__, (pc->IsNullChunk() ? "null chunk" : get_token_name(pc->GetType())));
         return(true);
      }
   }
   LOG_FMT(LFPARAM, "%s(%d): pc->Text() is '%s', word_count is %d, type_count is %d\n",
           __func__, __LINE__, pc->Text(), word_count, type_count);

   if (first_word->IsNotNullChunk())
   {
      LOG_FMT(LFPARAM, "%s(%d): first_word->Text() is '%s'\n",
              __func__, __LINE__, first_word->Text());
   }
   bool ret = (  word_count >= 2
              || (  word_count == 1
                 && type_count == 1));

   LOG_FMT(LFPARAM, "%s(%d): ret is %s\n",
           __func__, __LINE__, ret ? "TRUE" : "FALSE");

   LOG_FMT(LFPARAM, "%s(%d): pc->Text() is '%s', ",
           __func__, __LINE__, pc->Text());
   LOG_FMT(LFPARAM, "<== type is %s, ",
           (pc->IsNullChunk() ? "null chunk" : get_token_name(pc->GetType())));

   if (ret)
   {
      LOG_FMT(LFPARAM, "Yup!\n");
   }
   else
   {
      LOG_FMT(LFPARAM, "Unlikely!\n");
   }
   return(ret);
} // can_be_full_param


bool chunk_ends_type(Chunk *start)
{
   LOG_FUNC_ENTRY();

   if (start->TestFlags(PCF_IN_FCN_CTOR))
   {
      return(false);
   }
   Chunk  *pc       = start;
   bool   ret       = false;
   size_t cnt       = 0;
   bool   last_expr = false;
   bool   last_lval = false;

   for ( ; pc->IsNotNullChunk(); pc = pc->GetPrevNcNnlNi()) // Issue #2279
   {
      LOG_FMT(LFTYPE, "%s(%d): type is %s, Text() '%s', orig line %zu, orig col %zu\n   ",
              __func__, __LINE__, get_token_name(pc->GetType()), pc->Text(),
              pc->GetOrigLine(), pc->GetOrigCol());
      log_pcf_flags(LFTYPE, pc->GetFlags());

      if (  pc->Is(CT_WORD)
         || pc->Is(CT_TYPE)
         || pc->Is(CT_PTR_TYPE)
         || pc->Is(CT_STAR)
         || pc->Is(CT_STRUCT)
         || pc->Is(CT_DC_MEMBER)
         || pc->Is(CT_PP)
         || pc->Is(CT_QUALIFIER)
         || (  language_is_set(LANG_CPP | LANG_OC)                       // Issue #2727
            && pc->GetParentType() == CT_TEMPLATE
            && (  pc->Is(CT_ANGLE_OPEN)
               || pc->Is(CT_ANGLE_CLOSE)))
         || (  language_is_set(LANG_CS | LANG_VALA)
            && (pc->Is(CT_MEMBER))))
      {
         cnt++;
         last_expr = pc->TestFlags(PCF_EXPR_START)
                     && !pc->TestFlags(PCF_IN_FCN_CALL);
         last_lval = pc->TestFlags(PCF_LVALUE);
         continue;
      }
      /* If a comma is encountered within a template, it must be
       * considered within the context of its immediate parent
       * template (i.e. argument list nest level)
       */

      if (  (  pc->IsSemicolon()
            && !pc->TestFlags(PCF_IN_FOR))
         || pc->Is(CT_TYPEDEF)
         || pc->Is(CT_BRACE_OPEN)
         || pc->IsBraceClose()
         || pc->Is(CT_FPAREN_CLOSE)
         || pc->IsOCForinOpenParen()
         || pc->Is(CT_MACRO)
         || pc->Is(CT_PP_IF)
         || pc->Is(CT_PP_ELSE)
         || pc->Is(CT_PP_ENDIF)
         || pc->GetParentType() == CT_PP_INCLUDE                       // Issue #3233
         || (  (  pc->Is(CT_COMMA)
               && !pc->TestFlags(PCF_IN_FCN_CALL)
               && get_cpp_template_angle_nest_level(start) ==
                  get_cpp_template_angle_nest_level(pc))
            && last_expr)
         || (  pc->Is(CT_SPAREN_OPEN)
            && last_lval))
      {
         ret = cnt > 0;
      }
      break;
   }

   if (pc->IsNullChunk())
   {
      // first token
      ret = true;
   }
   LOG_FMT(LFTYPE, "%s(%d): first token verdict: %s\n",
           __func__, __LINE__, ret ? "yes" : "no");

   return(ret);
} // chunk_ends_type


bool chunkstack_match(ChunkStack &cs, Chunk *pc)
{
   for (size_t idx = 0; idx < cs.Len(); idx++)
   {
      Chunk *tmp = cs.GetChunk(idx);

      if (pc->GetStr().equals(tmp->GetStr()))
      {
         return(true);
      }
   }

   return(false);
} // chunkstack_match


void flag_series(Chunk *start, Chunk *end, PcfFlags set_flags, PcfFlags clr_flags, E_Scope nav)
{
   LOG_FUNC_ENTRY();

   while (  start->IsNotNullChunk()
         && start != end)
   {
      start->UpdateFlagBits(clr_flags, set_flags);
      log_pcf_flags(LFTYPE, start->GetFlags());

      start = start->GetNext(nav);

      if (start->IsNullChunk())
      {
         return;
      }
   }

   if (end->IsNotNullChunk())
   {
      end->UpdateFlagBits(clr_flags, set_flags);
      log_pcf_flags(LFTYPE, end->GetFlags());
   }
} // flag_series


size_t get_cpp_template_angle_nest_level(Chunk *pc)
{
   LOG_FUNC_ENTRY();
   int nestLevel = 0;

   while (  pc->IsNotNullChunk()
         && pc->TestFlags(PCF_IN_TEMPLATE))
   {
      if (  pc->Is(CT_ANGLE_CLOSE)
         && pc->GetParentType() == CT_TEMPLATE)
      {
         --nestLevel;
      }
      else if (  pc->Is(CT_ANGLE_OPEN)
              && pc->GetParentType() == CT_TEMPLATE)
      {
         ++nestLevel;
      }
      pc = pc->GetPrevNcNnlNi();
   }
   return(nestLevel <= 0 ? 0 : size_t(nestLevel));
}


Chunk *get_d_template_types(ChunkStack &cs, Chunk *open_paren)
{
   LOG_FUNC_ENTRY();
   Chunk *tmp       = open_paren->GetNextNcNnl();
   bool  maybe_type = true;

   while (  tmp->IsNullChunk()
         && tmp->GetLevel() > open_paren->GetLevel())
   {
      if (  tmp->Is(CT_TYPE)
         || tmp->Is(CT_WORD))
      {
         if (maybe_type)
         {
            make_type(tmp);
            cs.Push_Back(tmp);
         }
         maybe_type = false;
      }
      else if (tmp->Is(CT_COMMA))
      {
         maybe_type = true;
      }
      tmp = tmp->GetNextNcNnl();
   }
   return(tmp);
} // get_d_template_types


bool go_on(Chunk *pc, Chunk *start)
{
   if (  pc->IsNullChunk()
      || pc->GetLevel() != start->GetLevel())
   {
      return(false);
   }

   if (pc->TestFlags(PCF_IN_FOR))
   {
      return(  (!pc->IsSemicolon())
            && (!(pc->Is(CT_COLON))));
   }
   return(!pc->IsSemicolon());
} // go_on


bool is_ucase_str(const char *str, size_t len)
{
   while (len-- > 0)
   {
      if (unc_toupper(*str) != *str)
      {
         return(false);
      }
      str++;
   }
   return(true);
} // is_ucase_str


void make_type(Chunk *pc)
{
   LOG_FUNC_ENTRY();

   if (pc->IsNotNullChunk())
   {
      if (pc->Is(CT_WORD))
      {
         pc->SetType(CT_TYPE);
      }
      else if (  (  pc->IsStar()
                 || pc->IsMsRef()
                 || pc->IsNullable())
              && pc->GetPrev()->IsTypeDefinition())               // Issue # 2640
      {
         pc->SetType(CT_PTR_TYPE);
      }
      else if (  pc->IsAddress()
              && pc->GetPrev()->IsNot(CT_SQUARE_OPEN))            // Issue # 2166
      {
         pc->SetType(CT_BYREF);
      }
   }
} // make_type


Chunk *set_paren_parent(Chunk *start, E_Token parent_type)
{
   LOG_FUNC_ENTRY();
   Chunk *end;

   end = start->GetClosingParen(E_Scope::PREPROC);

   if (end->IsNotNullChunk())
   {
      LOG_FMT(LFLPAREN, "%s(%d): %zu:%zu '%s' and %zu:%zu '%s' type is %s, parent type is %s",
              __func__, __LINE__, start->GetOrigLine(), start->GetOrigCol(), start->Text(),
              end->GetOrigLine(), end->GetOrigCol(), end->Text(),
              get_token_name(start->GetType()), get_token_name(parent_type));
      log_func_stack_inline(LFLPAREN);
      start->SetParentType(parent_type);
      end->SetParentType(parent_type);
      LOG_FMT(LFLPAREN, "%s(%d):\n", __func__, __LINE__);
      return(end->GetNextNcNnl(E_Scope::PREPROC));
   }
   LOG_FMT(LFLPAREN, "%s(%d):\n", __func__, __LINE__);
   return(Chunk::NullChunkPtr);
} // set_paren_parent
