/**
 * @file braces.cpp
 * Adds or removes braces.
 *
 * @author  Ben Gardner
 * @license GPL v2+
 */

#include "braces.h"

#include "calculate_closing_brace_position.h"
#include "combine_tools.h"
#include "newlines.h"
#include "prototypes.h"

constexpr static auto LCURRENT = LBR;

using namespace uncrustify;

using std::vector;


//! Converts a single brace into a virtual brace
static void convert_brace(Chunk *br);


//! Converts a single virtual brace into a real brace
static void convert_vbrace(Chunk *br);


static void convert_vbrace_to_brace();


//! Go backwards to honor brace newline removal limits
static void examine_braces();


/**
 * Step forward and count the number of semi colons at the current level.
 * Abort if more than 1 or if we enter a preprocessor
 */
static void examine_brace(Chunk *bopen);


static void move_case_break();


static void move_case_return();


static void mod_case_brace();


static void mod_full_brace_if_chain();


/**
 * Checks to see if the braces can be removed.
 *  - less than a certain length
 *  - doesn't mess up if/else stuff
 */
static bool can_remove_braces(Chunk *bopen);


/**
 * Checks to see if the virtual braces should be converted to real braces.
 *  - over a certain length
 *
 * @param vbopen  Virtual Brace Open chunk
 *
 * @return true (convert to real braces) or false (leave alone)
 */
static bool should_add_braces(Chunk *vbopen);


/**
 * Collect the text into txt that contains the full tag name.
 * Mainly for collecting namespace 'a.b.c' or function 'foo::bar()' names.
 */
static void append_tag_name(UncText &txt, Chunk *pc);


//! Remove the case brace, if allowable.
static Chunk *mod_case_brace_remove(Chunk *br_open);


//! Add the case brace, if allowable.
static Chunk *mod_case_brace_add(Chunk *cl_colon);


/**
 * Traverse the if chain and see if all can be removed
 *
 * @param br_start  chunk pointing to opening brace of if clause
 */
static void process_if_chain(Chunk *br_start);


/**
 * Check if parenthesis pair that comes before a brace spans multiple lines
 *
 *
 * @param brace  the brace chunk whose predecessing parenthesis will be checked
 *
 * @pre   it needs to be of type CT_BRACE_OPEN or CT_BRACE_CLOSE,
 *        its parent type needs to be one of this types:
 *            CT_IF, CT_ELSEIF, CT_FOR, CT_USING_STMT, CT_WHILE,
 *            CT_FUNC_CLASS_DEF, CT_FUNC_DEF
 *
 * @return  false: if preconditions are not met,
 *                 if an error occurs while  counting the newline between the
 *                 parenthesis or
 *                 when no newlines are found between the parenthesis
 */
static bool paren_multiline_before_brace(Chunk *brace)
{
   if (  (  brace->IsNot(CT_BRACE_OPEN)
         && brace->IsNot(CT_BRACE_CLOSE))
      || (  brace->GetParentType() != CT_IF
         && brace->GetParentType() != CT_ELSEIF
         && brace->GetParentType() != CT_FOR
         && brace->GetParentType() != CT_USING_STMT
         && brace->GetParentType() != CT_WHILE
         && brace->GetParentType() != CT_FUNC_CLASS_DEF
         && brace->GetParentType() != CT_FUNC_DEF))
   {
      return(false);
   }
   const auto paren_t = CT_SPAREN_CLOSE;

   // find parenthesis pair of the if/for/while/...
   auto paren_close = brace->GetPrevType(paren_t, brace->GetLevel(), E_Scope::ALL);
   auto paren_open  = paren_close->GetOpeningParen();

   if (  paren_close->IsNullChunk()
      || paren_open->IsNullChunk()
      || paren_close == brace
      || paren_open == paren_close)
   {
      return(false);
   }
   // determine number of lines in the parenthesis pair spans
   auto       nl_count = size_t{};
   const auto ret_flag = newlines_between(paren_open, paren_close, nl_count);

   if (!ret_flag)
   {
      LOG_FMT(LERR, "%s(%d): newlines_between error\n", __func__, __LINE__);
      return(false);
   }
   // nl_count = 0 -> 1 line
   return(nl_count > 0);
}


void do_braces()
{
   LOG_FUNC_ENTRY();
   // Mark one-liners
   // Issue #2232 put this at the beginning
   Chunk *pc = Chunk::GetHead()->GetNextNcNnl();

   while (pc->IsNotNullChunk())
   {
      if (  pc->IsNot(CT_BRACE_OPEN)
         && pc->IsNot(CT_VBRACE_OPEN))
      {
         pc = pc->GetNextNcNnl();
         continue;
      }
      Chunk         *br_open = pc;
      const E_Token brc_type = E_Token(pc->GetType() + 1); // corresponds to closing type
      // Detect empty bodies
      Chunk         *tmp = pc->GetNextNcNnl();

      if (tmp->Is(brc_type))
      {
         br_open->SetFlagBits(PCF_EMPTY_BODY);
         tmp->SetFlagBits(PCF_EMPTY_BODY);
      }
      // Scan for the brace close or a newline
      tmp = br_open->GetNextNc();

      while (tmp->IsNotNullChunk())
      {
         if (tmp->IsNewline())
         {
            break;
         }

         if (  tmp->Is(brc_type)
            && br_open->GetLevel() == tmp->GetLevel())
         {
            flag_series(br_open, tmp, PCF_ONE_LINER);
            break;
         }
         tmp = tmp->GetNextNc();
      }
      pc = pc->GetNextNcNnl();
   }
   log_rule_B("mod_full_brace_if_chain");
   log_rule_B("mod_full_brace_if_chain_only");

   if (  options::mod_full_brace_if_chain()
      || options::mod_full_brace_if_chain_only())
   {
      mod_full_brace_if_chain();
   }
   log_rule_B("mod_full_brace_if");
   log_rule_B("mod_full_brace_do");
   log_rule_B("mod_full_brace_for");
   log_rule_B("mod_full_brace_using");
   log_rule_B("mod_full_brace_while");

   if ((options::mod_full_brace_if() |
        options::mod_full_brace_do() |
        options::mod_full_brace_for() |
        options::mod_full_brace_using() |
        options::mod_full_brace_while()) & IARF_REMOVE)
   {
      examine_braces();
   }
   // convert vbraces if needed
   log_rule_B("mod_full_brace_if");
   log_rule_B("mod_full_brace_do");
   log_rule_B("mod_full_brace_for");
   log_rule_B("mod_full_brace_function");
   log_rule_B("mod_full_brace_using");
   log_rule_B("mod_full_brace_while");

   if ((options::mod_full_brace_if() |
        options::mod_full_brace_do() |
        options::mod_full_brace_for() |
        options::mod_full_brace_function() |
        options::mod_full_brace_using() |
        options::mod_full_brace_while()) & IARF_ADD)
   {
      convert_vbrace_to_brace();
   }
   log_rule_B("mod_case_brace");

   if (options::mod_case_brace() != IARF_IGNORE)
   {
      mod_case_brace();
   }
   log_rule_B("mod_move_case_break");

   if (options::mod_move_case_break())
   {
      move_case_break();
   }
   log_rule_B("mod_move_case_return");

   if (options::mod_move_case_return())
   {
      move_case_return();
   }
} // do_braces


