/** 
 *  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/SSyntax.h"
#include "stoolkit/syntax/SHunspellPattern.h"
#include "stoolkit/syntax/SSyntaxMarker.h"
#include "stoolkit/SIO.h"
#include "stoolkit/SProperties.h"
#include "stoolkit/SUtil.h"


static SStringVector syntaxSearchPath;

SSyntax::SSyntax (void) : parser ("")
{
  textData = 0;
  pattern = 0;
  listener = 0;
  syntaxListener = 0;
  syntaxState = 0;
}

SSyntax::~SSyntax ()
{
  if (syntaxState) delete syntaxState;
  if (pattern) delete pattern;
  clear ();
}

bool
SSyntax::isSupported (const SString& syn)
{
  SStringVector v(syn, ":", true);
  if (v.size() != 2) return false;
  if (v[0] == "test") return true;
  if (v[0] == "hunspell") return true;
  return false;
}

/**
 * @param ps - supported properties:
 *  none
 *  xml
 *  properties
 */
bool
SSyntax::setSyntax (const SString&  ps)
{
  parser = ps;
  if (pattern)
  {
    delete pattern;
    pattern = 0;
  }
  if (syntaxState)
  {
    delete syntaxState; 
    syntaxState = 0;
    if (syntaxListener) syntaxListener->syntaxChanged (
       SSyntaxListener::SD_PARSING_DONE);
  }
  clear ();
  if (ps == "")
  {
    return true;
  }
  SStringVector split (ps, ":", true);
  if (split.size() != 2)
  {
    parser = "";
    return false;
  }
  if (split[0] == "test") // update isSupported()
  {
    if (split[1] == "yuko")
    {
      pattern = new SPattern ();
      CHECK_NEW (pattern);
      lineGlobalChange ();
      return true;
    }
    else if (split[1] == "lines")
    {
      pattern = new SPattern ();
      CHECK_NEW (pattern);
      lineGlobalChange ();
      return true;
    }
  }
  else if (split[0] == "hunspell")
  {
    pattern = new SHunspellPattern (split[1], getPath());  
    CHECK_NEW (pattern);
    if (!pattern->isValid ())
    {
      delete pattern;
      pattern = 0;
      parser = "";
      return false;
    }
    lineGlobalChange ();
    return true;
  }
  parser = "";
  return false;
}

const SString&
SSyntax::getParser () const
{
  return parser;
}

void SSyntax::clear ()
{ 
  unsigned int i;
  for (i=0; i<syntaxLines.size(); i++)
  {
     delete syntaxLines[i];
  }
  syntaxLines.clear ();
  for (i=0; i<dataLines.size(); i++)
  {
     delete dataLines[i];
  }
  dataLines.clear ();
  clearIterator ();
  if (syntaxState)
  {
    delete syntaxState;
    syntaxState = 0;
    if (syntaxListener) syntaxListener->syntaxChanged (
       SSyntaxListener::SD_PARSING_DONE);
  }
}

void
SSyntax::clearIterator ()
{
  iteratorDataIndex = STextIndex (0,0);
  iteratorSyntaxIndex = STextIndex (0,0);
}

void
SSyntax::setTextData (const STextData* td)
{
  textData = td;
  clear ();
  lineGlobalChange ();
} 

SSyntax::SS_Tag
SSyntax::getTag (const STextIndex& index)
{
  if (parser.size()==0) return SD_NONE;
  if (index.line > syntaxLines.size()) return SD_NONE;
  if (index.index > syntaxLines[index.line]->size()) return SD_NONE;
  // Strip off control characters
  return (SS_Tag) (syntaxLines[index.line]->peek (index.index) & 0xff);
}

