/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsMathMLOperators.h"
#include "nsCOMPtr.h"
#include "nsDataHashtable.h"
#include "nsHashKeys.h"
#include "nsTArray.h"

#include "nsIPersistentProperties2.h"
#include "nsISimpleEnumerator.h"
#include "nsContentUtils.h"
#include "nsCRT.h"

// operator dictionary entry
struct OperatorData {
  OperatorData(void)
    : mFlags(0),
      mLeadingSpace(0.0f),
      mTrailingSpace(0.0f)
  {
  }

  // member data
  nsString        mStr;
  nsOperatorFlags mFlags;
  float           mLeadingSpace;   // unit is em
  float           mTrailingSpace;  // unit is em
};

static int32_t         gTableRefCount = 0;
static uint32_t        gOperatorCount = 0;
static OperatorData*   gOperatorArray = nullptr;
static nsDataHashtable<nsStringHashKey, OperatorData*>* gOperatorTable = nullptr;
static bool            gGlobalsInitialized   = false;

static const char16_t kDashCh  = char16_t('#');
static const char16_t kColonCh = char16_t(':');

static void
SetBooleanProperty(OperatorData* aOperatorData,
                   nsString      aName)
{
  if (aName.IsEmpty())
    return;

  if (aName.EqualsLiteral("stretchy") && (1 == aOperatorData->mStr.Length()))
    aOperatorData->mFlags |= NS_MATHML_OPERATOR_STRETCHY;
  else if (aName.EqualsLiteral("fence"))
    aOperatorData->mFlags |= NS_MATHML_OPERATOR_FENCE;
  else if (aName.EqualsLiteral("accent"))
    aOperatorData->mFlags |= NS_MATHML_OPERATOR_ACCENT;
  else if (aName.EqualsLiteral("largeop"))
    aOperatorData->mFlags |= NS_MATHML_OPERATOR_LARGEOP;
  else if (aName.EqualsLiteral("separator"))
    aOperatorData->mFlags |=  NS_MATHML_OPERATOR_SEPARATOR;
  else if (aName.EqualsLiteral("movablelimits"))
    aOperatorData->mFlags |= NS_MATHML_OPERATOR_MOVABLELIMITS;
  else if (aName.EqualsLiteral("symmetric"))
    aOperatorData->mFlags |= NS_MATHML_OPERATOR_SYMMETRIC;
  else if (aName.EqualsLiteral("integral"))
    aOperatorData->mFlags |= NS_MATHML_OPERATOR_INTEGRAL;
  else if (aName.EqualsLiteral("mirrorable"))
    aOperatorData->mFlags |= NS_MATHML_OPERATOR_MIRRORABLE;
}

static void
SetProperty(OperatorData* aOperatorData,
            nsString      aName,
            nsString      aValue)
{
  if (aName.IsEmpty() || aValue.IsEmpty())
    return;

  // XXX These ones are not kept in the dictionary
  // Support for these requires nsString member variables 
  // maxsize (default: infinity)
  // minsize (default: 1)

  if (aName.EqualsLiteral("direction")) {
    if (aValue.EqualsLiteral("vertical"))
      aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_VERTICAL;
    else if (aValue.EqualsLiteral("horizontal"))
      aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_HORIZONTAL;
    else return; // invalid value
  } else {
    bool isLeadingSpace;
    if (aName.EqualsLiteral("lspace"))
      isLeadingSpace = true;
    else if (aName.EqualsLiteral("rspace"))
      isLeadingSpace = false;
    else return;  // input is not applicable

    // aValue is assumed to be a digit from 0 to 7
    nsresult error = NS_OK;
    float space = aValue.ToFloat(&error) / 18.0;
    if (NS_FAILED(error)) return;

    if (isLeadingSpace)
      aOperatorData->mLeadingSpace = space;
    else
      aOperatorData->mTrailingSpace = space;
  }
}

static bool
SetOperator(OperatorData*   aOperatorData,
            nsOperatorFlags aForm,
            const nsCString& aOperator,
            nsString&        aAttributes)