static void examine_braces()
{
   LOG_FUNC_ENTRY();

   log_rule_B("mod_full_brace_nl_block_rem_mlcond");
   const auto multiline_block = options::mod_full_brace_nl_block_rem_mlcond();

   log_rule_B("mod_full_brace_if");
   log_rule_B("mod_full_brace_do");
   log_rule_B("mod_full_brace_for");
   log_rule_B("mod_full_brace_using");
   log_rule_B("mod_full_brace_while");

   for (Chunk *pc = Chunk::GetTail(); pc->IsNotNullChunk();)
   {
      Chunk *prev = pc->GetPrevType(CT_BRACE_OPEN);

      if (  pc->Is(CT_BRACE_OPEN)
         && !pc->TestFlags(PCF_IN_PREPROC)
         && (  (  (  pc->GetParentType() == CT_IF
                  || pc->GetParentType() == CT_ELSE
                  || pc->GetParentType() == CT_ELSEIF)
               && options::mod_full_brace_if() == IARF_REMOVE)
            || (  pc->GetParentType() == CT_DO
               && options::mod_full_brace_do() == IARF_REMOVE)
            || (  pc->GetParentType() == CT_FOR
               && options::mod_full_brace_for() == IARF_REMOVE)
            || (  pc->GetParentType() == CT_USING_STMT
               && options::mod_full_brace_using() == IARF_REMOVE)
            || (  pc->GetParentType() == CT_WHILE
               && options::mod_full_brace_while() == IARF_REMOVE)))
      {
         if (  multiline_block
            && paren_multiline_before_brace(pc))
         {
            pc = prev;
            continue;
         }
         examine_brace(pc);
      }
      pc = prev;
   }
} // examine_braces


static bool should_add_braces(Chunk *vbopen)
{
   LOG_FUNC_ENTRY();
   log_rule_B("mod_full_brace_nl");
   const size_t nl_max = options::mod_full_brace_nl();

   if (nl_max == 0)
   {
      return(false);
   }
   LOG_FMT(LBRDEL, "%s(%d): start on %zu:\n",
           __func__, __LINE__, vbopen->GetOrigLine());

   size_t nl_count = 0;

   Chunk  *pc = Chunk::NullChunkPtr;

   for (pc = vbopen->GetNextNc(E_Scope::PREPROC);
        (pc->IsNotNullChunk() && pc->GetLevel() > vbopen->GetLevel());
        pc = pc->GetNextNc(E_Scope::PREPROC))
   {
      if (pc->IsNewline())
      {
         nl_count += pc->GetNlCount();
      }
   }

   if (  pc->IsNotNullChunk()
      && nl_count > nl_max
      && vbopen->GetPpLevel() == pc->GetPpLevel())
   {
      LOG_FMT(LBRDEL, "%s(%d): exceeded %zu newlines\n",
              __func__, __LINE__, nl_max);
      return(true);
   }
   return(false);
}


static bool can_remove_braces(Chunk *bopen)
{
   LOG_FUNC_ENTRY();
   LOG_FMT(LBRDEL, "%s(%d): start on line %zu:\n",
           __func__, __LINE__, bopen->GetOrigLine());

   // Cannot remove braces inside a preprocessor
   if (bopen->TestFlags(PCF_IN_PREPROC))
   {
      return(false);
   }
   Chunk *pc = bopen->GetNextNcNnl(E_Scope::PREPROC);

   if (pc->Is(CT_BRACE_CLOSE))
   {
      // Can't remove empty statement
      return(false);
   }
   const size_t level = bopen->GetLevel() + 1;

   log_rule_B("mod_full_brace_nl");
   const size_t nl_max = options::mod_full_brace_nl();
   Chunk        *prev  = Chunk::NullChunkPtr;

   size_t       semi_count = 0;
   bool         hit_semi   = false;
   size_t       nl_count   = 0;
   size_t       if_count   = 0;
   int          br_count   = 0;

   pc = bopen->GetNextNc(E_Scope::ALL);
   LOG_FMT(LBRDEL, "%s(%d):  - begin with token '%s', orig line is %zu, orig col is %zu\n",
           __func__, __LINE__, pc->Text(), pc->GetOrigLine(), pc->GetOrigCol());

   while (  pc->IsNotNullChunk()
         && pc->GetLevel() >= level)
   {
      LOG_FMT(LBRDEL, "%s(%d): test token '%s', orig line is %zu, orig col is %zu\n",
              __func__, __LINE__, pc->Text(), pc->GetOrigLine(), pc->GetOrigCol());

      if (pc->TestFlags(PCF_IN_PREPROC))
      {
         // Cannot remove braces that contain a preprocessor
         return(false);
      }

      if (pc->IsNewline())
      {
         nl_count += pc->GetNlCount();

         if (  nl_max > 0
            && nl_count > nl_max)
         {
            LOG_FMT(LBRDEL, "%s(%d):  exceeded %zu newlines\n",
                    __func__, __LINE__, nl_max);
            return(false);
         }
      }
      else
      {
         if (pc->Is(CT_BRACE_OPEN))
         {
            br_count++;
         }
         else if (pc->Is(CT_BRACE_CLOSE))
         {
            if (br_count == 0)
            {
               fprintf(stderr, "%s(%d): br_count is ZERO, cannot be decremented, at line %zu, column %zu\n",
                       __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol());
               log_flush(true);
               exit(EX_SOFTWARE);
            }
            br_count--;

            if (pc->GetLevel() == level)
            {
               // mean a statement in a braces { stmt; }
               // as a statement with a semicolon { stmt; };
               ++semi_count;
               hit_semi = true;
            }
         }
         else if (  (  pc->Is(CT_IF)
                    || pc->Is(CT_ELSEIF))
                 && br_count == 0)
         {
            if_count++;
         }

         if (pc->GetLevel() == level)
         {
            if (  semi_count > 0
               && hit_semi)
            {
               // should have bailed due to close brace level drop
               LOG_FMT(LBRDEL, "%s(%d):  no close brace\n", __func__, __LINE__);
               return(false);
            }
            LOG_FMT(LBRDEL, "%s(%d): Text() '%s', orig line is %zu, semi_count is %zu\n",
                    __func__, __LINE__, pc->Text(), pc->GetOrigLine(), semi_count);

            if (pc->Is(CT_ELSE))
            {
               LOG_FMT(LBRDEL, "%s(%d):  bailed on '%s' on line %zu\n",
                       __func__, __LINE__, pc->Text(), pc->GetOrigLine());
               return(false);
            }

            if (  pc->IsSemicolon()
               || pc->Is(CT_IF)
               || pc->Is(CT_ELSEIF)
               || pc->Is(CT_FOR)
               || pc->Is(CT_DO)
               || pc->Is(CT_WHILE)
               || pc->Is(CT_USING_STMT)
               || (  pc->Is(CT_BRACE_OPEN)
                  && prev->Is(CT_FPAREN_CLOSE)))
            {
               hit_semi |= pc->IsSemicolon();

               if (++semi_count > 1)
               {
                  LOG_FMT(LBRDEL, "%s(%d):  bailed on %zu because of '%s' on line %zu\n",
                          __func__, __LINE__, bopen->GetOrigLine(), pc->Text(), pc->GetOrigLine());
                  return(false);
               }
            }
         }
      }
      prev = pc;
      pc   = pc->GetNextNc();
   }

   if (pc->IsNullChunk())
   {
      LOG_FMT(LBRDEL, "%s(%d):  pc is null chunk\n", __func__, __LINE__);
      return(false);
   }

   if (  pc->Is(CT_BRACE_CLOSE)
      && pc->GetParentType() == CT_IF)
   {
      Chunk *next     = pc->GetNextNcNnl(E_Scope::PREPROC);
      Chunk *tmp_prev = pc->GetPrevNcNnl(E_Scope::PREPROC);

      if (  next->Is(CT_ELSE)
         && tmp_prev->IsBraceClose()
         && tmp_prev->GetParentType() == CT_IF)
      {
         LOG_FMT(LBRDEL, "%s(%d):  - bailed on '%s'[%s] on line %zu due to 'if' and 'else' sequence\n",
                 __func__, __LINE__, get_token_name(pc->GetType()), get_token_name(pc->GetParentType()),
                 pc->GetOrigLine());
         return(false);
      }
   }
   LOG_FMT(LBRDEL, "%s(%d):  - end on '%s' on line %zu. if_count is %zu semi_count is %zu\n",
           __func__, __LINE__, get_token_name(pc->GetType()), pc->GetOrigLine(), if_count, semi_count);

   return(  pc->Is(CT_BRACE_CLOSE)
         && pc->GetPpLevel() == bopen->GetPpLevel());
} // can_remove_braces