SSyntax::SS_Tag
SSyntax::getTagByTDI (const STextIndex& index)
{
  if (parser.size()==0) return SD_NONE;
  if (index.line > textData->size()) return SD_NONE;
  if (index.line > syntaxLines.size()) return SD_NONE;
  if (index.index > textData->size(index.line)) return SD_NONE;
  if (parser == "test:lines") return (SS_Tag)(index.line % (int) SD_MAX);

  // return to previous iterator
  if (index.line != iteratorDataIndex.line 
     || index.index < iteratorDataIndex.index)
  {
     iteratorDataIndex.index = 0;
     iteratorSyntaxIndex.index = 0;
  } 
  iteratorDataIndex.line = index.line;
  iteratorSyntaxIndex.line = index.line;

  const SGlyph* g0 = (iteratorDataIndex.index > 0)
   ? textData->peekGlyphAt (STextIndex(iteratorDataIndex.line, 
   iteratorDataIndex.index-1)) : 0;
  while (iteratorDataIndex.index < index.index)
  {
    const SGlyph* g = textData->peekGlyphAt (iteratorDataIndex);
    SV_UCS4 chars = g->getChars ();
    if (chars.size() == 0) break; // sanity
    SV_UCS4 emb = g->getEmbeddingMarks(g0);
    g0 = g;
    iteratorSyntaxIndex.index += (chars.size() + emb.size());
    iteratorDataIndex.index = iteratorDataIndex.index + 1;
  }
  // Strip off embedding control characters
  unsigned int i=0;
  unsigned int max = dataLines[index.line]->size();
  for (i=iteratorSyntaxIndex.index; i<max; i++)
  {
    SS_UCS4 c = dataLines[index.line]->peek (i);
    if (c!=SD_CD_LRO && c!=SD_CD_RLO && c!=SD_CD_LRE && c!=SD_CD_RLE
       && c!=SD_CD_PDF) // most likely we wont have SD_CD_PDF
    {
      break;
    }
  }
  if (i==max) return SD_ERROR;
  if (syntaxLines[index.line]->size()<=i) return SD_ERROR;
  return (SS_Tag) (syntaxLines[index.line]->peek (i) & 0xff);
}

void
SSyntax::lineRemoved (void* src, unsigned int index)
{
  if (parser.size()==0) return;
  if (textData->size() == 0)
  {
     clear ();
     return;
  }
  if (index >= dataLines.size () || index > textData->size())
  {
#if DEBUG_PARSER
     fprintf (stderr, 
        "ERROR: lineRemoved index=%u lines.size=%u textData.size=%u\n", 
         index, lines.size(), textData->size());
#endif
     lineGlobalChange();
     return;
  }
  delete dataLines[index];
  dataLines.remove (index);
  delete syntaxLines[index];
  syntaxLines.remove (index);
  clearIterator ();
  updateSyntaxState (STextIndex (index, 0));
}

void
SSyntax::lineInserted (void* src, unsigned int index)
{
  if (parser.size()==0) return;
  if (index > dataLines.size () || index >= textData->size())
  {
#if 0
     fprintf (stderr, 
        "ERROR: lineInserted index=%u lines.size=%u textData.size=%u\n", 
         index, dataLines.size(), textData->size());
#endif
     lineGlobalChange();
     return;
  }
  SV_UCS4* l = new SV_UCS4(textData->getChars (index));
  CHECK_NEW (l);
  dataLines.insert (index, l);

  SSyntaxRow* row = new SSyntaxRow();
  CHECK_NEW (row);
  for (unsigned int i=0; i<l->size(); i++)
  {
    row->append (0);
  }
  syntaxLines.insert (index, row);
  clearIterator ();
  updateSyntaxState (STextIndex (index, 0));
}

