/**
 * @file align_oc_msg_colons.cpp
 *
 * @author  Guy Maurel
 * @license GPL v2+
 */

#include "align_oc_msg_colons.h"

#include "align_stack.h"
#include "log_rules.h"

constexpr static auto LCURRENT = LOCMSG;

using namespace uncrustify;


void align_oc_msg_colon(Chunk *so)
{
   LOG_FUNC_ENTRY();

   AlignStack nas;   // for the parameter tag

   nas.Start(1);
   nas.Reset();
   log_rule_B("align_on_tabstop");
   nas.m_right_align = !options::align_on_tabstop();

   AlignStack cas;   // for the colons

   log_rule_B("align_oc_msg_colon_span");
   size_t span = options::align_oc_msg_colon_span();

   cas.Start(span);

   size_t level = so->GetLevel();
   Chunk  *pc   = so->GetNextNcNnl(E_Scope::PREPROC);

   bool   did_line   = false;
   bool   has_colon  = false;
   size_t lcnt       = 0;  // line count with no colon for span
   bool   first_line = true;

   while (  pc->IsNotNullChunk()
         && pc->GetLevel() > level)
   {
      if (pc->GetLevel() > (level + 1))
      {
         // do nothing
      }
      else if (pc->IsNewline())
      {
         if (!has_colon)
         {
            ++lcnt;
         }
         did_line = false;

         log_rule_B("align_oc_msg_colon_xcode_like");

         if (  options::align_oc_msg_colon_xcode_like()
            && first_line
            && !has_colon)
         {
            span = 0;
         }
         has_colon  = !has_colon;
         first_line = false;
      }
      else if (  !did_line
              && (lcnt < span + 1)
              && pc->Is(CT_OC_COLON))
      {
         has_colon = true;
         cas.Add(pc);
         Chunk *tmp = pc->GetPrev();

         if (  tmp->IsNotNullChunk()
            && (  tmp->Is(CT_OC_MSG_FUNC)
               || tmp->Is(CT_OC_MSG_NAME)))
         {
            nas.Add(tmp);
            tmp->SetFlagBits(PCF_DONT_INDENT);
         }
         did_line = true;
      }
      pc = pc->GetNext(E_Scope::PREPROC);
   }
   log_rule_B("align_oc_msg_colon_first");
   nas.m_skip_first = !options::align_oc_msg_colon_first();
   cas.m_skip_first = !options::align_oc_msg_colon_first();

   // find the longest args that isn't the first one
   size_t first_len = 0;
   size_t mlen      = 0;
   Chunk  *longest  = Chunk::NullChunkPtr;

   size_t len = nas.m_aligned.Len();

   for (size_t idx = 0; idx < len; idx++)
   {
      Chunk *tmp = nas.m_aligned.GetChunk(idx);

      if (tmp->IsNotNullChunk())
      {
         size_t tlen = tmp->GetStr().size();

         if (tlen > mlen)
         {
            mlen = tlen;

            if (idx != 0)
            {
               longest = tmp;
            }
         }

         if (idx == 0)
         {
            first_len = tlen + 1;
         }
      }
   }

   // add spaces before the longest arg
   log_rule_B("indent_oc_msg_colon");
   len = options::indent_oc_msg_colon();
   size_t len_diff = mlen - first_len;

   log_rule_B("indent_columns");
   size_t indent_size = options::indent_columns();

   // Align with first colon if possible by removing spaces
   log_rule_B("indent_oc_msg_prioritize_first_colon");

   if (  longest->IsNotNullChunk()
      && options::indent_oc_msg_prioritize_first_colon()
      && len_diff > 0
      && (  (longest->GetColumn() >= len_diff)
         && (longest->GetColumn() - len_diff) > (longest->GetBraceLevel() * indent_size)))
   {
      longest->SetColumn(longest->GetColumn() - len_diff);
   }
   else if (  longest->IsNotNullChunk()
           && len > 0)
   {
      Chunk chunk;

      chunk.SetType(CT_SPACE);
      chunk.SetParentType(CT_NONE);
      chunk.SetOrigLine(longest->GetOrigLine());
      chunk.SetOrigCol(longest->GetOrigCol());
      chunk.SetLevel(longest->GetLevel());
      chunk.SetBraceLevel(longest->GetBraceLevel());
      chunk.SetFlags(longest->GetFlags() & PCF_COPY_FLAGS);

      // start at one since we already indent for the '['
      for (size_t idx = 1; idx < len; idx++)
      {
         chunk.Str().append(' ');
      }

      chunk.CopyAndAddBefore(longest);
   }
   nas.End();
   cas.End();
} // align_oc_msg_colon


void align_oc_msg_colons()
{
   LOG_FUNC_ENTRY();

   for (Chunk *pc = Chunk::GetHead(); pc->IsNotNullChunk(); pc = pc->GetNext())
   {
      if (  pc->Is(CT_SQUARE_OPEN)
         && pc->GetParentType() == CT_OC_MSG)
      {
         align_oc_msg_colon(pc);
      }
   }
} // align_oc_msg_colons