static void examine_brace(Chunk *bopen)
{
   LOG_FUNC_ENTRY();
   LOG_FMT(LBRDEL, "%s(%d): start on orig line %zu, bopen->GetLevel() is %zu\n",
           __func__, __LINE__, bopen->GetOrigLine(), bopen->GetLevel());

   const size_t level = bopen->GetLevel() + 1;

   log_rule_B("mod_full_brace_nl");
   const size_t nl_max = options::mod_full_brace_nl();

   Chunk        *prev      = Chunk::NullChunkPtr;
   size_t       semi_count = 0;
   bool         hit_semi   = false;
   size_t       nl_count   = 0;
   size_t       if_count   = 0;
   int          br_count   = 0;

   Chunk        *pc = bopen->GetNextNc();

   while (  pc->IsNotNullChunk()
         && pc->GetLevel() >= level)
   {
      if (pc->Is(CT_NEWLINE))
      {
         LOG_FMT(LBRDEL, "%s(%d): orig line is %zu, orig col is %zu, <Newline>\n",
                 __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol());
      }
      else
      {
         LOG_FMT(LBRDEL, "%s(%d): orig line is %zu, orig col is %zu, Text() '%s'\n",
                 __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text());
      }

      if (pc->TestFlags(PCF_IN_PREPROC))
      {
         // Cannot remove braces that contain a preprocessor
         LOG_FMT(LBRDEL, "%s(%d):  PREPROC\n", __func__, __LINE__);
         return;
      }

      if (pc->IsNewline())
      {
         nl_count += pc->GetNlCount();

         if (  nl_max > 0
            && nl_count > nl_max)
         {
            LOG_FMT(LBRDEL, "%s(%d):  exceeded %zu newlines\n",
                    __func__, __LINE__, nl_max);
            return;
         }
      }
      else
      {
         LOG_FMT(LBRDEL, "%s(%d): for pc->Text() '%s', pc->GetLevel() is %zu,  bopen->GetLevel() is %zu\n",
                 __func__, __LINE__, pc->Text(), pc->GetLevel(), bopen->GetLevel());

         if (  pc->Is(CT_BRACE_OPEN)
            && pc->GetLevel() == bopen->GetLevel())
         {
            br_count++;
            LOG_FMT(LBRDEL, "%s(%d): br_count is now %d, pc->GetLevel() is %zu,  bopen->GetLevel() is %zu\n",
                    __func__, __LINE__, br_count, pc->GetLevel(), bopen->GetLevel());
         }
         else if (  pc->Is(CT_BRACE_CLOSE)
                 && pc->GetLevel() == bopen->GetLevel())
         {
            if (br_count == 0)
            {
               fprintf(stderr, "%s(%d): br_count is ZERO, cannot be decremented, at line %zu, column %zu\n",
                       __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol());
               log_flush(true);
               exit(EX_SOFTWARE);
            }
            br_count--;
            LOG_FMT(LBRDEL, "%s(%d): br_count is now %d, pc->GetLevel() is %zu,  bopen->GetLevel() is %zu\n",
                    __func__, __LINE__, br_count, pc->GetLevel(), bopen->GetLevel());

            if (br_count == 0)
            {
               Chunk *next = pc->GetNextNcNnl(E_Scope::PREPROC);

               if (  next->IsNullChunk()
                  || next->IsNot(CT_BRACE_CLOSE))
               {
                  LOG_FMT(LBRDEL, "%s(%d):  junk after close brace\n", __func__, __LINE__);
                  return;
               }
            }
         }
         else if (  (  pc->Is(CT_IF)
                    || pc->Is(CT_ELSEIF))
                 && br_count == 0)
         {
            if_count++;
         }
         LOG_FMT(LBRDEL, "%s(%d): pc->GetLevel() is %zu, level is %zu\n",
                 __func__, __LINE__, pc->GetLevel(), level);

         if (pc->GetLevel() == level)
         {
            if (  semi_count > 0
               && hit_semi)
            {
               // should have bailed due to close brace level drop
               LOG_FMT(LBRDEL, "%s(%d):  no close brace\n", __func__, __LINE__);
               return;
            }
            LOG_FMT(LBRDEL, "%s(%d): Text() '%s', orig line is %zu, semi_count is %zu\n",
                    __func__, __LINE__, pc->Text(), pc->GetOrigLine(), semi_count);

            if (pc->Is(CT_ELSE))
            {
               LOG_FMT(LBRDEL, "%s(%d):  bailed on '%s' on line %zu\n",
                       __func__, __LINE__, pc->Text(), pc->GetOrigLine());
               return;
            }

            if (prev->IsNotNullChunk())
            {
               LOG_FMT(LBRDEL, "%s(%d): orig line is %zu, orig col is %zu, Text() '%s', prev->Text '%s', prev->GetType() %s\n",
                       __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text(), prev->Text(), get_token_name(prev->GetType()));
            }
            else
            {
               LOG_FMT(LBRDEL, "%s(%d): orig line is %zu, orig col is %zu, Text() '%s', prev is a null chunk\n",
                       __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text());
            }
            LOG_FMT(LBRDEL, "%s(%d): for pc->Text() '%s', pc->GetLevel() is %zu,  bopen->GetLevel() is %zu\n",
                    __func__, __LINE__, pc->Text(), pc->GetLevel(), bopen->GetLevel());

            if (  pc->IsSemicolon()
               || pc->Is(CT_IF)
               || pc->Is(CT_ELSEIF)
               || pc->Is(CT_FOR)
               || pc->Is(CT_DO)
               || pc->Is(CT_WHILE)
               || pc->Is(CT_SWITCH)
               || pc->Is(CT_USING_STMT)
               || (  pc->Is(CT_BRACE_OPEN)
                  && pc->GetLevel() == bopen->GetLevel())) // Issue #1758
            {
               LOG_FMT(LBRDEL, "%s(%d): pc->Text() '%s', orig line is %zu, orig col is %zu, level is %zu\n",
                       __func__, __LINE__, pc->Text(), pc->GetOrigLine(), pc->GetOrigCol(), pc->GetLevel());
               hit_semi |= pc->IsSemicolon();
               semi_count++;
               LOG_FMT(LBRDEL, "%s(%d): semi_count is %zu\n",
                       __func__, __LINE__, semi_count);

               if (semi_count > 1)
               {
                  LOG_FMT(LBRDEL, "%s(%d):  bailed on %zu because of '%s' on line %zu\n",
                          __func__, __LINE__, bopen->GetOrigLine(), pc->Text(), pc->GetOrigLine());
                  return;
               }
            }
         }
      }
      prev = pc;
      pc   = pc->GetNext();                  // Issue #1907
   }

   if (pc->IsNullChunk())
   {
      LOG_FMT(LBRDEL, "%s(%d): pc is a null chunk\n", __func__, __LINE__);
      return;
   }
   LOG_FMT(LBRDEL, "%s(%d):  - end on '%s' on line %zu. if_count is %zu, semi_count is %zu\n",
           __func__, __LINE__, get_token_name(pc->GetType()), pc->GetOrigLine(), if_count, semi_count);

   if (pc->Is(CT_BRACE_CLOSE))
   {
      Chunk *next = pc->GetNextNcNnl();

      if (next->IsNotNullChunk())
      {
         while (next->Is(CT_VBRACE_CLOSE))
         {
            next = next->GetNextNcNnl();
         }

         if (next->IsNotNullChunk())
         {
            LOG_FMT(LBRDEL, "%s(%d): orig line is %zu, orig col is %zu, next is '%s'\n",
                    __func__, __LINE__, next->GetOrigLine(), next->GetOrigCol(), get_token_name(next->GetType()));
         }

         if (  if_count > 0
            && (  next->Is(CT_ELSE)
               || next->Is(CT_ELSEIF)))
         {
            LOG_FMT(LBRDEL, "%s(%d):  bailed on because 'else' is next and %zu ifs\n",
                    __func__, __LINE__, if_count);
            return;
         }
      }
      LOG_FMT(LBRDEL, "%s(%d): semi_count is %zu\n",
              __func__, __LINE__, semi_count);

      if (semi_count > 0)
      {
         LOG_FMT(LBRDEL, "%s(%d): bopen->GetParentType() is %s\n",
                 __func__, __LINE__, get_token_name(bopen->GetParentType()));

         if (bopen->GetParentType() == CT_ELSE)
         {
            Chunk *tmp_next = bopen->GetNextNcNnl();

            if (tmp_next->Is(CT_IF))
            {
               Chunk *tmp_prev = bopen->GetPrevNcNnl();
               LOG_FMT(LBRDEL, "%s(%d):  else-if removing braces on line %zu and %zu\n",
                       __func__, __LINE__, bopen->GetOrigLine(), pc->GetOrigLine());

               Chunk::Delete(bopen);
               Chunk::Delete(pc);
               newline_del_between(tmp_prev, tmp_next);

               log_rule_B("nl_else_if");

               if (options::nl_else_if() & IARF_ADD)
               {
                  newline_add_between(tmp_prev, tmp_next);
               }
               return;
            }
         }
         // we have a pair of braces with only 1 statement inside
         LOG_FMT(LBRDEL, "%s(%d): we have a pair of braces with only 1 statement inside\n",
                 __func__, __LINE__);
         LOG_FMT(LBRDEL, "%s(%d): removing braces on line %zu and %zu\n",
                 __func__, __LINE__, bopen->GetOrigLine(), pc->GetOrigLine());
         convert_brace(bopen);
         convert_brace(pc);
      }
      else
      {
         LOG_FMT(LBRDEL, "%s(%d):  empty statement\n", __func__, __LINE__);
      }
   }
   else
   {
      LOG_FMT(LBRDEL, "%s(%d):  not a close brace? - '%s'\n",
              __func__, __LINE__, pc->Text());
   }
} // examine_brace


