/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2023  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 
#include "stoolkit/syntax/SSyntaxMarker.h"
#include "stoolkit/syntax/SSyntax.h"


/**
 * The _textData and _lines arrays should be in sync, they
 * should have the same sizes.
 * @param _around is around which we should mark unmarked in our buffer.
 */
SSyntaxMarker::SSyntaxMarker (SSyntaxData& _lines, const SUnicodeData& _ulines, 
  const STextIndex& _around) 
   : syntaxLines (_lines), dataLines (_ulines)
{
  STextIndex first = _around;
  // look backwards for a sync
  if (decrement(&first)) while ((getSyntaxAt (first) & SGC_BEGIN_MARK) == 0)
  {
    if (!decrement(&first)) break;
  }
  // hack, comment this out if you have a proper parser.
  // FIXME FIXME
  //first = STextIndex (0,0);
//  fprintf (stderr, "SGC syntax started around=%u,%u found=%u,%u\n", 
//     _around.line, _around.index, first.line, first.index); 

  startIndex = first;
  unsigned int firstLineSize = getLineSize (startIndex.line) 
        - startIndex.index;
  // linesizes always contains 1 more lines than real lines.
  lineSizes.append (0);
  lineSizes.append (firstLineSize);
/*
  fprintf (stderr, "firstLineSize=%u getLineSize=%u\n", 
     firstLineSize, getLineSize (startIndex.line));
*/

  isStarted = true;
  actionMap.put ("none", ((int) SSyntax::SD_NONE) << 8);
  actionMap.put ("error", ((int) SSyntax::SD_ERROR) << 8);
  actionMap.put ("number", ((int) SSyntax::SD_NUMBER) << 8); 
  actionMap.put ("string", ((int) SSyntax::SD_STRING) << 8); 
  actionMap.put ("comment", ((int) SSyntax::SD_COMMENT) << 8);
  actionMap.put ("keyword", ((int) SSyntax::SD_KEYWORD) << 8);
  actionMap.put ("variable", ((int) SSyntax::SD_VARIABLE) << 8);
  actionMap.put ("define", ((int) SSyntax::SD_DEFINE) << 8);
  actionMap.put ("control", ((int) SSyntax::SD_CONTROL) << 8);
  actionMap.put ("other", ((int) SSyntax::SD_OTHER) << 8);
} 

SSyntaxMarker::~SSyntaxMarker ()
{
}

/* SMatcherIterator */
// FIXME FIXME FIXME 
int
SSyntaxMarker::getNextCharacter ()
{
  if (isStarted)
  {
    currentIndex = startIndex;
    isStarted = false;
    if (isEOD (currentIndex)) return -1;
  }
  else
  {
    if (!increment (&currentIndex)) return -1;
  }
  // lineSizes keeps track of incremental sizes.
  if (currentIndex.line - startIndex.line == lineSizes.size()-1)
  {
    lineSizes.append (getLineSize (currentIndex.line) 
          + lineSizes[currentIndex.line - startIndex.line ]);
  }
  return getCharAt (currentIndex);
}

bool
SSyntaxMarker::isEOD (const STextIndex& idx) const
{
  if (idx.line >= dataLines.size()) return true;
  if (idx.index >= getLineSize(idx.line)) return true;
  return false;
}

void
SSyntaxMarker::beginActionBlock ()
{
  minModified = STextIndex (dataLines.size(), 0);
  maxModified = STextIndex (0, 0);
}


/* SMatcherAction */
// Apply action from from till till exclusive
// 1. Mark shadow syntax
// 2. modify minModified and maxModified
void
SSyntaxMarker::applyAction (const SString& name,
    unsigned int markFrom, unsigned int markTill)
{
  STextIndex from = position2Index (markFrom);
  STextIndex till = position2Index (markTill);

#if DEBUG_PARSER
fprintf (stderr, "applyAction %*.*s %u..%u  %u.%u..%u,%u\n", 
  SSARGS(name), markFrom, markTill, 
  from.line, from.index, till.line, till.index);
#endif
  if (from < minModified)
  {
    minModified = from;
  }
  if (till > maxModified)
  {
    maxModified = till;
  }
  int shadow = actionMap.get (name);
  while (from < till)
  {
    int old = getSyntaxAt (from);
    shadow = (old & 0xff) | shadow;
    // This will wipe out extra marks
    setSyntaxAt (from, shadow);
    if (!increment (&from)) break;
  }
}