{
  static const char16_t kNullCh = char16_t('\0');

  // aOperator is in the expanded format \uNNNN\uNNNN ...
  // First compress these Unicode points to the internal nsString format
  int32_t i = 0;
  nsAutoString name, value;
  int32_t len = aOperator.Length();
  char16_t c = aOperator[i++];
  uint32_t state  = 0;
  char16_t uchar = 0;
  while (i <= len) {
    if (0 == state) {
      if (c != '\\')
        return false;
      if (i < len)
        c = aOperator[i];
      i++;
      if (('u' != c) && ('U' != c))
        return false;
      if (i < len)
        c = aOperator[i];
      i++;
      state++;
    }
    else {
      if (('0' <= c) && (c <= '9'))
         uchar = (uchar << 4) | (c - '0');
      else if (('a' <= c) && (c <= 'f'))
         uchar = (uchar << 4) | (c - 'a' + 0x0a);
      else if (('A' <= c) && (c <= 'F'))
         uchar = (uchar << 4) | (c - 'A' + 0x0a);
      else return false;
      if (i < len)
        c = aOperator[i];
      i++;
      state++;
      if (5 == state) {
        value.Append(uchar);
        uchar = 0;
        state = 0;
      }
    }
  }
  if (0 != state) return false;

  // Quick return when the caller doesn't care about the attributes and just wants
  // to know if this is a valid operator (this is the case at the first pass of the
  // parsing of the dictionary in InitOperators())
  if (!aForm) return true;

  // Add operator to hash table
  aOperatorData->mFlags |= aForm;
  aOperatorData->mStr.Assign(value);
  value.AppendInt(aForm, 10);
  gOperatorTable->Put(value, aOperatorData);

#ifdef DEBUG
  NS_LossyConvertUTF16toASCII str(aAttributes);
#endif
  // Loop over the space-delimited list of attributes to get the name:value pairs
  aAttributes.Append(kNullCh);  // put an extra null at the end
  char16_t* start = aAttributes.BeginWriting();
  char16_t* end   = start;
  while ((kNullCh != *start) && (kDashCh != *start)) {
    name.SetLength(0);
    value.SetLength(0);
    // skip leading space, the dash amounts to the end of the line
    while ((kNullCh!=*start) && (kDashCh!=*start) && nsCRT::IsAsciiSpace(*start)) {
      ++start;
    }
    end = start;
    // look for ':'
    while ((kNullCh!=*end) && (kDashCh!=*end) && !nsCRT::IsAsciiSpace(*end) &&
           (kColonCh!=*end)) {
      ++end;
    }
    // If ':' is not found, then it's a boolean property
    bool IsBooleanProperty = (kColonCh != *end);
    *end = kNullCh; // end segment here
    // this segment is the name
    if (start < end) {
      name.Assign(start);
    }
    if (IsBooleanProperty) {
      SetBooleanProperty(aOperatorData, name);
    } else {
      start = ++end;
      // look for space or end of line
      while ((kNullCh!=*end) && (kDashCh!=*end) &&
             !nsCRT::IsAsciiSpace(*end)) {
        ++end;
      }
      *end = kNullCh; // end segment here
      if (start < end) {
        // this segment is the value
        value.Assign(start);
      }
      SetProperty(aOperatorData, name, value);
    }
    start = ++end;
  }
  return true;
}