void 
SSyntax::lineChanged (void* src, unsigned int index)
{
  if (parser.size()==0) return;
  if (index > dataLines.size () || index >= textData->size())
  {
#if 0
     fprintf (stderr, 
        "ERROR: lineChanged index=%u lines.size=%u textData.size=%u\n", 
         index, dataLines.size(), textData->size());
#endif
     lineGlobalChange();
     return;
  }

  SV_UCS4* newdl = new SV_UCS4 (textData->getChars (index));
  CHECK_NEW (newdl);
  // check what changed.
  unsigned int floor = 0;
  unsigned int ceiling = 0;

  unsigned int lsize = newdl->size();
  unsigned int dsize = dataLines[index]->size();

  while (floor < lsize && floor < dsize)
  {
    if (newdl->peek  (floor) != dataLines[index]->peek (floor)) break;
    floor++;
  }
  while (ceiling < lsize && ceiling < dsize 
      && floor + ceiling < dsize && floor + ceiling <  lsize)
  {
    if (newdl->peek  (lsize-ceiling-1) 
       != dataLines[index]->peek (dsize-ceiling-1)) break;
    ceiling++;
  }
  SSyntaxRow* newsn = new SSyntaxRow();
  CHECK_NEW (newsn);
  // copy old data
  unsigned int i;
  // fprintf (stderr, "floor=%u ceiling=%u\n", floor, ceiling);
  // keep the syntax but remove the control
  for (i=0; i<floor; i++)
  {
    newsn->append (0xff & (unsigned int)syntaxLines[index]->peek (i));
  }
  for (i=0; i<ceiling; i++)
  {
    newsn->insert (floor, 0xff & (unsigned int)syntaxLines[index]->peek (dsize-i-1));
  }
  for (i=floor; i<lsize-ceiling; i++)
  {
    newsn->insert (i, 0);
  }
  if (newdl->size() != newsn->size())
  {
    fprintf (stderr, "Internal error SSyntax::lineChanged."); 
    lineGlobalChange();
    return;
  }

  delete dataLines[index];
  delete syntaxLines[index];
  dataLines.replace (index, newdl);
  syntaxLines.replace (index, newsn);
  clearIterator ();
  updateSyntaxState (STextIndex (index, 0));
}

void 
SSyntax::lineGlobalChange ()
{
  clear ();
  if (parser.size()==0) return;

  for (unsigned i=0; i<textData->size(); i++)
  {
    lineInserted (this, i);
  }
  updateSyntaxState (STextIndex(0,0));
}
void
SSyntax::updateSyntaxState (const STextIndex ndx)
{
  if (parser.size()==0)
  {
    if (syntaxState)
    {
      delete syntaxState;
      syntaxState = 0;
      if (syntaxListener) syntaxListener->syntaxChanged (
          SSyntaxListener::SD_PARSING_DONE);
    }
    return;
  }
  if (syntaxState)
  {
    STextIndex crawlIndex = syntaxState->getCurrentIndex ();
    // If the crawlIndex is less than our index, dont do anything,
    // this point is not checked yet.
    // the linesizes are cached as we crawl, so we should do this.
    if (crawlIndex.line < ndx.line)
    {
       return;
    }
    delete syntaxState;
    syntaxState = 0;
  }
  // create a new syntaxState
  SSyntaxMarker* marker = new SSyntaxMarker (syntaxLines, dataLines, ndx);
  CHECK_NEW (marker);
  SMatcher* matcher = new SMatcher (*pattern, *marker);
  CHECK_NEW (matcher);
  // effectively this is an idle timer.
  STimer* timer = STimer::newTimer(0, this);
  syntaxState = new SSyntaxState ( matcher, marker, timer);
  CHECK_NEW (syntaxState);
  if (syntaxListener) syntaxListener->syntaxChanged (
       SSyntaxListener::SD_PARSING_STARTED);
}

// do another iteration of syntax checking
// return false if finished, and cleanup syntaxState.
bool
SSyntax::timeout (const SEventSource* s)
{
  if (syntaxState == 0)
  {
    return false;// never
  }
  // 100 characters at a time
  unsigned int count = 0;
  unsigned int oldN = SD_MATCH_EOD;
  SS_UCS4 n = 0;
  while ((n=syntaxState->matcher->find (true)) != SD_MATCH_EOD)
  {
    if (n==SD_MATCH_AGAIN)
    {
      if (count < SD_UNIT_WORK_COUNT) continue;
      return true; // call timer again.
    }
    count++;
    if (oldN == n)
    {
      fprintf (stderr, "Detected infinite loop in matcher at %u.\n",
         oldN);
      n = SD_MATCH_EOD;
      break;
    }
    oldN = n;
    applyActions ();

    // set a sync marker to begin here
    STextIndex idx = syntaxState->marker->position2Index (n); 
    int syn = syntaxState->marker->getSyntaxAt (idx);
    syn = syn | SGC_BEGIN_MARK;
    syntaxState->marker->setSyntaxAt (idx, syn);
  }
  // end of file reached.
  applyActions ();
  if (n != SD_MATCH_EOD)
  {
    // set a sync marker to begin here
    STextIndex idx = syntaxState->marker->position2Index (n); 
    int syn = syntaxState->marker->getSyntaxAt (idx);
    syn = syn | SGC_BEGIN_MARK;
    syntaxState->marker->setSyntaxAt (idx, syn);
  }
  delete syntaxState;
  syntaxState = 0;
  if (syntaxListener) syntaxListener->syntaxChanged (
       SSyntaxListener::SD_PARSING_DONE);
//  fprintf (stderr, "SGC syntax finished.\n");
  return false;
}