// 1. move shadow syntax to real
// 2. update minModified and maxModified
// 3. update the minLimits and minLimits to know where stuff changed.
void
SSyntaxMarker::endActionBlock ()
{
#if DEBUG_PARSER
fprintf (stderr, "finish %u.%u..%u,%u\n", 
  minModified.line, minModified.index,
  maxModified.line, maxModified.index);
#endif

  STextIndex min = STextIndex (dataLines.size(), 0);
  STextIndex max = STextIndex (0, 0);

  STextIndex from = minModified;
  while (from < maxModified)
  {
    int vle = getSyntaxAt (from);
    int o = (vle & 0xff);
    int n = (vle & 0xff00) >> 8;
    if (o == n)
    {
      if (!increment (&from)) break;
      continue;
    }
    if (from < min) min = from;
    if (from > max) max = from;
    setSyntaxAt (from, n);
    if (!increment (&from)) break;
  }
  // multiline, scrolled editor. write yuko in Hungarian kmap
  // yuko is not first line. o is not error with test SPattern.
  // dont know why...
//  minModified = min;
//  maxModified = max;
}

// Increment the in index
// return false if in is already at the end
// For non expanded lines we generate virtual new index if 
// it is has a proper ending.
bool
SSyntaxMarker::decrement (STextIndex* in)
{
  if (in->line == 0 && in->index==0) return false;
  if (in->index == 0)
  {
    in->line--;
    in->index = getLineSize (in->line);
    if (in->index==0)
    {
#if DEBUG_PARSER
      fprintf (stderr, "SSyntaxMarker::decrement: empty line detected.\n");
#endif
      return false;
    }
  }
  // There can be no empty lines in the middle of the file 
  in->index = in->index-1;
  return true;
}

// Decrement the in index
// return false if in is already at the end
// For non expanded lines we generate virtual new index if 
// it is has a proper ending.
bool
SSyntaxMarker::increment (STextIndex* in)
{
  if (in->line >= dataLines.size()) return false;
  unsigned int ls = getLineSize (in->line);
  if (in->index+1 == ls)
  {
    in->line++;
    in->index=0;
    if (in->line >= dataLines.size()) return false;
    ls = getLineSize (in->line);
    if (ls == 0) return false;
    return true;
  }
  in->index++;
  if (in->index >= ls) return false;
  return true;
}

// Get character at the index.
// return -1 if in is out of bounds.
int
SSyntaxMarker::getCharAt (const STextIndex& in) const
{
  if (in.line >= dataLines.size()) return -1;
  if (in.index >= dataLines[in.line]->size()) return -1;
  // todo sanity check here
  SS_UCS4 ret = dataLines[in.line]->peek (in.index);
  // Paragraph separator
//fprintf (stderr, "%u,%u=[%c]", in.line, in.index, (char) ret);
  if (ret == 0x2029) return (int) '\n';
  return (int) ret;
}

// Get character at the index.
// return -1 if in is out of bounds.
int
SSyntaxMarker::getSyntaxAt (const STextIndex& in) const
{
  if (in.line >= syntaxLines.size()) return -1;
  if (in.index >= syntaxLines[in.line]->size()) return -1;
  // todo sanity check here
  return syntaxLines[in.line]->peek (in.index);
}

// Get character at the index.
// return false if in is out of bounds.
bool
SSyntaxMarker::setSyntaxAt (const STextIndex& in, int syn)
{
  if (in.line >= syntaxLines.size()) return false;
  if (in.index >= syntaxLines[in.line]->size()) return false;
  syntaxLines[in.line]->replace (in.index, syn);
  return true;
}

// get the line size, adjusted for non-expanded chanacters.
// an extra functionality is to sync expanded lines, as
// they are not always reported elsewhere.
unsigned int
SSyntaxMarker::getLineSize  (unsigned int lineno) const
{
  if (lineno >= dataLines.size()) return 0;
  // line is expaned
  return dataLines[lineno]->size();
}

// linsizes[line] contain the accumulated number of characters 
// from startIndex at the end of the line.
STextIndex
SSyntaxMarker::position2Index (unsigned int position)
{
  if (position < lineSizes[1])
  {
    return STextIndex (startIndex.line, startIndex.index + position);
  }
  // This can return a bigger value
  unsigned int mapIndex = lineSizes.findSorted (position);
  if (mapIndex>=lineSizes.size()) 
  {
    return STextIndex (startIndex.line+lineSizes.size()-1, 0);
  }
  while (position < lineSizes[mapIndex] && mapIndex > 0) mapIndex--;
  unsigned int addValue =  lineSizes[mapIndex];
  return STextIndex (mapIndex+startIndex.line, position-addValue);
}
