// Created on: 1992-06-24
// Created by: Gilles DEBARBOUILLE
// Copyright (c) 1992-1999 Matra Datavision
// Copyright (c) 1999-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#include <Units_Lexicon.hxx>
#include <Units_NoSuchType.hxx>
#include <Units_Operators.hxx>
#include <Units_Sentence.hxx>
#include <Units_ShiftedToken.hxx>
#include <Units_Token.hxx>
#include <NCollection_Sequence.hxx>
#include <NCollection_HSequence.hxx>

static occ::handle<Units_Token> CreateTokenForNumber(const char* str)
{
  TCollection_AsciiString tstr    = str[0];
  bool                    IsPoint = false;
  size_t                  len     = strlen(str);
  for (size_t in = 1; in < len; in++)
  {
    if (str[in] == '0' || str[in] == '1' || str[in] == '2' || str[in] == '3' || str[in] == '4'
        || str[in] == '5' || str[in] == '6' || str[in] == '7' || str[in] == '8' || str[in] == '9')
    {
      tstr.AssignCat(str[in]);
    }
    else if (str[in] == '.' && !IsPoint)
    {
      tstr.AssignCat(str[in]);
      IsPoint = true;
    }
    else
      break;
  }
  return new Units_Token(tstr.ToCString(), "0");
}

//=================================================================================================

Units_Sentence::Units_Sentence(const occ::handle<Units_Lexicon>& alexicon, const char* astring)
{
  int                      index;
  size_t                   i, limchain;
  occ::handle<Units_Token> token;
  occ::handle<Units_Token> referencetoken;

  thesequenceoftokens = new NCollection_HSequence<occ::handle<Units_Token>>();
  occ::handle<NCollection_HSequence<occ::handle<Units_Token>>> lstlexicon = alexicon->Sequence();
  if (lstlexicon.IsNull())
    throw Units_NoSuchType("BAD LEXICON descriptor");
  limchain = strlen(astring);
  i        = 0;

  TCollection_AsciiString tmpstr = astring;
  // occ::handle<Units_Token> tmptoken;
  TCollection_AsciiString PrevMean;
  TCollection_AsciiString PrevWord;
  while (i < limchain)
  {
    bool                    IsFound  = false;
    TCollection_AsciiString LastWord = "";
    for (index = 1; index <= lstlexicon->Length(); index++)
    {
      referencetoken                = lstlexicon->Value(index);
      TCollection_AsciiString aword = referencetoken->Word();
      int                     num   = tmpstr.Search(aword);
      if (num == 1 && aword.Length() >= LastWord.Length())
      {
        token    = referencetoken->Creates();
        LastWord = aword;
        IsFound  = true;
      }
    }
    if (!IsFound)
    {
      // may be it is a number(degree)?
      LastWord = tmpstr.SubString(1, 1);
      if (!LastWord.IsIntegerValue())
      {
        // unknown expression - not create sentene
        thesequenceoftokens->Clear();
#ifdef OCCT_DEBUG
        std::cout << "Warning: can not create correct sentence from string: " << astring
                  << std::endl;
#endif
        return;
      }
      else
      {
        // create token for number
        token    = CreateTokenForNumber(tmpstr.ToCString());
        LastWord = token->Word();
      }
    }
    if (i > 0)
    {
      // make additional checking
      if ((token->Mean() == "M" && (PrevMean == "M" || PrevMean == "MU" || PrevMean == "0"))
          || (token->Mean() == "U" && (PrevMean == "U" || PrevMean == "0"))
          || (token->Mean() == "O" && (PrevMean == "M" || PrevMean == "O"))
          || ((token->Word() == "+" || token->Word() == "-") && PrevWord != "(")
          || (token->Mean() == "S" && (PrevMean == "M"))
          || (token->Mean() == "0" && (PrevMean == "M" || PrevMean == "U" || PrevMean == "MU")))
      {
        // incorrect situation
        thesequenceoftokens->Clear();
#ifdef OCCT_DEBUG
        std::cout << "Warning: can not create correct sentence from string: " << astring
                  << std::endl;
#endif
        return;
      }
    }
    thesequenceoftokens->Append(token);
    PrevMean = token->Mean();
    PrevWord = token->Word();
    i        = i + LastWord.Length();
    tmpstr.Remove(1, LastWord.Length());
  }
}