void
SSyntax::applyActions ()
{
  syntaxState->marker->beginActionBlock ();
  syntaxState->matcher->applyActions (*syntaxState->marker);
  syntaxState->marker->endActionBlock ();
  STextIndex minModified = syntaxState->marker->minModified;
  STextIndex maxModified = syntaxState->marker->maxModified;
  // maxmodified is incluside.
  if (minModified <= maxModified)
  {
    // we are lazy, and set whole line modified instead of converting
    // our dataLine index to real textData index.
    maxModified.line++;
    maxModified.index=0;
    // reverse index.
    unsigned int lineCeiling = (maxModified.line >= textData->size()) 
       ?  0 : textData->size() -  maxModified.line;
    STextDataEvent evt (minModified);
    if (listener)
    {
       STextDataEvent evt (minModified);
       evt.setRemaining (STextIndex (lineCeiling, 0));
       evt.attribute = true;
       listener->textChanged (this, evt);
    }
  }
}

// It is a setter only
void
SSyntax::addTextDataListener (STextDataListener* _listener)
{
  listener = _listener;
}

void
SSyntax::addSyntaxListener (SSyntaxListener* _listener)
{
  syntaxListener = _listener;
}

void
SSyntax::setPath (const SStringVector& l)
{
  syntaxSearchPath = l;
}

const SStringVector&
SSyntax::getPath ()
{
  if (syntaxSearchPath.size() == 0) 
  {
     guessPath();
  }
  return syntaxSearchPath;
}

/**
 * search files for property in order and set the path to the 
 * property. Always add YUDIT_DATA/syntax
 */
void
SSyntax::guessPath ()
{
  syntaxSearchPath = getUserPath("yudit.syntaxpath", "syntax");
  //fprintf (stderr, "syntaxpath is %*.*s\n", SSARGS(syntaxSearchPath.join(",")));
}


// Get available syntax highlight categories
SStringVector
SSyntax::getCategories ()
{
  SStringVector ret;
//  ret.append ("test");
  ret.append ("hunspell");
  return SStringVector(ret);
}

// Get available syntax within a category. Please note that
// syntax itself should be unique across all categories.
SStringVector
SSyntax::getAvaliableList (const SString& category)
{
  SStringVector ret;
  if (category == "test")
  {
    ret.append ("lines");
    ret.append ("yuko");
    return SStringVector(ret);
  }
  SProperties   prop;
  unsigned int i;
  unsigned int j;
  if (category == "hunspell")
  {
    SStringVector p("*.dic");
    SStringVector spath = getPath();
    for (i=spath.size(); i>0; i--)
    {
      SDir dir (spath[i-1]);
      SStringVector f = dir.list (p);
      for (unsigned int j=0; j<f.size(); j++)
      {
        SString s = f[j];
        if (s.size() > 4) s.truncate (s.size()-4);
        prop.put (s, s);
      }
    }
  }
  for (i=0; i<prop.size(); i++)
  {
    for (j=0; j<prop.size(i); j++)
    {
       ret.append (*prop.get (i, j));
    }
  }
  ret.sort();
  return SStringVector(ret);
}

SString
SSyntax::getFolderFor (const SString& name)
{
  SStringVector v (name, ":", true);
  if (v.size () != 2) return (SString ("none"));
  if (v[0] == "simple") return  (SString ("built-in"));
  if (v[0] == "test") return  (SString ("built-in"));
  if (v[0] == "hunspell")
  {
    return SHunspellPattern::getFolderFor (v[1], getPath());
  }
  return SString ("");
}

SString
SSyntax::getMissingFile (const SString& name)
{
  SStringVector v (name, ":", true);
  if (v.size () != 2) return (SString (""));
  if (v[0] == "simple") return  (SString (""));
  if (v[0] == "test") return  (SString (""));
  if (v[0] == "hunspell")
  {
    return SHunspellPattern::getMissingFile (v[1], getPath());
  }
  return SString ("");
}
