/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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 "DOMtoATK.h"
#include "nsUTF8Utils.h"

namespace mozilla {
namespace a11y {

namespace DOMtoATK {

void
AddBOMs(nsACString& aDest, const nsACString& aSource)
{
  uint32_t destlength = 0;

  // First compute how much room we will need.
  for (uint32_t srci = 0; srci < aSource.Length(); ) {
    int bytes = UTF8traits::bytes(aSource[srci]);
    if (bytes >= 4) {
      // Non-BMP character, will add a BOM after it.
      destlength += 3;
    }
    // Skip whole character encoding.
    srci += bytes;
    destlength += bytes;
  }

  uint32_t desti = 0; // Index within aDest.

  // Add BOMs after non-BMP characters.
  aDest.SetLength(destlength);
  for (uint32_t srci = 0; srci < aSource.Length(); ) {
    uint32_t bytes = UTF8traits::bytes(aSource[srci]);

    MOZ_ASSERT(bytes <= aSource.Length() - srci, "We should have the whole sequence");

    // Copy whole sequence.
    aDest.Replace(desti, bytes, Substring(aSource, srci, bytes));
    desti += bytes;
    srci += bytes;

    if (bytes >= 4) {
      // More than 4 bytes in UTF-8 encoding exactly means more than 16 encoded
      // bits.  This is thus a non-BMP character which needed a surrogate
      // pair to get encoded in UTF-16, add a BOM after it.

      // And add a BOM after it.
      aDest.Replace(desti, 3, "\xEF\xBB\xBF");
      desti += 3;
    }
  }
  MOZ_ASSERT(desti == destlength, "Incoherency between computed length"
                                  "and actually translated length");
}

void
ATKStringConverterHelper::AdjustOffsets(gint* aStartOffset, gint* aEndOffset,
                                        gint count)
{
  MOZ_ASSERT(!mAdjusted, "DOMtoATK::ATKStringConverterHelper::AdjustOffsets needs to be called only once");

  if (*aStartOffset > 0) {
    (*aStartOffset)--;
    mStartShifted = true;
  }

  if (*aEndOffset != -1 && *aEndOffset < count) {
    (*aEndOffset)++;
    mEndShifted = true;
  }

#ifdef DEBUG
  mAdjusted = true;
#endif
}

gchar*
ATKStringConverterHelper::FinishUTF16toUTF8(nsCString& aStr)
{
  int skip = 0;

  if (mStartShifted) {
    // AdjustOffsets added a leading character.

    MOZ_ASSERT(aStr.Length() > 0, "There should be a leading character");
    MOZ_ASSERT(static_cast<int>(aStr.Length()) >= UTF8traits::bytes(aStr.CharAt(0)),
               "The leading character should be complete");

    // drop first character
    skip = UTF8traits::bytes(aStr.CharAt(0));
  }

  if (mEndShifted) {
    // AdjustOffsets added a trailing character.

    MOZ_ASSERT(aStr.Length() > 0, "There should be a trailing character");

    int trail = -1;
    // Find beginning of last character.
    for (trail = aStr.Length() - 1; trail >= 0; trail--) {
      if (!UTF8traits::isInSeq(aStr.CharAt(trail))) {
        break;
      }
    }
    MOZ_ASSERT(trail >= 0,
               "There should be at least a whole trailing character");
    MOZ_ASSERT(trail + UTF8traits::bytes(aStr.CharAt(trail)) == static_cast<int>(aStr.Length()),
               "The trailing character should be complete");

    // Drop the last character.
    aStr.Truncate(trail);
  }

  // copy and return, libspi will free it
  return g_strdup(aStr.get() + skip);
}

gchar*
ATKStringConverterHelper::ConvertAdjusted(const nsAString& aStr)
{
  MOZ_ASSERT(mAdjusted, "DOMtoATK::ATKStringConverterHelper::AdjustOffsets needs to be called before ATKStringConverterHelper::ConvertAdjusted");

  NS_ConvertUTF16toUTF8 cautoStr(aStr);
  if (!cautoStr.get()) {
    return nullptr;
  }

  nsAutoCString cautoStrBOMs;
  AddBOMs(cautoStrBOMs, cautoStr);
  return FinishUTF16toUTF8(cautoStrBOMs);
}

gchar*
Convert(const nsAString& aStr)
{
  NS_ConvertUTF16toUTF8 cautoStr(aStr);
  if (!cautoStr.get()) {
    return nullptr;
  }

  nsAutoCString cautoStrBOMs;
  AddBOMs(cautoStrBOMs, cautoStr);
  return g_strdup(cautoStrBOMs.get());
}

void
ConvertTexttoAsterisks(nsAString& aString)
{
  for (uint32_t i = 0; i < aString.Length(); i++) {
    aString.ReplaceLiteral(i, 1, u"*");
  }
}

}

} // namespace a11y
} // namespace mozilla
