/* Copyright (c) 2010-2017 Takeru Ohta Copyright (c) 2011-2018 Akinori MUSHA
 * Licensed under the MIT license.
 *
 */
/*
*The MIT License
*
*Copyright (c) 2010 Takeru Ohta <phjgt308@gmail.com>
*Copyright (c) 2011-2018 Akinori MUSHA <knu@idaemons.org> (extended Ruby support)
*
*Permission is hereby granted, free of charge, to any person obtaining a copy
*of this software and associated documentation files (the "Software"), to deal
*in the Software without restriction, including without limitation the rights
*to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
*copies of the Software, and to permit persons to whom the Software is
*furnished to do so, subject to the following conditions:
*
*The above copyright notice and this permission notice shall be included in
*all copies or substantial portions of the Software.
*
*THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
*IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
*AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
*LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
*OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
*THE SOFTWARE.
 */

#ifndef UNF_NORMALIZER_HH
#define UNF_NORMALIZER_HH

#include <vector>
#include <string>
#include <algorithm>
#include <cstring>
#include "searcher.hh"
#include "char_stream.hh"
#include "table.hh"
#include "util.hh"

namespace UNF {
  class Normalizer {
  public:
    enum Form { FORM_NFD, FORM_NFC, FORM_NFKD, FORM_NFKC };

  public:
    Normalizer()
      : nf_d(TABLE::NODES, TABLE::CANONICAL_DECOM_ROOT, (const char *)TABLE::STRINGS),
	nf_kd(TABLE::NODES, TABLE::COMPATIBILITY_DECOM_ROOT, (const char *)TABLE::STRINGS),
	nf_c(TABLE::NODES, TABLE::CANONICAL_COM_ROOT, (const char *)TABLE::STRINGS),
	nf_c_qc(TABLE::NODES, TABLE::NFC_ILLEGAL_ROOT),
	nf_kc_qc(TABLE::NODES, TABLE::NFKC_ILLEGAL_ROOT),
	ccc(TABLE::NODES, TABLE::CANONICAL_CLASS_ROOT)
    {}

    const char* normalize(const char* src, Form form) {
      switch(form) {
      case FORM_NFD:  return nfd(src);
      case FORM_NFC:  return nfc(src);
      case FORM_NFKD: return nfkd(src);
      case FORM_NFKC: return nfkc(src);
      default:        return src;
      }
    }
    const char* nfd(const char* src)  { return decompose(src, nf_d); }
    const char* nfkd(const char* src) { return decompose(src, nf_kd); }
    const char* nfc(const char* src)  { return compose(src, nf_c_qc, nf_d); }
    const char* nfkc(const char* src) { return compose(src, nf_kc_qc, nf_kd); }

  private:
    const char* decompose(const char* src, const Trie::NormalizationForm& nf) {
      const char* beg = next_invalid_char(src, nf);
      if(*beg=='\0')
	return src;
      
      buffer.assign(src, beg);
      do {
	const char* end = next_valid_starter(beg, nf);
	decompose_one(beg, end, nf, buffer);
	beg = next_invalid_char(end, nf);
	buffer.append(end, beg);
      } while(*beg!='\0');
      
      return buffer.c_str();      
    }

    void decompose_one(const char* beg, const char* end, const Trie::NormalizationForm& nf, std::string& buf) {
      unsigned last = buf.size();
      nf.decompose(Trie::RangeCharStream(beg,end), buf);
      char* bufbeg = const_cast<char*>(buf.data());
      canonical_combining_class_ordering(bufbeg+last, bufbeg+buf.size());
    }

    const char* compose(const char* src, const Trie::NormalizationForm& nf, const Trie::NormalizationForm& nf_decomp) {
      const char* beg = next_invalid_char(src, nf);
      if(*beg=='\0')
	return src;
      
      buffer.assign(src, beg);
      while(*beg!='\0') {
	const char* end = next_valid_starter(beg, nf);
	buffer2.clear();
	decompose_one(beg, end, nf_decomp, buffer2);
	end = compose_one(buffer2.c_str(), end, buffer);
	beg = next_invalid_char(end, nf);
	buffer.append(end, beg);
      }

      return buffer.c_str();      
    }

    const char* compose_one(const char* starter, const char* rest_starter, std::string& buf) {
      Trie::CharStreamForComposition in(starter, rest_starter, canonical_classes, buffer3);
      while(in.within_first())
	nf_c.compose(in, buf);
      return in.cur();
    }

    void canonical_combining_class_ordering(char* beg, const char* end) {
      canonical_classes.assign(end-beg+1, 0); // +1 is for sentinel value
      ccc.sort(beg, canonical_classes);
    }

    const char* next_invalid_char(const char* src, const Trie::NormalizationForm& nf) const {
      int last_canonical_class = 0;
      const char* cur = Util::nearest_utf8_char_start_point(src);
      const char* starter = cur;
      
      for(; *cur != '\0'; cur = Util::nearest_utf8_char_start_point(cur+1)) {
	int canonical_class = ccc.get_class(cur);
	if(last_canonical_class > canonical_class && canonical_class != 0)
	  return starter;

	if(nf.quick_check(cur)==false)
	  return starter;

	if(canonical_class==0)
	  starter=cur;

	last_canonical_class = canonical_class;
      }
      return cur;
    }

    const char* next_valid_starter(const char* src, const Trie::NormalizationForm& nf) const {
      const char* cur = Util::nearest_utf8_char_start_point(src+1);
      while(ccc.get_class(cur)!=0 || nf.quick_check(cur)==false)
	cur = Util::nearest_utf8_char_start_point(cur+1);
      return cur;
    }

  private:
    const Trie::NormalizationForm nf_d;
    const Trie::NormalizationForm nf_kd;
    const Trie::NormalizationForm nf_c;
    const Trie::NormalizationForm nf_c_qc;
    const Trie::NormalizationForm nf_kc_qc;
    const Trie::CanonicalCombiningClass ccc;
    
    std::string buffer;
    std::string buffer2;
    std::string buffer3;
    std::vector<unsigned char> canonical_classes;
  };
}

#endif
