/* 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/. */

/* Classes to iterate over grapheme, word, sentence, or line. */

#include "mozilla/intl/Segmenter.h"

#include "icu4x/GraphemeClusterSegmenter.hpp"
#include "icu4x/LineSegmenter.hpp"
#include "icu4x/SentenceSegmenter.hpp"
#include "icu4x/WordSegmenter.hpp"
#include "mozilla/ClearOnShutdown.h"
#include "nsUnicodeProperties.h"
#include "mozilla/Utf16.h"
#include "nsThreadUtils.h"

#include <mutex>

using namespace icu4x;
using namespace mozilla::unicode;

namespace mozilla::intl {

SegmentIteratorUtf16::SegmentIteratorUtf16(Span<const char16_t> aText)
    : mText(aText) {}

Maybe<uint32_t> SegmentIteratorUtf16::Seek(uint32_t aPos) {
  if (mPos < aPos) {
    mPos = aPos;
  }
  return Next();
}

LineBreakIteratorUtf16::LineBreakIteratorUtf16(Span<const char16_t> aText,
                                               const LineBreakOptions& aOptions)
    : SegmentIteratorUtf16(aText), mOptions(aOptions) {
  mSegmenter = capi::icu4x_LineSegmenter_create_auto_mv1();
  mIterator = capi::icu4x_LineSegmenter_segment_utf16_mv1(
      mSegmenter, {mText.Elements(), mText.Length()});
}

LineBreakIteratorUtf16::~LineBreakIteratorUtf16() {
  capi::icu4x_LineBreakIteratorUtf16_destroy_mv1(mIterator);
  capi::icu4x_LineSegmenter_destroy_mv1(mSegmenter);
}

Maybe<uint32_t> LineBreakIteratorUtf16::Next() {
  MOZ_ASSERT(mIterator);

  const int32_t nextPos =
      capi::icu4x_LineBreakIteratorUtf16_next_mv1(mIterator);
  if (nextPos < 0) {
    return Nothing();
  }
  if (!nextPos) {
    return Next();
  }
  mPos = nextPos;
  return Some(mPos);
}

Maybe<uint32_t> LineBreakIteratorUtf16::Seek(uint32_t aPos) {
  MOZ_ASSERT(mIterator);

  if (mPos >= aPos) {
    return Next();
  }

  while (mPos < aPos) {
    const int32_t nextPos =
        capi::icu4x_LineBreakIteratorUtf16_next_mv1(mIterator);
    if (nextPos < 0) {
      return Nothing();
    }
    mPos = static_cast<uint32_t>(nextPos);
  }

  if (aPos < mPos) {
    return Some(mPos);
  }

  return Next();
}

WordBreakIteratorUtf16::WordBreakIteratorUtf16(Span<const char16_t> aText)
    : SegmentIteratorUtf16(aText) {
  mSegmenter = capi::icu4x_WordSegmenter_create_auto_mv1();
  mIterator = capi::icu4x_WordSegmenter_segment_utf16_mv1(
      mSegmenter, {mText.Elements(), mText.Length()});
}

WordBreakIteratorUtf16::~WordBreakIteratorUtf16() {
  capi::icu4x_WordBreakIteratorUtf16_destroy_mv1(mIterator);
  capi::icu4x_WordSegmenter_destroy_mv1(mSegmenter);
}

void WordBreakIteratorUtf16::Reset(Span<const char16_t> aText) {
  mPos = 0;
  mText = aText;
  if (mIterator) {
    capi::icu4x_WordBreakIteratorUtf16_destroy_mv1(mIterator);
    mIterator = nullptr;
  }
  mIterator = capi::icu4x_WordSegmenter_segment_utf16_mv1(
      mSegmenter, {mText.Elements(), mText.Length()});
}

Maybe<uint32_t> WordBreakIteratorUtf16::Next() {
  MOZ_ASSERT(mIterator);

  const int32_t nextPos =
      capi::icu4x_WordBreakIteratorUtf16_next_mv1(mIterator);
  if (nextPos < 0) {
    return Nothing();
  }
  if (!nextPos) {
    return Next();
  }
  mPos = nextPos;
  return Some(mPos);
}

Maybe<uint32_t> WordBreakIteratorUtf16::Seek(uint32_t aPos) {
  MOZ_ASSERT(mIterator);

  if (mPos >= aPos) {
    return Next();
  }

  while (mPos < aPos) {
    const int32_t nextPos =
        capi::icu4x_WordBreakIteratorUtf16_next_mv1(mIterator);
    if (nextPos < 0) {
      return Nothing();
    }
    mPos = static_cast<uint32_t>(nextPos);
  }

  if (aPos < mPos) {
    return Some(mPos);
  }

  return Next();
}

capi::GraphemeClusterSegmenter* GraphemeClusterBreakIteratorUtf16::sSegmenter =
    nullptr;

GraphemeClusterBreakIteratorUtf16::GraphemeClusterBreakIteratorUtf16(
    Span<const char16_t> aText)
    : SegmentIteratorUtf16(aText) {
  static std::once_flag sOnce;

  std::call_once(sOnce, [] {
    auto result = capi::icu4x_GraphemeClusterSegmenter_create_mv1();
    sSegmenter = result;

    NS_DispatchToMainThread(
        NS_NewRunnableFunction("GraphemeClusterBreakIteratorUtf16", [] {
          RunOnShutdown([] {
            capi::icu4x_GraphemeClusterSegmenter_destroy_mv1(sSegmenter);
            sSegmenter = nullptr;
          });
        }));
  });

  MOZ_RELEASE_ASSERT(sSegmenter);
  mIterator = capi::icu4x_GraphemeClusterSegmenter_segment_utf16_mv1(
      sSegmenter, {mText.Elements(), mText.Length()});
}

GraphemeClusterBreakIteratorUtf16::~GraphemeClusterBreakIteratorUtf16() {
  capi::icu4x_GraphemeClusterBreakIteratorUtf16_destroy_mv1(mIterator);
}

Maybe<uint32_t> GraphemeClusterBreakIteratorUtf16::Next() {
  MOZ_ASSERT(mIterator);

  const int32_t nextPos =
      capi::icu4x_GraphemeClusterBreakIteratorUtf16_next_mv1(mIterator);
  if (nextPos < 0) {
    return Nothing();
  }
  if (!nextPos) {
    return Next();
  }
  mPos = nextPos;
  return Some(mPos);
}

Maybe<uint32_t> GraphemeClusterBreakIteratorUtf16::Seek(uint32_t aPos) {
  MOZ_ASSERT(mIterator);

  if (mPos >= aPos) {
    return Next();
  }

  while (mPos < aPos) {
    const int32_t nextPos =
        capi::icu4x_GraphemeClusterBreakIteratorUtf16_next_mv1(mIterator);
    if (nextPos < 0) {
      return Nothing();
    }
    mPos = static_cast<uint32_t>(nextPos);
  }

  if (aPos < mPos) {
    return Some(mPos);
  }

  return Next();
}

GraphemeClusterBreakReverseIteratorUtf16::
    GraphemeClusterBreakReverseIteratorUtf16(Span<const char16_t> aText)
    : SegmentIteratorUtf16(aText) {
  mPos = mText.Length();
}

Maybe<uint32_t> GraphemeClusterBreakReverseIteratorUtf16::Next() {
  if (mPos == 0) {
    return Nothing();
  }

  uint32_t ch;
  do {
    ch = mText[--mPos];

    if (mPos > 0 && IsSurrogatePair(mText[mPos - 1], ch)) {
      ch = SurrogateToUCS4(mText[--mPos], ch);
    }

    if (!IsClusterExtender(ch)) {
      break;
    }
  } while (mPos > 0);

  // XXX May need to handle conjoining Jamo

  return Some(mPos);
}

Maybe<uint32_t> GraphemeClusterBreakReverseIteratorUtf16::Seek(uint32_t aPos) {
  if (mPos > aPos) {
    mPos = aPos;
  }
  return Next();
}

SentenceBreakIteratorUtf16::SentenceBreakIteratorUtf16(
    Span<const char16_t> aText)
    : SegmentIteratorUtf16(aText) {
  mSegmenter = capi::icu4x_SentenceSegmenter_create_mv1();
  mIterator = capi::icu4x_SentenceSegmenter_segment_utf16_mv1(
      mSegmenter, {mText.Elements(), mText.Length()});
}

SentenceBreakIteratorUtf16::~SentenceBreakIteratorUtf16() {
  capi::icu4x_SentenceBreakIteratorUtf16_destroy_mv1(mIterator);
  capi::icu4x_SentenceSegmenter_destroy_mv1(mSegmenter);
}

Maybe<uint32_t> SentenceBreakIteratorUtf16::Seek(uint32_t aPos) {
  MOZ_ASSERT(mIterator);

  if (mPos >= aPos) {
    return Next();
  }

  while (mPos < aPos) {
    const int32_t nextPos =
        capi::icu4x_SentenceBreakIteratorUtf16_next_mv1(mIterator);
    if (nextPos < 0) {
      return Nothing();
    }
    mPos = static_cast<uint32_t>(nextPos);
  }

  if (aPos < mPos) {
    return Some(mPos);
  }

  return Next();
}

Maybe<uint32_t> SentenceBreakIteratorUtf16::Next() {
  MOZ_ASSERT(mIterator);

  const int32_t nextPos =
      capi::icu4x_SentenceBreakIteratorUtf16_next_mv1(mIterator);
  if (nextPos < 0) {
    return Nothing();
  }
  if (!nextPos) {
    return Next();
  }
  mPos = nextPos;
  return Some(mPos);
}

Result<UniquePtr<Segmenter>, ICUError> Segmenter::TryCreate(
    Span<const char> aLocale, const SegmenterOptions& aOptions) {
  return MakeUnique<Segmenter>(aLocale, aOptions);
}

UniquePtr<SegmentIteratorUtf16> Segmenter::Segment(
    Span<const char16_t> aText) const {
  switch (mOptions.mGranularity) {
    case SegmenterGranularity::Grapheme:
      return MakeUnique<GraphemeClusterBreakIteratorUtf16>(aText);
    case SegmenterGranularity::Sentence:
      return MakeUnique<SentenceBreakIteratorUtf16>(aText);
    case SegmenterGranularity::Word:
      return MakeUnique<WordBreakIteratorUtf16>(aText);
    case SegmenterGranularity::Line:
      return MakeUnique<LineBreakIteratorUtf16>(aText);
  }
  MOZ_ASSERT_UNREACHABLE("All granularities must be handled!");
  return nullptr;
}

}  // namespace mozilla::intl
