/* 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 "GreekCasing.h"
#include "nsUnicharUtils.h"
#include "nsUnicodeProperties.h"

// Custom uppercase mapping for Greek; see bug 307039 for details
#define GREEK_LOWER_ALPHA 0x03B1
#define GREEK_LOWER_ALPHA_TONOS 0x03AC
#define GREEK_LOWER_ALPHA_OXIA 0x1F71
#define GREEK_LOWER_EPSILON 0x03B5
#define GREEK_LOWER_EPSILON_TONOS 0x03AD
#define GREEK_LOWER_EPSILON_OXIA 0x1F73
#define GREEK_LOWER_ETA 0x03B7
#define GREEK_LOWER_ETA_TONOS 0x03AE
#define GREEK_LOWER_ETA_OXIA 0x1F75
#define GREEK_LOWER_IOTA 0x03B9
#define GREEK_LOWER_IOTA_TONOS 0x03AF
#define GREEK_LOWER_IOTA_OXIA 0x1F77
#define GREEK_LOWER_IOTA_DIALYTIKA 0x03CA
#define GREEK_LOWER_IOTA_DIALYTIKA_TONOS 0x0390
#define GREEK_LOWER_IOTA_DIALYTIKA_OXIA 0x1FD3
#define GREEK_LOWER_OMICRON 0x03BF
#define GREEK_LOWER_OMICRON_TONOS 0x03CC
#define GREEK_LOWER_OMICRON_OXIA 0x1F79
#define GREEK_LOWER_UPSILON 0x03C5
#define GREEK_LOWER_UPSILON_TONOS 0x03CD
#define GREEK_LOWER_UPSILON_OXIA 0x1F7B
#define GREEK_LOWER_UPSILON_DIALYTIKA 0x03CB
#define GREEK_LOWER_UPSILON_DIALYTIKA_TONOS 0x03B0
#define GREEK_LOWER_UPSILON_DIALYTIKA_OXIA 0x1FE3
#define GREEK_LOWER_OMEGA 0x03C9
#define GREEK_LOWER_OMEGA_TONOS 0x03CE
#define GREEK_LOWER_OMEGA_OXIA 0x1F7D
#define GREEK_UPPER_ALPHA 0x0391
#define GREEK_UPPER_EPSILON 0x0395
#define GREEK_UPPER_ETA 0x0397
#define GREEK_UPPER_IOTA 0x0399
#define GREEK_UPPER_IOTA_DIALYTIKA 0x03AA
#define GREEK_UPPER_OMICRON 0x039F
#define GREEK_UPPER_UPSILON 0x03A5
#define GREEK_UPPER_UPSILON_DIALYTIKA 0x03AB
#define GREEK_UPPER_OMEGA 0x03A9
#define GREEK_UPPER_ALPHA_TONOS 0x0386
#define GREEK_UPPER_ALPHA_OXIA 0x1FBB
#define GREEK_UPPER_EPSILON_TONOS 0x0388
#define GREEK_UPPER_EPSILON_OXIA 0x1FC9
#define GREEK_UPPER_ETA_TONOS 0x0389
#define GREEK_UPPER_ETA_OXIA 0x1FCB
#define GREEK_UPPER_IOTA_TONOS 0x038A
#define GREEK_UPPER_IOTA_OXIA 0x1FDB
#define GREEK_UPPER_OMICRON_TONOS 0x038C
#define GREEK_UPPER_OMICRON_OXIA 0x1FF9
#define GREEK_UPPER_UPSILON_TONOS 0x038E
#define GREEK_UPPER_UPSILON_OXIA 0x1FEB
#define GREEK_UPPER_OMEGA_TONOS 0x038F
#define GREEK_UPPER_OMEGA_OXIA 0x1FFB
#define COMBINING_ACUTE_ACCENT 0x0301
#define COMBINING_DIAERESIS 0x0308
#define COMBINING_ACUTE_TONE_MARK 0x0341
#define COMBINING_GREEK_DIALYTIKA_TONOS 0x0344

namespace mozilla {

uint32_t GreekCasing::UpperCase(uint32_t aCh, GreekCasing::State& aState,
                                bool& aMarkEtaPos, bool& aUpdateMarkedEta) {
  aMarkEtaPos = false;
  aUpdateMarkedEta = false;

  uint8_t category = unicode::GetGeneralCategory(aCh);

  if (aState == kEtaAccMarked) {
    switch (category) {
      case HB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER:
      case HB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER:
      case HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER:
      case HB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER:
      case HB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER:
      case HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK:
      case HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK:
      case HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK:
        aUpdateMarkedEta = true;
        break;
      default:
        break;
    }
    aState = kEtaAcc;
  }

  switch (aCh) {
    case GREEK_UPPER_ALPHA:
    case GREEK_LOWER_ALPHA:
      aState = kAlpha;
      return GREEK_UPPER_ALPHA;

    case GREEK_UPPER_EPSILON:
    case GREEK_LOWER_EPSILON:
      aState = kEpsilon;
      return GREEK_UPPER_EPSILON;

    case GREEK_UPPER_ETA:
    case GREEK_LOWER_ETA:
      aState = kEta;
      return GREEK_UPPER_ETA;

    case GREEK_UPPER_IOTA:
      aState = kIota;
      return GREEK_UPPER_IOTA;

    case GREEK_UPPER_OMICRON:
    case GREEK_LOWER_OMICRON:
      aState = kOmicron;
      return GREEK_UPPER_OMICRON;

    case GREEK_UPPER_UPSILON:
      switch (aState) {
        case kOmicron:
          aState = kOmicronUpsilon;
          break;
        default:
          aState = kUpsilon;
          break;
      }
      return GREEK_UPPER_UPSILON;

    case GREEK_UPPER_OMEGA:
    case GREEK_LOWER_OMEGA:
      aState = kOmega;
      return GREEK_UPPER_OMEGA;

    // iota and upsilon may be the second vowel of a diphthong
    case GREEK_LOWER_IOTA:
      switch (aState) {
        case kAlphaAcc:
        case kEpsilonAcc:
        case kOmicronAcc:
        case kUpsilonAcc:
          aState = kInWord;
          return GREEK_UPPER_IOTA_DIALYTIKA;
        default:
          break;
      }
      aState = kIota;
      return GREEK_UPPER_IOTA;

    case GREEK_LOWER_UPSILON:
      switch (aState) {
        case kAlphaAcc:
        case kEpsilonAcc:
        case kEtaAcc:
        case kOmicronAcc:
          aState = kInWord;
          return GREEK_UPPER_UPSILON_DIALYTIKA;
        case kOmicron:
          aState = kOmicronUpsilon;
          break;
        default:
          aState = kUpsilon;
          break;
      }
      return GREEK_UPPER_UPSILON;

    case GREEK_UPPER_IOTA_DIALYTIKA:
    case GREEK_LOWER_IOTA_DIALYTIKA:
    case GREEK_UPPER_UPSILON_DIALYTIKA:
    case GREEK_LOWER_UPSILON_DIALYTIKA:
    case COMBINING_DIAERESIS:
      aState = kDiaeresis;
      return ToUpperCase(aCh);

    // remove accent if it follows a vowel or diaeresis,
    // and set appropriate state for diphthong detection
    case COMBINING_ACUTE_ACCENT:
    case COMBINING_ACUTE_TONE_MARK:
      switch (aState) {
        case kAlpha:
          aState = kAlphaAcc;
          return uint32_t(-1);  // omit this char from result string
        case kEpsilon:
          aState = kEpsilonAcc;
          return uint32_t(-1);
        case kEta:
          aState = kEtaAcc;
          return uint32_t(-1);
        case kIota:
          aState = kIotaAcc;
          return uint32_t(-1);
        case kOmicron:
          aState = kOmicronAcc;
          return uint32_t(-1);
        case kUpsilon:
          aState = kUpsilonAcc;
          return uint32_t(-1);
        case kOmicronUpsilon:
          aState = kInWord;  // this completed a diphthong
          return uint32_t(-1);
        case kOmega:
          aState = kOmegaAcc;
          return uint32_t(-1);
        case kDiaeresis:
          aState = kInWord;
          return uint32_t(-1);
        default:
          break;
      }
      break;

    // combinations with dieresis+accent just strip the accent,
    // and reset to start state (don't form diphthong with following vowel)
    case GREEK_LOWER_IOTA_DIALYTIKA_TONOS:
    case GREEK_LOWER_IOTA_DIALYTIKA_OXIA:
      aState = kInWord;
      return GREEK_UPPER_IOTA_DIALYTIKA;

    case GREEK_LOWER_UPSILON_DIALYTIKA_TONOS:
    case GREEK_LOWER_UPSILON_DIALYTIKA_OXIA:
      aState = kInWord;
      return GREEK_UPPER_UPSILON_DIALYTIKA;

    case COMBINING_GREEK_DIALYTIKA_TONOS:
      aState = kInWord;
      return COMBINING_DIAERESIS;

    // strip accents from vowels, and note the vowel seen so that we can detect
    // diphthongs where diaeresis needs to be added
    case GREEK_LOWER_ALPHA_TONOS:
    case GREEK_LOWER_ALPHA_OXIA:
    case GREEK_UPPER_ALPHA_TONOS:
    case GREEK_UPPER_ALPHA_OXIA:
      aState = kAlphaAcc;
      return GREEK_UPPER_ALPHA;

    case GREEK_LOWER_EPSILON_TONOS:
    case GREEK_LOWER_EPSILON_OXIA:
    case GREEK_UPPER_EPSILON_TONOS:
    case GREEK_UPPER_EPSILON_OXIA:
      aState = kEpsilonAcc;
      return GREEK_UPPER_EPSILON;

    case GREEK_LOWER_ETA_TONOS:
    case GREEK_UPPER_ETA_TONOS:
      if (aState == kStart) {
        aState = kEtaAccMarked;
        aMarkEtaPos = true;  // mark in case we need to remove the tonos later
        return GREEK_UPPER_ETA_TONOS;  // treat as disjunctive eta for now
      }
      // if not in initial state, fall through to strip the accent
      [[fallthrough]];

    case GREEK_LOWER_ETA_OXIA:
    case GREEK_UPPER_ETA_OXIA:
      aState = kEtaAcc;
      return GREEK_UPPER_ETA;

    case GREEK_LOWER_IOTA_TONOS:
    case GREEK_LOWER_IOTA_OXIA:
    case GREEK_UPPER_IOTA_TONOS:
    case GREEK_UPPER_IOTA_OXIA:
      aState = kIotaAcc;
      return GREEK_UPPER_IOTA;

    case GREEK_LOWER_OMICRON_TONOS:
    case GREEK_LOWER_OMICRON_OXIA:
    case GREEK_UPPER_OMICRON_TONOS:
    case GREEK_UPPER_OMICRON_OXIA:
      aState = kOmicronAcc;
      return GREEK_UPPER_OMICRON;

    case GREEK_LOWER_UPSILON_TONOS:
    case GREEK_LOWER_UPSILON_OXIA:
    case GREEK_UPPER_UPSILON_TONOS:
    case GREEK_UPPER_UPSILON_OXIA:
      switch (aState) {
        case kOmicron:
          aState = kInWord;  // this completed a diphthong
          break;
        default:
          aState = kUpsilonAcc;
          break;
      }
      return GREEK_UPPER_UPSILON;

    case GREEK_LOWER_OMEGA_TONOS:
    case GREEK_LOWER_OMEGA_OXIA:
    case GREEK_UPPER_OMEGA_TONOS:
    case GREEK_UPPER_OMEGA_OXIA:
      aState = kOmegaAcc;
      return GREEK_UPPER_OMEGA;
  }

  // all other characters just reset the state to either kStart or kInWord,
  // and use standard mappings
  switch (category) {
    case HB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER:
    case HB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER:
    case HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER:
    case HB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER:
    case HB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER:
    case HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK:
    case HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK:
    case HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK:
      aState = kInWord;
      break;
    default:
      aState = kStart;
      break;
  }

  return ToUpperCase(aCh);
}

}  // namespace mozilla