static void convert_brace(Chunk *br)
{
   LOG_FUNC_ENTRY();

   if (br->TestFlags(PCF_KEEP_BRACE))
   {
      return;
   }
   Chunk *tmp;

   if (br->Is(CT_BRACE_OPEN))
   {
      br->SetType(CT_VBRACE_OPEN);
      br->Str().clear();
      tmp = br->GetPrev();

      if (tmp->IsNullChunk())
      {
         return;
      }
   }
   else if (br->Is(CT_BRACE_CLOSE))
   {
      br->SetType(CT_VBRACE_CLOSE);
      br->Str().clear();
      tmp = br->GetNext();

      if (tmp->IsNullChunk())
      {
         return;
      }
   }
   else
   {
      return;
   }

   if (tmp->IsNewline())
   {
      if (tmp->GetNlCount() > 1)
      {
         if (!br->TestFlags(PCF_ONE_LINER)) // Issue #2232
         {
            tmp->SetNlCount(tmp->GetNlCount() - 1);
            LOG_FMT(LBRDEL, "%s(%d): tmp new line count is %zu\n",
                    __func__, __LINE__, tmp->GetNlCount());
         }
      }
      else
      {
         // Issue #2219
         // look for opening brace
         Chunk *brace = Chunk::NullChunkPtr;

         if (br->Is(CT_VBRACE_OPEN))
         {
            brace = tmp;
         }
         else if (br->Is(CT_VBRACE_CLOSE))
         {
            brace = br->GetOpeningParen();

            if (brace->IsNullChunk())
            {
               brace = br->GetPrevType(CT_BRACE_OPEN, br->GetLevel());
            }
         }

         if (  br->Is(CT_VBRACE_OPEN)
            || (  br->Is(CT_VBRACE_CLOSE)
               && brace->GetOrigLine() < tmp->GetOrigLine()))
         {
            if (tmp->SafeToDeleteNl())
            {
               Chunk::Delete(tmp);
            }
         }
      }
   }
} // convert_brace


static void convert_vbrace(Chunk *vbr)
{
   LOG_FUNC_ENTRY();

   if (vbr->Is(CT_VBRACE_OPEN))
   {
      vbr->SetType(CT_BRACE_OPEN);
      vbr->Str() = "{";

      /*
       * If the next chunk is a preprocessor, then move the open brace after the
       * preprocessor.
       */
      Chunk *tmp = vbr->GetNext();

      if (tmp->Is(CT_PREPROC))
      {
         tmp = vbr->GetNext(E_Scope::PREPROC);
         vbr->MoveAfter(tmp);
         newline_add_after(vbr);
      }
   }
   else if (vbr->Is(CT_VBRACE_CLOSE))
   {
      vbr->SetType(CT_BRACE_CLOSE);
      vbr->Str() = "}";

      /*
       * If the next chunk is a comment, followed by a newline, then
       * move the brace after the newline and add another newline after
       * the close brace, unless we're keeping a one-liner.
       */
      Chunk *tmp = vbr->GetNext();

      if (  tmp->IsComment()
         && (  !vbr->TestFlags(PCF_ONE_LINER)
            || !options::nl_if_leave_one_liners()))
      {
         tmp = tmp->GetNext();

         if (tmp->IsNewline())
         {
            vbr->MoveAfter(tmp);
            newline_add_after(vbr);
         }
      }
   }
} // convert_vbrace