//=================================================================================================

void Units_Sentence::SetConstants()
{
  int                      index;
  double                   value;
  occ::handle<Units_Token> token;
  TCollection_AsciiString  string;

  for (index = 1; index <= thesequenceoftokens->Length(); index++)
  {
    token = thesequenceoftokens->Value(index);
    if (token->Value() == 0.)
    {
      string = token->Word();
      if (string.IsRealValue())
      {
        value = string.RealValue();
        token->Mean("K");
        token->Value(value);
      }
    }
  }
}

//=================================================================================================

static occ::handle<Units_Token> CalculateLocal(
  const occ::handle<NCollection_HSequence<occ::handle<Units_Token>>>& aSeq)
{
  // std::cout<<std::endl;
  // for(int index=1; index<=aSeq->Length(); index++) {
  //   occ::handle<Units_Token> tok = aSeq->Value(index);
  //   std::cout<<tok->Word()<<" ";
  // }
  // std::cout<<std::endl;
  occ::handle<Units_Token> tok1, tok2;
  int                      i, j;

  if (aSeq->Length() == 1)
  {
    return aSeq->Value(1);
  }

  // case of unar sign
  if (aSeq->Length() == 2)
  {
    if (aSeq->Value(1)->Word() == "+")
      aSeq->Remove(1);
    if (aSeq->Value(1)->Word() == "-")
    {
      tok2                          = aSeq->Value(2);
      TCollection_AsciiString aword = "-";
      aword.AssignCat(tok2->Word());
      tok1 = new Units_Token(aword.ToCString(),
                             tok2->Mean().ToCString(),
                             tok2->Value() * (-1.0),
                             tok2->Dimensions());
      aSeq->Remove(1);
      aSeq->SetValue(1, tok1);
    }
    return aSeq->Value(1);
  }

  bool IsBracket = true;
  while (IsBracket)
  {
    for (i = 1; i <= aSeq->Length(); i++)
    {
      if (aSeq->Value(i)->Word() == "(")
      {
        occ::handle<NCollection_HSequence<occ::handle<Units_Token>>> TmpSeq =
          new NCollection_HSequence<occ::handle<Units_Token>>;
        int NbBrackets = 1;
        for (j = i + 1; j <= aSeq->Length(); j++)
        {
          if (aSeq->Value(j)->Word() == ")")
            NbBrackets--;
          if (aSeq->Value(j)->Word() == "(")
            NbBrackets++;
          if (NbBrackets > 0)
            TmpSeq->Append(aSeq->Value(j));
          else
            break;
        }
        tok1 = CalculateLocal(TmpSeq);
        aSeq->Remove(i + 1, j);
        aSeq->SetValue(i, tok1);
        break;
      }
    }
    if (i > aSeq->Length())
    {
      IsBracket = false;
      // there are not brackets => make calculations
      // step 1: operation '**'
      for (i = 1; i <= aSeq->Length(); i++)
      {
        if (aSeq->Value(i)->Word() == "**")
        {
          tok1 = aSeq->Value(i - 1);
          tok2 = aSeq->Value(i + 1);
          tok1 = pow(tok1, tok2);
          aSeq->Remove(i);
          aSeq->Remove(i);
          aSeq->SetValue(i - 1, tok1);
          i--;
        }
      }
      // step 2: operation '*', '.' and '/'
      for (i = 1; i <= aSeq->Length(); i++)
      {
        if (aSeq->Value(i)->Mean() == "O")
        {
          tok1 = aSeq->Value(i - 1);
          tok2 = aSeq->Value(i + 1);
          if (aSeq->Value(i)->Word() == "/")
            tok1 = tok1 / tok2;
          else
            tok1 = tok1 * tok2;
          aSeq->Remove(i);
          aSeq->Remove(i);
          aSeq->SetValue(i - 1, tok1);
          i--;
        }
      }
      // now aSeq have to include only one result token
    }
  }
  return tok1;
}