static nsresult
InitOperators(void)
{
  // Load the property file containing the Operator Dictionary
  nsresult rv;
  nsCOMPtr<nsIPersistentProperties> mathfontProp;
  rv = NS_LoadPersistentPropertiesFromURISpec(
         getter_AddRefs(mathfontProp),
         NS_LITERAL_CSTRING("resource://gre/res/fonts/mathfont.properties"));

  if (NS_FAILED(rv)) return rv;

  // Parse the Operator Dictionary in two passes.
  // The first pass is to count the number of operators; the second pass is to
  // allocate the necessary space for them and to add them in the hash table.
  for (int32_t pass = 1; pass <= 2; pass++) {
    OperatorData dummyData;
    OperatorData* operatorData = &dummyData;
    nsCOMPtr<nsISimpleEnumerator> iterator;
    if (NS_SUCCEEDED(mathfontProp->Enumerate(getter_AddRefs(iterator)))) {
      bool more;
      uint32_t index = 0;
      nsAutoCString name;
      nsAutoString attributes;
      while ((NS_SUCCEEDED(iterator->HasMoreElements(&more))) && more) {
        nsCOMPtr<nsISupports> supports;
        nsCOMPtr<nsIPropertyElement> element;
        if (NS_SUCCEEDED(iterator->GetNext(getter_AddRefs(supports)))) {
          element = do_QueryInterface(supports);
          if (NS_SUCCEEDED(element->GetKey(name)) &&
              NS_SUCCEEDED(element->GetValue(attributes))) {
            // expected key: operator.\uNNNN.{infix,postfix,prefix}
            if ((21 <= name.Length()) && (0 == name.Find("operator.\\u"))) {
              name.Cut(0, 9); // 9 is the length of "operator.";
              int32_t len = name.Length();
              nsOperatorFlags form = 0;
              if (kNotFound != name.RFind(".infix")) {
                form = NS_MATHML_OPERATOR_FORM_INFIX;
                len -= 6;  // 6 is the length of ".infix";
              }
              else if (kNotFound != name.RFind(".postfix")) {
                form = NS_MATHML_OPERATOR_FORM_POSTFIX;
                len -= 8; // 8 is the length of ".postfix";
              }
              else if (kNotFound != name.RFind(".prefix")) {
                form = NS_MATHML_OPERATOR_FORM_PREFIX;
                len -= 7; // 7 is the length of ".prefix";
              }
              else continue; // input is not applicable
              name.SetLength(len);
              if (2 == pass) { // allocate space and start the storage
                if (!gOperatorArray) {
                  if (0 == gOperatorCount) return NS_ERROR_UNEXPECTED;
                  gOperatorArray = new OperatorData[gOperatorCount];
                  if (!gOperatorArray) return NS_ERROR_OUT_OF_MEMORY;
                }
                operatorData = &gOperatorArray[index];
              }
              else {
                form = 0; // to quickly return from SetOperator() at pass 1
              }
              // See if the operator should be retained
              if (SetOperator(operatorData, form, name, attributes)) {
                index++;
                if (1 == pass) gOperatorCount = index;
              }
            }
          }
        }
      }
    }
  }
  return NS_OK;
}

static nsresult
InitOperatorGlobals()
{
  gGlobalsInitialized = true;
  nsresult rv = NS_ERROR_OUT_OF_MEMORY;
  gOperatorTable = new nsDataHashtable<nsStringHashKey, OperatorData*>();
  if (gOperatorTable) {
    rv = InitOperators();
  }
  if (NS_FAILED(rv))
    nsMathMLOperators::CleanUp();
  return rv;
}

void
nsMathMLOperators::CleanUp()
{
  if (gOperatorArray) {
    delete[] gOperatorArray;
    gOperatorArray = nullptr;
  }
  if (gOperatorTable) {
    delete gOperatorTable;
    gOperatorTable = nullptr;
  }
}

void
nsMathMLOperators::AddRefTable(void)
{
  gTableRefCount++;
}

void
nsMathMLOperators::ReleaseTable(void)
{
  if (0 == --gTableRefCount) {
    CleanUp();
  }
}

static OperatorData*
GetOperatorData(const nsString& aOperator, nsOperatorFlags aForm)
{
  nsAutoString key(aOperator);
  key.AppendInt(aForm);
  return gOperatorTable->Get(key);
}