static void convert_vbrace_to_brace()
{
   LOG_FUNC_ENTRY();

   // Find every vbrace open
   log_rule_B("mod_full_brace_if");
   log_rule_B("mod_full_brace_if_chain");
   log_rule_B("mod_full_brace_for");
   log_rule_B("mod_full_brace_do");
   log_rule_B("mod_full_brace_while");
   log_rule_B("mod_full_brace_using");
   log_rule_B("mod_full_brace_function");

   for (Chunk *pc = Chunk::GetHead(); pc->IsNotNullChunk(); pc = pc->GetNextNcNnl())
   {
      if (pc->IsNot(CT_VBRACE_OPEN))
      {
         continue;
      }
      auto const in_preproc = pc->TestFlags(PCF_IN_PREPROC);

      if (  (  (  pc->GetParentType() == CT_IF
               || pc->GetParentType() == CT_ELSE
               || pc->GetParentType() == CT_ELSEIF)
            && (options::mod_full_brace_if() & IARF_ADD)
            && !options::mod_full_brace_if_chain())
         || (  pc->GetParentType() == CT_FOR
            && (options::mod_full_brace_for() & IARF_ADD))
         || (  pc->GetParentType() == CT_DO
            && (options::mod_full_brace_do() & IARF_ADD))
         || (  pc->GetParentType() == CT_WHILE
            && (options::mod_full_brace_while() & IARF_ADD))
         || (  pc->GetParentType() == CT_USING_STMT
            && (options::mod_full_brace_using() & IARF_ADD))
         || (  pc->GetParentType() == CT_FUNC_DEF
            && (options::mod_full_brace_function() & IARF_ADD)))
      {
         // Find the matching vbrace close
         Chunk *vbc = Chunk::NullChunkPtr;
         Chunk *tmp = pc->GetNext();

         while (tmp->IsNotNullChunk())
         {
            if (  in_preproc
               && !tmp->TestFlags(PCF_IN_PREPROC))
            {
               // Can't leave a preprocessor
               break;
            }

            if (  pc->GetBraceLevel() == tmp->GetBraceLevel()
               && tmp->Is(CT_VBRACE_CLOSE)
               && pc->GetParentType() == tmp->GetParentType()
               && ((tmp->GetFlags() & PCF_IN_PREPROC) == (pc->GetFlags() & PCF_IN_PREPROC)))
            {
               vbc = tmp;
               break;
            }
            tmp = tmp->GetNext();
         }

         if (vbc->IsNullChunk())
         {
            continue;
         }
         // if we found a corresponding virtual closing brace
         convert_vbrace(pc);   // convert both the opening
         convert_vbrace(vbc);  // and closing brace
      }
   }
} // convert_vbrace_to_brace


Chunk *insert_comment_after(Chunk *ref, E_Token cmt_type,
                            const UncText &cmt_text)
{
   LOG_FUNC_ENTRY();

   Chunk new_cmt = *ref;

   new_cmt.SetFlags(ref->GetFlags() & PCF_COPY_FLAGS);
   new_cmt.SetType(cmt_type);
   new_cmt.Str().clear();

   if (cmt_type == CT_COMMENT_CPP)
   {
      new_cmt.Str().append("// ");
      new_cmt.Str().append(cmt_text);
   }
   else
   {
      if (ref->Is(CT_PP_ELSE))
      {  // make test c/ 02501 stable
         new_cmt.Str().append(" ");
      }
      new_cmt.Str().append("/* ");
      new_cmt.Str().append(cmt_text);
      new_cmt.Str().append(" */");
   }
   // TODO: expand comment type to cover other comment styles?

   new_cmt.SetColumn(ref->GetColumn() + ref->Len() + 1);
   new_cmt.SetOrigCol(new_cmt.GetColumn());

   return(new_cmt.CopyAndAddAfter(ref));
}


static void append_tag_name(UncText &txt, Chunk *pc)
{
   LOG_FUNC_ENTRY();
   Chunk *tmp = pc->GetPrevNcNnl();

   LOG_FMT(LMCB, "%s(%d): txt is '%s'\n",
           __func__, __LINE__, txt.c_str());

   // step backwards over all a::b stuff
   while (tmp->IsNotNullChunk())
   {
      if (  tmp->IsNot(CT_DC_MEMBER)
         && tmp->IsNot(CT_MEMBER))
      {
         break;
      }
      tmp = tmp->GetPrevNcNnl();
      pc  = tmp;

      if (!tmp->IsWord())
      {
         break;
      }
   }
   txt += pc->GetStr();
   LOG_FMT(LMCB, "%s(%d): txt is '%s'\n",
           __func__, __LINE__, txt.c_str());

   pc = pc->GetNextNcNnl();

   while (pc->IsNotNullChunk())
   {
      if (  pc->IsNot(CT_DC_MEMBER)
         && pc->IsNot(CT_MEMBER))
      {
         break;
      }
      txt += pc->GetStr();
      LOG_FMT(LMCB, "%s(%d): txt is '%s'\n",
              __func__, __LINE__, txt.c_str());
      pc = pc->GetNextNcNnl();

      if (pc->IsNotNullChunk())
      {
         txt += pc->GetStr();
         LOG_FMT(LMCB, "%s(%d): txt is '%s'\n",
                 __func__, __LINE__, txt.c_str());
      }
      pc = pc->GetNextNcNnl();
   }
} // append_tag_name