//=================================================================================================

occ::handle<Units_Token> Units_Sentence::Evaluate()
{
  occ::handle<Units_Token> rtoken, ktoken;
  if (thesequenceoftokens->Length() == 0)
    return rtoken;

  /* old variant
    int index;
    static char *ooper[6] = { "+", "-", "*", ".", "/", "**" };
    int numterm,i,j,k,maxlevel,curlevel,level[255];
    occ::handle<Units_Token> currenttoken;
    TCollection_AsciiString string;
    TCollection_AsciiString mean;
    TCollection_AsciiString oper;

    numterm=curlevel=i=0;
    for(index=1;index<=thesequenceoftokens->Length();index++) {
      string = thesequenceoftokens->Value(index)->Word();

      if( string=="(" ) {
        level[numterm]=curlevel;
        curlevel++;
      }
      else if( string==")" ) {
        curlevel--;
        level[numterm]=curlevel;
      }
      else {
        level[numterm]=curlevel;
      }
      numterm++;
    }

    //if(IPrint==1) {
    //  std::cout<<std::endl;
    //  for(index=1; index<=thesequenceoftokens->Length(); index++) {
    //    occ::handle<Units_Token> tok = thesequenceoftokens->Value(index);
    //    std::cout<<tok->Word()<<" ";
    //  }
    //  std::cout<<std::endl;
    //  for(index=1; index<=thesequenceoftokens->Length(); index++) {
    //    std::cout<<level[index-1]<<" ";
    //  }
    //  std::cout<<std::endl;
    //}

    if(curlevel) {
      std::cout<<" Incorrect number of brackets"<<std::endl;
      return new Units_Token();
    }

    for(k=0; k<6; k++) {
      for(index=1,i=0; index<=thesequenceoftokens->Length(); index++,i++)	{
        if( thesequenceoftokens->Value(index)->Word() == ooper[k] ) {
          j=i+1;
          curlevel=level[j];
          while(j < numterm && level[j] >= curlevel)
            level[j++]++;
          j=i-1;
          curlevel=level[j];
          while(j >= 0 && level[j] >= curlevel)
            level[j--]++;
        }
      }
    }

    occ::handle<Units_Dimensions> aDim;

    numterm--;
    while(numterm>0) {

      maxlevel=0;
      for(i=0; i<=numterm; i++)if(level[i] > maxlevel)
        maxlevel=level[i];

      i=0;
      while(level[i] != maxlevel)
        i++;

      oper.Clear();

      k=i;
      while( k<=numterm && level[k]==maxlevel ) {
        ktoken=thesequenceoftokens->Value(k+1);
        mean=ktoken->Mean();
        if(mean=="O") {
          oper = ktoken->Word();
        }
        else if(mean!="S") {
          if     (oper == "/")  rtoken = rtoken / ktoken;
          else if(oper == "+")  rtoken = rtoken + ktoken;
          else if(oper == "-")  rtoken = rtoken - ktoken;
          else if(oper == "*")  rtoken = rtoken * ktoken;
          else if(oper == "**") rtoken = pow(rtoken,ktoken);
          else if(oper == ".")  rtoken = rtoken * ktoken;
          else                  rtoken = ktoken->Creates();
        }
        k++;
      }
      thesequenceoftokens->SetValue(i+1,rtoken);
      level[i]=maxlevel-1;

      if( (i+1) != k ) {
        thesequenceoftokens->Remove(i+2,k);
        j=i;
        while(k <= numterm)
          level[++j]=level[k++];
        numterm=j;
      }
    }

    rtoken = thesequenceoftokens->Value(1)->Creates();

    if( rtoken->Value()==0. ) {
      std::cout << "*BAD token value from unit '" << rtoken->Word() << "'" << std::endl;
      rtoken->Value(1.);
    }
  */ // end old variant

  // variant skl 15.09.2005
  rtoken = CalculateLocal(thesequenceoftokens);

  // rtoken->Dump(0,1);

  return rtoken;
}