bool
nsMathMLOperators::LookupOperator(const nsString&       aOperator,
                                  const nsOperatorFlags aForm,
                                  nsOperatorFlags*      aFlags,
                                  float*                aLeadingSpace,
                                  float*                aTrailingSpace)
{
  if (!gGlobalsInitialized) {
    InitOperatorGlobals();
  }
  if (gOperatorTable) {
    NS_ASSERTION(aFlags && aLeadingSpace && aTrailingSpace, "bad usage");
    NS_ASSERTION(aForm > 0 && aForm < 4, "*** invalid call ***");

    // The MathML REC says:
    // If the operator does not occur in the dictionary with the specified form,
    // the renderer should use one of the forms which is available there, in the
    // order of preference: infix, postfix, prefix.

    OperatorData* found;
    int32_t form = NS_MATHML_OPERATOR_GET_FORM(aForm);
    if (!(found = GetOperatorData(aOperator, form))) {
      if (form == NS_MATHML_OPERATOR_FORM_INFIX ||
          !(found =
            GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_INFIX))) {
        if (form == NS_MATHML_OPERATOR_FORM_POSTFIX ||
            !(found =
              GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_POSTFIX))) {
          if (form != NS_MATHML_OPERATOR_FORM_PREFIX) {
            found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_PREFIX);
          }
        }
      }
    }
    if (found) {
      NS_ASSERTION(found->mStr.Equals(aOperator), "bad setup");
      *aLeadingSpace = found->mLeadingSpace;
      *aTrailingSpace = found->mTrailingSpace;
      *aFlags &= ~NS_MATHML_OPERATOR_FORM; // clear the form bits
      *aFlags |= found->mFlags; // just add bits without overwriting
      return true;
    }
  }
  return false;
}

void
nsMathMLOperators::LookupOperators(const nsString&       aOperator,
                                   nsOperatorFlags*      aFlags,
                                   float*                aLeadingSpace,
                                   float*                aTrailingSpace)
{
  if (!gGlobalsInitialized) {
    InitOperatorGlobals();
  }

  aFlags[NS_MATHML_OPERATOR_FORM_INFIX] = 0;
  aLeadingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = 0.0f;
  aTrailingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = 0.0f;

  aFlags[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0;
  aLeadingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0.0f;
  aTrailingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0.0f;

  aFlags[NS_MATHML_OPERATOR_FORM_PREFIX] = 0;
  aLeadingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = 0.0f;
  aTrailingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = 0.0f;

  if (gOperatorTable) {
    OperatorData* found;
    found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_INFIX);
    if (found) {
      aFlags[NS_MATHML_OPERATOR_FORM_INFIX] = found->mFlags;
      aLeadingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = found->mLeadingSpace;
      aTrailingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = found->mTrailingSpace;
    }
    found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_POSTFIX);
    if (found) {
      aFlags[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mFlags;
      aLeadingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mLeadingSpace;
      aTrailingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mTrailingSpace;
    }
    found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_PREFIX);
    if (found) {
      aFlags[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mFlags;
      aLeadingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mLeadingSpace;
      aTrailingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mTrailingSpace;
    }
  }
}

/* static */ bool
nsMathMLOperators::IsMirrorableOperator(const nsString& aOperator)
{
  // LookupOperator will search infix, postfix and prefix forms of aOperator and
  // return the first form found. It is assumed that all these forms have same
  // mirrorability.
  nsOperatorFlags flags = 0;
  float dummy;
  nsMathMLOperators::LookupOperator(aOperator,
                                    NS_MATHML_OPERATOR_FORM_INFIX,
                                    &flags, &dummy, &dummy);
  return NS_MATHML_OPERATOR_IS_MIRRORABLE(flags);
}

/* static */ nsStretchDirection
nsMathMLOperators::GetStretchyDirection(const nsString& aOperator)
{
  // LookupOperator will search infix, postfix and prefix forms of aOperator and
  // return the first form found. It is assumed that all these forms have same
  // direction.
  nsOperatorFlags flags = 0;
  float dummy;
  nsMathMLOperators::LookupOperator(aOperator,
                                    NS_MATHML_OPERATOR_FORM_INFIX,
                                    &flags, &dummy, &dummy);

  if (NS_MATHML_OPERATOR_IS_DIRECTION_VERTICAL(flags)) {
      return NS_STRETCH_DIRECTION_VERTICAL;
  } else if (NS_MATHML_OPERATOR_IS_DIRECTION_HORIZONTAL(flags)) {
    return NS_STRETCH_DIRECTION_HORIZONTAL;
  } else {
    return NS_STRETCH_DIRECTION_UNSUPPORTED;
  }
}