void add_long_closebrace_comment()
{
   LOG_FUNC_ENTRY();
   Chunk *fcn_pc = Chunk::NullChunkPtr;
   Chunk *sw_pc  = Chunk::NullChunkPtr;
   Chunk *ns_pc  = Chunk::NullChunkPtr;
   Chunk *cl_pc  = Chunk::NullChunkPtr;

   for (Chunk *pc = Chunk::GetHead(); pc->IsNotNullChunk(); pc = pc->GetNextNcNnl())
   {
      if (  pc->Is(CT_FUNC_DEF)
         || pc->Is(CT_OC_MSG_DECL))
      {
         fcn_pc = pc;
      }
      else if (pc->Is(CT_SWITCH))
      {
         // pointless, since it always has the text "switch"
         sw_pc = pc;
      }
      else if (pc->Is(CT_NAMESPACE))
      {
         ns_pc = pc;
      }
      else if (pc->Is(CT_CLASS))
      {
         cl_pc = pc;
      }

      if (  pc->IsNot(CT_BRACE_OPEN)
         || pc->TestFlags(PCF_IN_PREPROC))
      {
         continue;
      }
      Chunk  *br_open = pc;
      size_t nl_count = 0;

      Chunk  *tmp = pc;

      while ((tmp = tmp->GetNext(E_Scope::PREPROC))->IsNotNullChunk())
      {
         if (tmp->IsNewline())
         {
            nl_count += tmp->GetNlCount();
            continue;
         }

         // handle only matching closing braces, skip other chunks
         if (  tmp->GetLevel() != br_open->GetLevel()
            || tmp->IsNot(CT_BRACE_CLOSE))
         {
            continue;
         }
         Chunk *br_close = tmp;

         tmp = tmp->GetNext();

         // check for a possible end semicolon
         if (tmp->Is(CT_SEMICOLON))
         {
            // set br_close to the semi token,
            // as br_close is used to add the comment after it
            br_close = tmp;
            tmp      = tmp->GetNext();
         }

         // make sure a newline follows in order to not overwrite an already
         // existring comment
         if (  tmp->IsNotNullChunk()
            && !tmp->IsNewline())
         {
            break;
         }
         size_t  nl_min  = 0;
         Chunk   *tag_pc = Chunk::NullChunkPtr;
         UncText xstr;

         if (  br_open->GetParentType() == CT_FUNC_DEF
            || br_open->GetParentType() == CT_OC_MSG_DECL)
         {
            log_rule_B("mod_add_long_function_closebrace_comment");
            nl_min = options::mod_add_long_function_closebrace_comment();
            tag_pc = fcn_pc;

            if (tag_pc->IsNotNullChunk())
            {
               append_tag_name(xstr, tag_pc);
               LOG_FMT(LMCB, "%s(%d): xstr is '%s'\n",
                       __func__, __LINE__, xstr.c_str());
            }
         }
         else if (  br_open->GetParentType() == CT_SWITCH
                 && sw_pc->IsNotNullChunk())
         {
            log_rule_B("mod_add_long_switch_closebrace_comment");
            nl_min = options::mod_add_long_switch_closebrace_comment();
            tag_pc = sw_pc;
            xstr   = sw_pc->GetStr();
            LOG_FMT(LMCB, "%s(%d): xstr is '%s'\n",
                    __func__, __LINE__, xstr.c_str());
         }
         else if (  br_open->GetParentType() == CT_NAMESPACE
                 && ns_pc->IsNotNullChunk())
         {
            log_rule_B("mod_add_long_namespace_closebrace_comment");
            nl_min = options::mod_add_long_namespace_closebrace_comment();
            tag_pc = ns_pc;
            xstr   = tag_pc->GetStr();    // add 'namespace' to the string
            LOG_FMT(LMCB, "%s(%d): xstr is '%s'\n",
                    __func__, __LINE__, xstr.c_str());

            // next chunk, normally is going to be the namespace name
            // append it with a space to generate "namespace xyz"
            Chunk *tmp_next = tag_pc->GetNextNcNnl();

            if (tmp_next->IsNot(CT_BRACE_OPEN)) // anonymous namespace -> ignore
            {
               xstr.append(" ");
               LOG_FMT(LMCB, "%s(%d): xstr is '%s'\n",
                       __func__, __LINE__, xstr.c_str());
               append_tag_name(xstr, tmp_next);
               LOG_FMT(LMCB, "%s(%d): xstr is '%s'\n",
                       __func__, __LINE__, xstr.c_str());
            }
         }
         else if (  br_open->GetParentType() == CT_CLASS
                 && cl_pc->IsNotNullChunk()
                 && (  !language_is_set(LANG_CPP)   // proceed if not C++
                    || br_close->Is(CT_SEMICOLON))) // else a C++ class needs to end with a semicolon
         {
            log_rule_B("mod_add_long_class_closebrace_comment");
            nl_min = options::mod_add_long_class_closebrace_comment();
            tag_pc = cl_pc;
            xstr   = tag_pc->GetStr();
            LOG_FMT(LMCB, "%s(%d): xstr is '%s'\n",
                    __func__, __LINE__, xstr.c_str());

            Chunk *tmp_next = cl_pc->GetNext();

            if (tag_pc->IsNotNullChunk())
            {
               xstr.append(" ");
               LOG_FMT(LMCB, "%s(%d): xstr is '%s'\n",
                       __func__, __LINE__, xstr.c_str());
               append_tag_name(xstr, tmp_next);
               LOG_FMT(LMCB, "%s(%d): xstr is '%s'\n",
                       __func__, __LINE__, xstr.c_str());
            }
         }

         if (  nl_min > 0
            && nl_count >= nl_min
            && tag_pc->IsNotNullChunk())
         {
            E_Token style;

            if (options::mod_add_force_c_closebrace_comment())
            {
               // force the C comment style
               style = CT_COMMENT;
            }
            else
            {
               // use the comment style that fits to the selected language
               style = language_is_set(LANG_CPP | LANG_CS)
                          ? CT_COMMENT_CPP : CT_COMMENT;
            }
            // Add a comment after the close brace
            LOG_FMT(LMCB, "%s(%d): xstr is '%s'\n",
                    __func__, __LINE__, xstr.c_str());
            insert_comment_after(br_close, style, xstr);
         }
         break;
      }
   }
} // add_long_closebrace_comment


static void move_case_break()
{
   LOG_FUNC_ENTRY();
   Chunk *prev = Chunk::NullChunkPtr;

   for (Chunk *pc = Chunk::GetHead(); pc->IsNotNullChunk(); pc = pc->GetNextNcNnl())
   {
      if (  pc->Is(CT_BREAK)
         && prev->Is(CT_BRACE_CLOSE)
         && prev->GetParentType() == CT_CASE
         && pc->GetPrev()->IsNewline()
         && prev->GetPrev()->IsNewline())
      {
         prev->SwapLines(pc);
      }
      prev = pc;
   }
}


static void move_case_return()
{
   LOG_FUNC_ENTRY();
   Chunk *prev = Chunk::NullChunkPtr;

   for (Chunk *pc = Chunk::GetHead(); pc->IsNotNullChunk(); pc = pc->GetNextNcNnl())
   {
      if (  pc->Is(CT_RETURN)
         && prev->Is(CT_BRACE_CLOSE)
         && prev->GetParentType() == CT_CASE
         && pc->GetPrev()->IsNewline()
         && prev->GetPrev()->IsNewline())
      {
         // Find the end of the return statement
         while (pc->IsNot(CT_SEMICOLON))
         {
            if (  pc->Is(CT_CASE)
               || pc->Is(CT_BRACE_CLOSE))
            {
               // This may indicate a semicolon was missing in the code to format.
               // Avoid moving the return statement to prevent potential unwanted errors.
               pc = Chunk::NullChunkPtr;
               break;
            }
            pc = pc->GetNext();
         }
         pc = pc->GetNextNl();
         pc = pc->GetNextNcNnl();

         if (pc->IsNotNullChunk())
         {
            // Swap all lines between brace close and current token
            LOG_FMT(LMCB, "%s(%d): move line %zu before line %zu\n",
                    __func__, __LINE__, prev->GetOrigLine(), pc->GetOrigLine());
            Chunk *curr = prev->GetNextNcNnl();

            while (curr != pc)
            {
               prev->SwapLines(curr);
               curr = prev->GetNextNcNnl();
            }
         }
      }
      prev = pc;
   }
} // move_case_return


