/*
   +----------------------------------------------------------------------+
   | HipHop for PHP                                                       |
   +----------------------------------------------------------------------+
   | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com)  |
   | Copyright (c) 1997-2010 The PHP Group                                |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
*/
#ifndef incl_HPHP_ICU_ITERATOR_H
#define incl_HPHP_ICU_ITERATOR_H

#include "hphp/runtime/ext/extension.h"
#include "hphp/runtime/ext/icu/icu.h"

#include <unicode/strenum.h>

namespace HPHP { namespace Intl {
/////////////////////////////////////////////////////////////////////////////
extern const StaticString s_IntlIterator;

struct IntlIterator : IntlError {
  IntlIterator() {}
  IntlIterator(const IntlIterator&) = delete;
  IntlIterator& operator=(const IntlIterator& src) {
    setEnumeration(src.enumeration()->clone());
    return *this;
  }
  ~IntlIterator() {
    setEnumeration(nullptr);
  }

  void setEnumeration(icu::StringEnumeration *se) {
    if (m_enum) {
      delete m_enum;
    }
    m_enum = se;
  }

  bool isValid() const {
    return m_enum;
  }

  static Object newInstance(icu::StringEnumeration *se = nullptr) {
    if (!c_IntlIterator) {
      c_IntlIterator = Unit::lookupClass(s_IntlIterator.get());
      assertx(c_IntlIterator);
    }
    Object obj{c_IntlIterator};
    if (se) {
      Native::data<IntlIterator>(obj)->setEnumeration(se);
    }
    return obj;
  }

  static IntlIterator* Get(ObjectData* obj) {
    return GetData<IntlIterator>(obj, s_IntlIterator);
  }

  int64_t key() const { return m_key; }
  Variant current() const { return m_current; }
  bool valid() const { return m_current.isString(); }

  Variant next() {
    UErrorCode error = U_ZERO_ERROR;
    int32_t len;
    const char *e = m_enum->next(&len, error);
    if (U_FAILURE(error)) {
      s_intl_error->setError(error, "Error fetching next iteration element");
      m_current = uninit_null();
    } else {
      m_current = String(e, len, CopyString);
    }
    m_key++;
    return m_current;
  }

  bool rewind() {
    UErrorCode error = U_ZERO_ERROR;
    m_enum->reset(error);
    if (U_FAILURE(error)) {
      s_intl_error->setError(error, "Error resetting enumeration");
      m_current = uninit_null();
      return false;
    }
    m_key = -1;
    next();
    return true;
  }

  icu::StringEnumeration *enumeration() const { return m_enum; }

private:
  icu::StringEnumeration *m_enum = nullptr;
  int64_t m_key = -1;
  Variant m_current{Variant::NullInit()};

  static Class* c_IntlIterator;
};

#if U_ICU_VERSION_MAJOR_NUM * 10 + U_ICU_VERSION_MINOR_NUM >= 42
// Proxy StringEnumeration for consistent behavior
struct BugStringCharEnumeration : icu::StringEnumeration {
  explicit BugStringCharEnumeration(UEnumeration* _uenum) : uenum(_uenum) {}
  ~BugStringCharEnumeration() { uenum_close(uenum); }

  int32_t count(UErrorCode& status) const override {
    return uenum_count(uenum, &status);
  }

  const icu::UnicodeString* snext(UErrorCode& status) override {
    int32_t length;
    const UChar* str = uenum_unext(uenum, &length, &status);
    if (str == 0 || U_FAILURE(status)) {
      return 0;
    }
    return &unistr.setTo(str, length);
  }

  const char* next(int32_t *resultLength, UErrorCode &status) override {
    int32_t length = -1;
    const char* str = uenum_next(uenum, &length, &status);
    if (str == 0 || U_FAILURE(status)) {
      return 0;
    }
    if (resultLength) {
      //the bug is that uenum_next doesn't set the length
      *resultLength = (length == -1) ? strlen(str) : length;
    }

    return str;
  }

  void reset(UErrorCode& status) override {
    uenum_reset(uenum, &status);
  }

  // Defined by UOBJECT_DEFINE_RTTI_IMPLEMENTATION
  UClassID getDynamicClassID() const override;
  static UClassID U_EXPORT2 getStaticClassID();

 private:
  UEnumeration *uenum;
};
#endif // icu >= 4.2

/////////////////////////////////////////////////////////////////////////////
}} // namespace HPHP::Intl

#endif // incl_HPHP_ICU_ITERATOR_H