static Chunk *mod_case_brace_remove(Chunk *br_open)
{
   LOG_FUNC_ENTRY();
   LOG_FMT(LMCB, "%s(%d): line %zu",
           __func__, __LINE__, br_open->GetOrigLine());

   // Find the matching brace close
   Chunk *next     = br_open->GetNextNcNnl(E_Scope::PREPROC);
   Chunk *br_close = br_open->GetNextType(CT_BRACE_CLOSE, br_open->GetLevel(), E_Scope::PREPROC);

   if (br_close->IsNullChunk())
   {
      LOG_FMT(LMCB, "%s(%d):  - no close\n", __func__, __LINE__);
      return(next);
   }
   // Make sure 'break', 'return', 'goto', 'case' or '}' is after the close brace
   Chunk *pc = br_close->GetNextNcNnl(E_Scope::PREPROC);

   if (  pc->IsNullChunk()
      || (  pc->IsNot(CT_BREAK)
         && pc->IsNot(CT_RETURN)
         && pc->IsNot(CT_CASE)
         && pc->IsNot(CT_GOTO)
         && pc->IsNot(CT_BRACE_CLOSE)))
   {
      LOG_FMT(LMCB, "%s(%d):  - after '%s'\n",
              __func__, __LINE__, (pc->IsNullChunk()) ? "null chuck" : get_token_name(pc->GetType()));
      return(next);
   }

   // scan to make sure there are no definitions at brace level between braces
   for (Chunk *tmp_pc = br_open;
        tmp_pc != br_close;
        tmp_pc = tmp_pc->GetNextNcNnl(E_Scope::PREPROC))
   {
      if (  tmp_pc->GetLevel() == (br_open->GetLevel() + 1)
         && tmp_pc->TestFlags(PCF_VAR_DEF))
      {
         LOG_FMT(LMCB, "%s(%d):  - vardef on line %zu: '%s'\n",
                 __func__, __LINE__, tmp_pc->GetOrigLine(), pc->Text());
         return(next);
      }
   }

   LOG_FMT(LMCB, "%s(%d):  - removing braces on lines %zu and %zu\n",
           __func__, __LINE__, br_open->GetOrigLine(), br_close->GetOrigLine());

   for (Chunk *tmp_pc = br_open;
        tmp_pc != br_close;
        tmp_pc = tmp_pc->GetNextNcNnl(E_Scope::PREPROC))
   {
      if (tmp_pc->GetBraceLevel() == 0)
      {
         fprintf(stderr, "%s(%d): brace level is ZERO, cannot be decremented, at line %zu, column %zu\n",
                 __func__, __LINE__, tmp_pc->GetOrigLine(), tmp_pc->GetOrigCol());
         log_flush(true);
         exit(EX_SOFTWARE);
      }
      tmp_pc->SetBraceLevel(tmp_pc->GetBraceLevel() - 1);

      if (tmp_pc->GetLevel() == 0)
      {
         fprintf(stderr, "%s(%d): tmp_pc->GetLevel() is ZERO, cannot be decremented, at line %zu, column %zu\n",
                 __func__, __LINE__, tmp_pc->GetOrigLine(), tmp_pc->GetOrigCol());
         log_flush(true);
         exit(EX_SOFTWARE);
      }
      tmp_pc->SetLevel(tmp_pc->GetLevel() - 1);
   }

   next = br_open->GetPrev(E_Scope::PREPROC);

   Chunk::Delete(br_open);
   Chunk::Delete(br_close);

   return(next->GetNext(E_Scope::PREPROC));
} // mod_case_brace_remove


static Chunk *mod_case_brace_add(Chunk *cl_colon)
{
   LOG_FUNC_ENTRY();
   LOG_FMT(LMCB, "%s(%d): orig line %zu, orig col is %zu\n",
           __func__, __LINE__, cl_colon->GetOrigLine(), cl_colon->GetOrigCol());

   Chunk *pc   = cl_colon;
   Chunk *last = Chunk::NullChunkPtr;
   // look for the case token to the colon
   Chunk *cas_ = cl_colon->GetPrevType(CT_CASE, cl_colon->GetLevel());
   // look for the parent
   Chunk *swit = cas_->GetParent();
   // look for the opening brace of the switch
   Chunk *open = swit->GetNextType(CT_BRACE_OPEN, swit->GetLevel());
   // look for the closing brace of the switch
   Chunk *clos = open->GetClosingParen();

   // find the end of the case-block
   pc = pc->GetNextNcNnl(E_Scope::PREPROC);

   while (pc->IsNotNullChunk())
   {
      LOG_FMT(LMCB, "%s(%d): Text() is '%s', orig line %zu, orig col is %zu, pp level is %zu\n",
              __func__, __LINE__, pc->Text(), pc->GetOrigLine(), pc->GetOrigCol(), pc->GetPpLevel());

      if (pc->GetLevel() == cl_colon->GetLevel())
      {
         if (pc->Is(CT_CASE))
         {
            LOG_FMT(LMCB, "%s(%d): Text() is '%s', orig line %zu, orig col is %zu\n",
                    __func__, __LINE__, pc->Text(), pc->GetOrigLine(), pc->GetOrigCol());
            last = calculate_closing_brace_position(cl_colon, pc);
            break;
         }
      }
      else if (pc->GetLevel() == cl_colon->GetLevel() - 1)
      {
         if (pc == clos)
         {
            LOG_FMT(LMCB, "%s(%d): Text() is '%s', orig line %zu, orig col is %zu\n",
                    __func__, __LINE__, pc->Text(), pc->GetOrigLine(), pc->GetOrigCol());
            // end of switch is reached
            last = calculate_closing_brace_position(cl_colon, pc);
            LOG_FMT(LMCB, "%s(%d): last->Text() is '%s', orig line %zu, orig col is %zu\n",
                    __func__, __LINE__, last->Text(), last->GetOrigLine(), last->GetOrigCol());
            break;
         }
      }
      pc = pc->GetNextNcNnl(E_Scope::PREPROC);
   }

   if (last->IsNullChunk())
   {
      LOG_FMT(LMCB, "%s(%d):  - last is null chunk\n", __func__, __LINE__);
      Chunk *next = cl_colon->GetNextNcNnl(E_Scope::PREPROC);
      return(next);
   }
   LOG_FMT(LMCB, "%s(%d): last->Text() is '%s', orig line %zu, orig col is %zu\n",
           __func__, __LINE__, last->Text(), last->GetOrigLine(), last->GetOrigCol());
   LOG_FMT(LMCB, "%s(%d): adding braces after '%s' on line %zu\n",
           __func__, __LINE__, cl_colon->Text(), cl_colon->GetOrigLine());

   Chunk chunk;

   chunk.SetType(CT_BRACE_OPEN);
   chunk.SetParentType(CT_CASE);
   chunk.SetOrigLine(cl_colon->GetOrigLine());
   chunk.SetOrigCol(cl_colon->GetOrigCol());
   chunk.SetLevel(cl_colon->GetLevel());
   chunk.SetPpLevel(cl_colon->GetPpLevel());
   chunk.SetBraceLevel(cl_colon->GetBraceLevel());
   chunk.SetFlags(pc->GetFlags() & PCF_COPY_FLAGS);
   chunk.Str() = "{";
   Chunk *br_open = chunk.CopyAndAddAfter(cl_colon);

   chunk.SetType(CT_BRACE_CLOSE);
   chunk.SetOrigLine(last->GetOrigLine());
   chunk.SetOrigCol(last->GetOrigCol());
   chunk.Str() = "}";
   Chunk *br_close = chunk.CopyAndAddAfter(last);

   for (pc = br_open->GetNext(E_Scope::PREPROC);
        pc != br_close;
        pc = pc->GetNext(E_Scope::PREPROC))
   {
      pc->SetLevel(pc->GetLevel() + 1);
      pc->SetBraceLevel(pc->GetBraceLevel() + 1);
   }

   return(br_open);
} // mod_case_brace_add


static void mod_case_brace()
{
   LOG_FUNC_ENTRY();

   Chunk *pc = Chunk::GetHead();

   // Make sure to start outside of a preprocessor line (see issue #3366)
   if (pc->IsPreproc())
   {
      pc = pc->GetNextNcNnlNpp();
   }

   while (pc->IsNotNullChunk())
   {
      Chunk *next = pc->GetNextNcNnl(E_Scope::PREPROC);

      if (next->IsNullChunk())
      {
         return;
      }
      log_rule_B("mod_case_brace");

      if (  options::mod_case_brace() == IARF_REMOVE
         && pc->Is(CT_BRACE_OPEN)
         && pc->GetParentType() == CT_CASE)
      {
         log_rule_B("mod_case_brace - add");
         pc = mod_case_brace_remove(pc);
      }
      else if (  (options::mod_case_brace() & IARF_ADD)
              && pc->Is(CT_CASE_COLON)
              && next->IsNot(CT_BRACE_OPEN)
              && next->IsNot(CT_BRACE_CLOSE)
              && next->IsNot(CT_CASE))
      {
         log_rule_B("mod_case_brace - remove");
         pc = mod_case_brace_add(pc);
      }
      else
      {
         pc = pc->GetNextNcNnl(E_Scope::PREPROC);
      }
   }
} // mod_case_brace


static void process_if_chain(Chunk *br_start)
{
   LOG_FUNC_ENTRY();
   LOG_FMT(LBRCH, "%s(%d): if starts on line %zu, orig col is %zu.\n",
           __func__, __LINE__, br_start->GetOrigLine(), br_start->GetOrigCol());

   vector<Chunk *> braces;

   braces.reserve(16);

   bool  must_have_braces   = false;
   bool  has_unbraced_block = false;

   Chunk *pc = br_start;

   while (pc->IsNotNullChunk())
   {
      LOG_CHUNK(LTOK, pc);

      if (pc->Is(CT_BRACE_OPEN))
      {
         const bool tmp = can_remove_braces(pc);
         LOG_FMT(LBRCH, "%s(%d): braces.size() is %zu, line is %zu, - can%s remove %s\n",
                 __func__, __LINE__, braces.size(), pc->GetOrigLine(), tmp ? "" : "not",
                 get_token_name(pc->GetType()));

         if (  !tmp
            || options::mod_full_brace_if_chain() == 2)
         {
            must_have_braces = true;
         }
      }
      else
      {
         const bool tmp = should_add_braces(pc);

         if (tmp)
         {
            must_have_braces = true;
         }
         LOG_FMT(LBRCH, "%s(%d): braces.size() is %zu, line is %zu, - %s %s\n",
                 __func__, __LINE__, braces.size(), pc->GetOrigLine(), tmp ? "should add" : "ignore",
                 get_token_name(pc->GetType()));

         has_unbraced_block = true;
      }

      if (  options::mod_full_brace_if_chain() == 3
         && !has_unbraced_block)
      {
         must_have_braces = true;
      }
      braces.push_back(pc);
      Chunk *br_close = pc->GetClosingParen(E_Scope::PREPROC);

      if (br_close->IsNullChunk())
      {
         break;
      }
      braces.push_back(br_close);

      pc = br_close->GetNextNcNnl(E_Scope::PREPROC);

      if (  pc->IsNullChunk()
         || pc->IsNot(CT_ELSE))
      {
         break;
      }
      log_rule_B("mod_full_brace_if_chain_only");

      if (options::mod_full_brace_if_chain_only())
      {
         // There is an 'else' - we want full braces.
         must_have_braces = true;
      }
      pc = pc->GetNextNcNnl(E_Scope::PREPROC);

      if (pc->Is(CT_ELSEIF))
      {
         while (  pc->IsNot(CT_VBRACE_OPEN)
               && pc->IsNot(CT_BRACE_OPEN))
         {
            pc = pc->GetNextNcNnl(E_Scope::PREPROC);
         }
      }

      if (pc->IsNullChunk())
      {
         break;
      }

      if (  pc->IsNot(CT_BRACE_OPEN)
         && pc->IsNot(CT_VBRACE_OPEN))
      {
         break;
      }
   }

   if (must_have_braces)
   {
      LOG_FMT(LBRCH, "%s(%d): add braces on lines[%zu]:",
              __func__, __LINE__, braces.size());

      const auto ite = braces.rend();

      for (auto itc = braces.rbegin(); itc != ite; ++itc)
      {
         const auto brace = *itc;

         brace->SetFlagBits(PCF_KEEP_BRACE);

         if (brace->IsVBrace())
         {
            LOG_FMT(LBRCH, "%s(%d):  %zu",
                    __func__, __LINE__, brace->GetOrigLine());
            convert_vbrace(brace);
         }
         else
         {
            LOG_FMT(LBRCH, "%s(%d):  {%zu}",
                    __func__, __LINE__, brace->GetOrigLine());
         }
      }

      LOG_FMT(LBRCH, "\n");
   }
   else if (options::mod_full_brace_if_chain())
   {
      log_rule_B("mod_full_brace_if_chain");
      LOG_FMT(LBRCH, "%s(%d): remove braces on lines[%zu]:\n",
              __func__, __LINE__, braces.size());

      /*
       * This might run because either
       * mod_full_brace_if_chain or mod_full_brace_if_chain_only
       * is used.
       * We only want to remove braces if the first one is active.
       */
      log_rule_B("mod_full_brace_nl_block_rem_mlcond");
      const auto multiline_block = options::mod_full_brace_nl_block_rem_mlcond();

      LOG_FMT(LBRCH, "%s(%d): remove braces on lines:\n", __func__, __LINE__);

      // Issue #2229
      const auto ite = braces.end();

      for (auto itc = braces.begin(); itc != ite; ++itc)
      {
         const auto brace = *itc;

         if (  (  brace->Is(CT_BRACE_OPEN)
               || brace->Is(CT_BRACE_CLOSE))
            && (brace->GetParentType() != CT_BRACED_INIT_LIST)
            && (multiline_block ? !paren_multiline_before_brace(brace) : true))
         {
            LOG_FMT(LBRCH, "%s(%d): brace orig line is %zu, orig col is %zu\n",
                    __func__, __LINE__, brace->GetOrigLine(), brace->GetOrigCol());
            convert_brace(brace);
         }
         else
         {
            LOG_FMT(LBRCH, "%s(%d): brace orig line is %zu, orig col is %zu\n",
                    __func__, __LINE__, brace->GetOrigLine(), brace->GetOrigCol());
         }
      }
   }
} // process_if_chain


static void mod_full_brace_if_chain()
{
   LOG_FUNC_ENTRY();

   for (Chunk *pc = Chunk::GetHead(); pc->IsNotNullChunk(); pc = pc->GetNext())
   {
      if (  pc->IsBraceOpen()
         && pc->GetParentType() == CT_IF)
      {
         process_if_chain(pc);
      }
   }
}
