text: // Copyright (c) 2009 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <stdio.h>
  #include <string.h>
  //#include <sys/time.h>                               // for gettimeofday
  #include <string>
  
  #include "encodings/lang_enc.h"
  
  #include "encodings/compact_lang_det/compact_lang_det.h"
  #include "encodings/compact_lang_det/compact_lang_det_impl.h"
  #include "encodings/compact_lang_det/getonescriptspan.h"
  #include "encodings/compact_lang_det/letterscript_enum.h"
  #include "encodings/compact_lang_det/tote.h"
  #include "encodings/compact_lang_det/utf8propjustletter.h"
  #include "encodings/compact_lang_det/utf8propletterscriptnum.h"
  #include "encodings/compact_lang_det/utf8scannotjustletterspecial.h"
  
  #include "encodings/compact_lang_det/cldutil_dbg.h"
  
  #include "encodings/compact_lang_det/win/cld_basictypes.h"
  #include "encodings/compact_lang_det/win/cld_commandlineflags.h"
  #include "encodings/compact_lang_det/win/cld_google.h"
  #include "encodings/compact_lang_det/win/cld_utf8statetable.h"
  
  // Linker supplies the right tables
  extern const UTF8PropObj compact_lang_det_generated_ctjkvz_b1_obj;
  extern const cld::CLDTableSummary kCjkBiTable_obj;
  extern const cld::CLDTableSummary kQuadTable_obj;
  extern const cld::CLDTableSummary kLongWord8Table_obj;
  
  DEFINE_bool(cld_html, false, "Print language spans in HTML on stderr");
  DEFINE_bool(cld_forcewords, false, "Score all words, in addition to quads");
  
  DEFINE_bool(cld_showme, false, "Put squeeze/repeat points into HTML text");
  DEFINE_bool(cld_echotext, false, "Print each scriptspan to stderr");
  DEFINE_int32(cld_textlimit, 160, "Examine only initial n KB of actual text");
  // 20 quadgrams is about 80 bytes or about 12 words in real text
  DEFINE_int32(cld_smoothwidth, 20, "Smoothing window width in quadgrams");
  
  
  static const int kLangHintInitial = 12;  // Boost language by N initially
  static const int kLangHintBoost = 12;    // Boost language by N/16 per quadgram
  
  static const int kShortSpanThresh = 32;       // Bytes
  static const int kMaxSecondChanceLen = 1024;  // Look at first 1K of short spans
  
  static const int kCheapSqueezeTestThresh = 4096;  // Only look for squeezing
                                                    // after this many text bytes
  static const int kCheapSqueezeTestLen = 256;   // Bytes to test to trigger sqz
  static const int kSpacesTriggerPercent = 25;   // Trigger sqz if >=25% spaces
  static const int kPredictTriggerPercent = 67;  // Trigger sqz if >=67% predicted
  
  static const int kChunksizeDefault = 48;      // Squeeze 48-byte chunks
  static const int kSpacesThreshPercent = 25;   // Squeeze if >=25% spaces
  static const int kPredictThreshPercent = 40;  // Squeeze if >=40% predicted
  
  static const int kMaxSpaceScan = 32;          // Bytes
  
  static const int kGoodLang1Percent = 70;
  static const int kGoodLang1and2Percent = 93;
  static const int kShortTextThresh = 256;      // Bytes
  
  static const int kMinChunkSizeQuads = 4;      // Chunk is at least four quads
  static const int kMaxChunkSizeQuads = 1024;   // Chunk is at most 1K quads
  
  static const int kDefaultWordSpan = 256;      // Scan at least this many initial
                                                // bytes with word scoring
  static const int kReallyBigWordSpan = 9999999;  // Forces word scoring all text
  
  static const int kMinReliableSeq = 50;      // Record in seq if >= 50% reliable
  
  static const int kPredictionTableSize = 4096;   // Must be exactly 4096 for
                                                  // cheap compressor
  
  //
  // Generated by dsites 2008.07.07 from 10% of Base
  //
  
  // Three packed language probs, subscripted by Encoding
  static const uint32 kEncodingHintProbs[] = {
    0x00000000,  // ASCII
    0x18120cd5,  // Latin2   POLISH.11 CZECH.5 HUNGARIAN.3
    0x1d3a4bc9,  // Latin3   AZERBAIJANI.10 BASQUE.3 CROATIAN.1
    0x030819d4,  // Latin4   ESTONIAN.11 ITALIAN.4 DUTCH.2
    0x00000000,  // ISO-8859-5
    0x00003742,  // Arabic   ARABIC.12
    0x00000000,  // Greek
    0x00000742,  // Hebrew   HEBREW.12
    0x00002242,  // Latin5   TURKISH.12
    0x060419c9,  // Latin6   ESTONIAN.10 FINNISH.3 GERMAN.1
    0x00000942,  // EUC-JP   Japanese.12
    0x00000942,  // SJS   Japanese.12
    0x00000942,  // JIS   Japanese.12
    0x00004642,  // BIG5   ChineseT.12
    0x00001142,  // GB   Chinese.12
    0x46295fcd,  // EUC-CN   UIGHUR.10 MALAY.6 ChineseT.5
    0x00000a42,  // KSC   Korean.12
    0x00000000,  // Unicode
    0x03104674,  // EUC   ChineseT.9 SWEDISH.8 DUTCH.3
    0x00000000,  // CNS
    0x0f1146c3,  // BIG5-CP950   ChineseT.9 Chinese.5 SPANISH.4
    0x00000942,  // CP932   Japanese.12
    0x00000000,  // UTF8
    0x00000000,  // Unknown
    0x00000000,  // ASCII-7-bit
    0x00000000,  // KOI8R
    0x00000000,  // CP1251
    0x00000000,  // CP1252
    0x00000000,  // KOI8U
    0x451d12cd,  // CP1250   CZECH.10 CROATIAN.6 SLOVAK.5
    0x0d06052a,  // ISO-8859-15   FRENCH.9 GERMAN.8 PORTUGUESE.7
    0x00002242,  // CP1254   TURKISH.12
    0x191516be,  // CP1257   LITHUANIAN.8 LATVIAN.7 ESTONIAN.7
    0x08003642,  // ISO-8859-11   THAI.12 ITALIAN.1
    0x00000000,  // CP874
    0x00003742,  // CP1256   ARABIC.12
    0x00000742,  // CP1255   HEBREW.12
    0x00000000,  // ISO-8859-8-I
    0x00000000,  // VISUAL
    0x00000000,  // CP852
    0x39001242,  // CSN_369103   CZECH.12 ESPERANTO.1
    0x00000000,  // CP1253
    0x00000000,  // CP866
    0x2e001944,  // ISO-8859-13   ESTONIAN.12 ALBANIAN.3
    0x08090a74,  // ISO-2022-KR   Korean.9 Japanese.8 ITALIAN.3
    0x00001142,  // GBK   Chinese.12
    0x4600113d,  // GB18030   Chinese.11 ChineseT.7
    0x00004642,  // BIG5_HKSCS   ChineseT.12
    0x00000000,  // ISO_2022_CN
    0x00000000,  // TSCII
    0x00000000,  // TAM
    0x00000000,  // TAB
    0x00000000,  // JAGRAN
    0x00000000,  // MACINTOSH
    0x00000000,  // UTF7
    0x00000000,  // BHASKAR
    0x00000000,  // HTCHANAKYA
    0x090646ca,  // UTF-16BE   ChineseT.10 GERMAN.4 Japanese.2
    0x00000000,  // UTF-16LE
    0x00000000,  // UTF-32BE
    0x00000000,  // UTF-32LE
    0x00000000,  // X-BINARYENC
    0x06001142,  // HZ-GB-2312   Chinese.12 GERMAN.1
    0x461109c2,  // X-UTF8UTF8   Japanese.9 Chinese.5 ChineseT.3
    0x00000000,  // X-TAM-ELANGO
    0x00000000,  // X-TAM-LTTMBARANI
    0x00000000,  // X-TAM-SHREE
    0x00000000,  // X-TAM-TBOOMIS
    0x00000000,  // X-TAM-TMNEWS
    0x00000000,  // X-TAM-WEBTAMIL
    0x00000000,  // X-KDDI-Shift_JIS
    0x00000000,  // X-DoCoMo-Shift_JIS
    0x00000000,  // X-SoftBank-Shift_JIS
    0x00000000,  // X-KDDI-ISO-2022-JP
    0x00000000,  // X-SoftBank-ISO-2022-JP
  };
  
  COMPILE_ASSERT(arraysize(kEncodingHintProbs) == NUM_ENCODINGS,
                 kEncodingHintProbs_has_incorrect_size);
  
  //
  // Generated by dsites 2008.07.07 from 10% of Base
  //
  
  // Three packed language probs, subscripted by (anchor) language
  static const uint32 kLanguageHintProbs[] = {
    0x00000000,  // ENGLISH
    0x00000242,  // DANISH   DANISH.12
    0x00000342,  // DUTCH   DUTCH.12
    0x00000442,  // FINNISH   FINNISH.12
    0x00000542,  // FRENCH   FRENCH.12
    0x00000642,  // GERMAN   GERMAN.12
    0x00000742,  // HEBREW   HEBREW.12
    0x00000842,  // ITALIAN   ITALIAN.12
    0x00000942,  // Japanese   Japanese.12
    0x00000a42,  // Korean   Korean.12
    0x51000b43,  // NORWEGIAN   NORWEGIAN.12 NORWEGIAN_N.2
    0x00000c42,  // POLISH   POLISH.12
    0x00000d42,  // PORTUGUESE   PORTUGUESE.12
    0x00000000,  // RUSSIAN
    0x00000f42,  // SPANISH   SPANISH.12
    0x00001042,  // SWEDISH   SWEDISH.12
    0x00001142,  // Chinese   Chinese.12
    0x00001242,  // CZECH   CZECH.12
    0x00000000,  // GREEK
    0x47001442,  // ICELANDIC   ICELANDIC.12 FAROESE.1
    0x00001542,  // LATVIAN   LATVIAN.12
    0x00001642,  // LITHUANIAN   LITHUANIAN.12
    0x00001742,  // ROMANIAN   ROMANIAN.12
    0x00001842,  // HUNGARIAN   HUNGARIAN.12
    0x00001942,  // ESTONIAN   ESTONIAN.12
    0x00000000,  // TG_UNKNOWN_LANGUAGE
    0x00000000,  // Unknown
    0x00001c42,  // BULGARIAN   BULGARIAN.12
    0x00001d42,  // CROATIAN   CROATIAN.12
    0x1e001d46,  // SERBIAN   CROATIAN.12 SERBIAN.5
    0x00000000,  // IRISH
    0x0f00203d,  // GALICIAN   GALICIAN.11 SPANISH.7
    0x5e00213a,  // TAGALOG   TAGALOG.11 SOMALI.4
    0x00002242,  // TURKISH   TURKISH.12
    0x00002342,  // UKRAINIAN   UKRAINIAN.12
    0x00000000,  // HINDI
    0x1c1e25d4,  // MACEDONIAN   MACEDONIAN.11 SERBIAN.4 BULGARIAN.2
    0x00002642,  // BENGALI   BENGALI.12
    0x00002742,  // INDONESIAN   INDONESIAN.12
    0x00000000,  // LATIN
    0x2700293c,  // MALAY   MALAY.11 INDONESIAN.6
    0x00000000,  // MALAYALAM
    0x00000000,  // WELSH
    0x00000000,  // NEPALI
    0x00000000,  // TELUGU
    0x00002e42,  // ALBANIAN   ALBANIAN.12
    0x00000000,  // TAMIL
    0x00003042,  // BELARUSIAN   BELARUSIAN.12
    0x00000000,  // JAVANESE
    0x00000000,  // OCCITAN
    0x375f3330,  // URDU   URDU.10 UIGHUR.7 ARABIC.4
    0x41003436,  // BIHARI   BIHARI.10 MARATHI.10
    0x00000000,  // GUJARATI
    0x0a4636b2,  // THAI   THAI.7 ChineseT.3 Korean.2
    0x00003742,  // ARABIC   ARABIC.12
    0x00003842,  // CATALAN   CATALAN.12
    0x00003942,  // ESPERANTO   ESPERANTO.12
    0x00003a42,  // BASQUE   BASQUE.12
    0x00000000,  // INTERLINGUA
    0x00000000,  // KANNADA
    0x05060cca,  // PUNJABI   POLISH.10 GERMAN.4 FRENCH.2
    0x00000000,  // SCOTS_GAELIC
    0x00003f42,  // SWAHILI   SWAHILI.12
    0x00004042,  // SLOVENIAN   SLOVENIAN.12
    0x00004142,  // MARATHI   MARATHI.12
    0x00004242,  // MALTESE   MALTESE.12
    0x00004342,  // VIETNAMESE   VIETNAMESE.12
    0x00000000,  // FRISIAN
    0x12004543,  // SLOVAK   SLOVAK.12 CZECH.2
    0x00004642,  // ChineseT   ChineseT.12
    0x00000000,  // FAROESE
    0x00000000,  // SUNDANESE
    0x79004944,  // UZBEK   UZBEK.12 TAJIK.3
    0x4d004a46,  // AMHARIC   AMHARIC.12 TIGRINYA.5
    0x00004b42,  // AZERBAIJANI   AZERBAIJANI.12
    0x00000000,  // GEORGIAN
    0x00000000,  // TIGRINYA
    0x00004e42,  // PERSIAN   PERSIAN.12
    0x00000000,  // BOSNIAN
    0x00000000,  // SINHALESE
    0x00000000,  // NORWEGIAN_N
    0x00000000,  // PORTUGUESE_P
    0x00000000,  // PORTUGUESE_B
    0x00000000,  // XHOSA
    0x00000000,  // ZULU
    0x00000000,  // GUARANI
    0x00000000,  // SESOTHO
    0x00000000,  // TURKMEN
    0x7a005933,  // KYRGYZ   KYRGYZ.10 TATAR.7
    0x00000000,  // BRETON
    0x00000000,  // TWI
    0x00000000,  // YIDDISH
    0x00000000,  // SERBO_CROATIAN
    0x00000000,  // SOMALI
    0x00005f42,  // UIGHUR   UIGHUR.12
    0x00006042,  // KURDISH   KURDISH.12
    0x00006142,  // MONGOLIAN   MONGOLIAN.12
    0x051130c9,  // ARMENIAN   BELARUSIAN.10 Chinese.3 FRENCH.1
    0x020f0521,  // LAOTHIAN   FRENCH.8 SPANISH.7 DANISH.6
    0x64004e35,  // SINDHI   PERSIAN.10 SINDHI.9
    0x00000000,  // RHAETO_ROMANCE
    0x00006642,  // AFRIKAANS   AFRIKAANS.12
    0x00000000,  // LUXEMBOURGISH
    0x00006842,  // BURMESE   BURMESE.12
    0x00002242,  // KHMER   TURKISH.12
    0x88006a3c,  // TIBETAN   TIBETAN.11 DZONGKHA.6
    0x00000000,  // DHIVEHI
    0x00000000,  // CHEROKEE
    0x00000000,  // SYRIAC
    0x00000000,  // LIMBU
    0x00000000,  // ORIYA
    0x00000000,  // ASSAMESE
    0x00000000,  // CORSICAN
    0x00000000,  // INTERLINGUE
    0x00007342,  // KAZAKH   KAZAKH.12
    0x00000000,  // LINGALA
    0x00000000,  // MOLDAVIAN
    0x5f007645,  // PASHTO   PASHTO.12 UIGHUR.4
    0x00000000,  // QUECHUA
    0x00000000,  // SHONA
    0x00007942,  // TAJIK   TAJIK.12
    0x00000000,  // TATAR
    0x00000000,  // TONGA
    0x00000000,  // YORUBA
    0x00000000,  // CREOLES_AND_PIDGINS_ENGLISH_BASED
    0x00000000,  // CREOLES_AND_PIDGINS_FRENCH_BASED
    0x00000000,  // CREOLES_AND_PIDGINS_PORTUGUESE_BASED
    0x00000000,  // CREOLES_AND_PIDGINS_OTHER
    0x00000000,  // MAORI
    0x00000000,  // WOLOF
    0x00000000,  // ABKHAZIAN
    0x00000000,  // AFAR
    0x00000000,  // AYMARA
    0x00000000,  // BASHKIR
    0x00000000,  // BISLAMA
    0x00000000,  // DZONGKHA
    0x00000000,  // FIJIAN
    0x00000000,  // GREENLANDIC
    0x00000000,  // HAUSA
    0x00000000,  // HAITIAN_CREOLE
    0x00000000,  // INUPIAK
    0x00000542,  // INUKTITUT   FRENCH.12
    0x00000000,  // KASHMIRI
    0x00000000,  // KINYARWANDA
    0x00000000,  // MALAGASY
    0x00000000,  // NAURU
    0x00000000,  // OROMO
    0x00000000,  // RUNDI
    0x00000000,  // SAMOAN
    0x00000000,  // SANGO
    0x344197d3,  // SANSKRIT   SANSKRIT.11 MARATHI.4 BIHARI.1
    0x00000000,  // SISWANT
    0x00000000,  // TSONGA
    0x00000000,  // TSWANA
    0x00000000,  // VOLAPUK
    0x00000000,  // ZHUANG
    0x00000000,  // KHASI
    0x00000000,  // SCOTS
    0x00000000,  // GANDA
    0x00000000,  // MANX
    0x00000000,  // MONTENEGRIN
    // Add new language hints just before here (just use 0x00000000)
  };
  
  COMPILE_ASSERT(arraysize(kLanguageHintProbs) == NUM_LANGUAGES,
                 kLanguageHintProbs_has_incorrect_size);
  
  //
  // Generated by dsites 2008.07.07 from 10% of Base
  //
  
  typedef struct {
    char key[4];
    uint32 probs;
  } HintEntry;
  
  
  // Massaged TLD, followed by three packed language probs
  // Hand-removed 4 items dsites 2008.07.15
  static const int kTLDHintProbsSize = 201;
  static const HintEntry kTLDHintProbs[kTLDHintProbsSize] = {   // MaxRange 12
    {{0x61,0x63,0x5f,0x5f}, 0x0a000945},   // ac__ Japanese.12 Korean.4
    {{0x61,0x64,0x5f,0x5f}, 0x00003842},   // ad__ CATALAN.12
    {{0x61,0x65,0x5f,0x5f}, 0x00003742},   // ae__ ARABIC.12
    {{0x61,0x66,0x5f,0x5f}, 0x4e00763d},   // af__ PASHTO.11 PERSIAN.7
    {{0x61,0x67,0x5f,0x5f}, 0x09000643},   // ag__ GERMAN.12 Japanese.2
    {{0x61,0x69,0x5f,0x5f}, 0x0c180938},   // ai__ Japanese.11 HUNGARIAN.7 POLISH.2
    {{0x61,0x6c,0x5f,0x5f}, 0x00002e42},   // al__ ALBANIAN.12
    {{0x61,0x6e,0x5f,0x5f}, 0x6e00033d},   // an__ DUTCH.11 LIMBU.7
    {{0x61,0x6f,0x5f,0x5f}, 0x05000d42},   // ao__ PORTUGUESE.12 FRENCH.1
    {{0x61,0x71,0x5f,0x5f}, 0x05000f29},   // aq__ SPANISH.9 FRENCH.6
    {{0x61,0x72,0x5f,0x5f}, 0x00000f42},   // ar__ SPANISH.12
    {{0x61,0x73,0x5f,0x5f}, 0x0f120bcd},   // as__ NORWEGIAN.10 CZECH.6 SPANISH.5
    {{0x61,0x74,0x5f,0x5f}, 0x00000642},   // at__ GERMAN.12
    {{0x61,0x77,0x5f,0x5f}, 0x0f000345},   // aw__ DUTCH.12 SPANISH.4
    {{0x61,0x78,0x5f,0x5f}, 0x00001042},   // ax__ SWEDISH.12
    {{0x61,0x7a,0x5f,0x5f}, 0x00004b42},   // az__ AZERBAIJANI.12
    {{0x62,0x61,0x5f,0x5f}, 0x00001d42},   // ba__ CROATIAN.12
    {{0x62,0x62,0x5f,0x5f}, 0x00002842},   // bb__ LATIN.12
    {{0x62,0x64,0x5f,0x5f}, 0x00002642},   // bd__ BENGALI.12
    {{0x62,0x65,0x5f,0x5f}, 0x05000335},   // be__ DUTCH.10 FRENCH.9
    {{0x62,0x66,0x5f,0x5f}, 0x00000542},   // bf__ FRENCH.12
    {{0x62,0x67,0x5f,0x5f}, 0x00001c42},   // bg__ BULGARIAN.12
    {{0x62,0x68,0x5f,0x5f}, 0x00003742},   // bh__ ARABIC.12
    {{0x62,0x69,0x5f,0x5f}, 0x0f00053f},   // bi__ FRENCH.11 SPANISH.9
    {{0x62,0x6a,0x5f,0x5f}, 0x00000542},   // bj__ FRENCH.12
    {{0x62,0x6d,0x5f,0x5f}, 0x98043929},   // bm__ ESPERANTO.9 FINNISH.8 SISWANT.6
    {{0x62,0x6e,0x5f,0x5f}, 0x00002942},   // bn__ MALAY.12
    {{0x62,0x6f,0x5f,0x5f}, 0x00000f42},   // bo__ SPANISH.12
    {{0x62,0x72,0x5f,0x5f}, 0x00000d42},   // br__ PORTUGUESE.12
    {{0x62,0x74,0x5f,0x5f}, 0x00008842},   // bt__ DZONGKHA.12
    {{0x62,0x77,0x5f,0x5f}, 0x06059ac4},   // bw__ TSWANA.9 FRENCH.6 GERMAN.5
    {{0x62,0x79,0x5f,0x5f}, 0x00003024},   // by__ BELARUSIAN.9
    {{0x62,0x7a,0x5f,0x5f}, 0x0f0a0924},   // bz__ Japanese.9 Korean.5 SPANISH.1
    {{0x63,0x61,0x5f,0x5f}, 0x00000542},   // ca__ FRENCH.12
    {{0x63,0x61,0x74,0x5f}, 0x00003842},   // cat_ CATALAN.12
    {{0x63,0x64,0x5f,0x5f}, 0x06051224},   // cd__ CZECH.9 FRENCH.5 GERMAN.1
    {{0x63,0x66,0x5f,0x5f}, 0x00000542},   // cf__ FRENCH.12
    {{0x63,0x67,0x5f,0x5f}, 0x00000542},   // cg__ FRENCH.12
    {{0x63,0x68,0x5f,0x5f}, 0x08050638},   // ch__ GERMAN.11 FRENCH.7 ITALIAN.2
    {{0x63,0x69,0x5f,0x5f}, 0x00000542},   // ci__ FRENCH.12
    {{0x63,0x6c,0x5f,0x5f}, 0x00000f42},   // cl__ SPANISH.12
    {{0x63,0x6d,0x5f,0x5f}, 0x00000542},   // cm__ FRENCH.12
    {{0x63,0x6e,0x5f,0x5f}, 0x00001142},   // cn__ Chinese.12
    {{0x63,0x6f,0x5f,0x5f}, 0x00000f42},   // co__ SPANISH.12
  // {{0x63,0x6f,0x6f,0x70}, 0x0f0509cd},   // coop Japanese.10 FRENCH.6 SPANISH.5
    {{0x63,0x72,0x5f,0x5f}, 0x00000f42},   // cr__ SPANISH.12
    {{0x63,0x75,0x5f,0x5f}, 0x00000f42},   // cu__ SPANISH.12
    {{0x63,0x76,0x5f,0x5f}, 0x00000d42},   // cv__ PORTUGUESE.12
    {{0x63,0x78,0x5f,0x5f}, 0x223a091f},   // cx__ Japanese.8 BASQUE.6 TURKISH.4
    {{0x63,0x79,0x5f,0x5f}, 0x150622ba},   // cy__ TURKISH.8 GERMAN.4 LATVIAN.3
    {{0x63,0x7a,0x5f,0x5f}, 0x00001242},   // cz__ CZECH.12
    {{0x64,0x65,0x5f,0x5f}, 0x00000642},   // de__ GERMAN.12
    {{0x64,0x6b,0x5f,0x5f}, 0x00000242},   // dk__ DANISH.12
    {{0x64,0x6f,0x5f,0x5f}, 0x21000f42},   // do__ SPANISH.12 TAGALOG.1
    {{0x64,0x7a,0x5f,0x5f}, 0x37000535},   // dz__ FRENCH.10 ARABIC.9
    {{0x65,0x63,0x5f,0x5f}, 0x00000f42},   // ec__ SPANISH.12
  // {{0x65,0x64,0x75,0x5f}, 0x2e0f3873},   // edu_ CATALAN.9 SPANISH.7 ALBANIAN.2
    {{0x65,0x65,0x5f,0x5f}, 0x00001942},   // ee__ ESTONIAN.12
    {{0x65,0x67,0x5f,0x5f}, 0x05003742},   // eg__ ARABIC.12 FRENCH.1
    {{0x65,0x72,0x5f,0x5f}, 0x00000b42},   // er__ NORWEGIAN.12
    {{0x65,0x73,0x5f,0x5f}, 0x38200fd4},   // es__ SPANISH.11 GALICIAN.4 CATALAN.2
    {{0x65,0x74,0x5f,0x5f}, 0x39004a39},   // et__ AMHARIC.11 ESPERANTO.3
    {{0x66,0x69,0x5f,0x5f}, 0x10000444},   // fi__ FINNISH.12 SWEDISH.3
    {{0x66,0x6a,0x5f,0x5f}, 0x050489e0},   // fj__ FIJIAN.12 FINNISH.5 FRENCH.3
    {{0x66,0x6f,0x5f,0x5f}, 0x00004742},   // fo__ FAROESE.12
    {{0x66,0x72,0x5f,0x5f}, 0x00000542},   // fr__ FRENCH.12
    {{0x67,0x61,0x5f,0x5f}, 0x00000542},   // ga__ FRENCH.12
    {{0x67,0x64,0x5f,0x5f}, 0x061d05d5},   // gd__ FRENCH.11 CROATIAN.5 GERMAN.3
    {{0x67,0x65,0x5f,0x5f}, 0x00004c2d},   // ge__ GEORGIAN.10
    {{0x67,0x66,0x5f,0x5f}, 0x00000542},   // gf__ FRENCH.12
    {{0x67,0x67,0x5f,0x5f}, 0x06002244},   // gg__ TURKISH.12 GERMAN.3
    {{0x67,0x68,0x5f,0x5f}, 0x05000436},   // gh__ FINNISH.10 FRENCH.10
    {{0x67,0x69,0x5f,0x5f}, 0x0f0538ce},   // gi__ CATALAN.10 FRENCH.7 SPANISH.6
    {{0x67,0x6c,0x5f,0x5f}, 0x398a0238},   // gl__ DANISH.11 GREENLANDIC.7 ESPERANTO.2
    {{0x67,0x6d,0x5f,0x5f}, 0x0600043e},   // gm__ FINNISH.11 GERMAN.8
    {{0x67,0x6e,0x5f,0x5f}, 0x00000542},   // gn__ FRENCH.12
  // {{0x67,0x6f,0x76,0x5f}, 0x05000f25},   // gov_ SPANISH.9 FRENCH.2
    {{0x67,0x70,0x5f,0x5f}, 0x00000542},   // gp__ FRENCH.12
    {{0x67,0x71,0x5f,0x5f}, 0x0f000547},   // gq__ FRENCH.12 SPANISH.6
    {{0x67,0x73,0x5f,0x5f}, 0x00000942},   // gs__ Japanese.12
    {{0x67,0x74,0x5f,0x5f}, 0x00000f42},   // gt__ SPANISH.12
    {{0x68,0x6b,0x5f,0x5f}, 0x11004643},   // hk__ ChineseT.12 Chinese.2
    {{0x68,0x6d,0x5f,0x5f}, 0x4606092e},   // hm__ Japanese.10 GERMAN.6 ChineseT.2
    {{0x68,0x6e,0x5f,0x5f}, 0x00000f42},   // hn__ SPANISH.12
    {{0x68,0x72,0x5f,0x5f}, 0x00001d42},   // hr__ CROATIAN.12
    {{0x68,0x74,0x5f,0x5f}, 0x0f000542},   // ht__ FRENCH.12 SPANISH.1
    {{0x68,0x75,0x5f,0x5f}, 0x00001842},   // hu__ HUNGARIAN.12
    {{0x69,0x64,0x5f,0x5f}, 0x00002742},   // id__ INDONESIAN.12
    {{0x69,0x65,0x5f,0x5f}, 0x050c1f24},   // ie__ IRISH.9 POLISH.5 FRENCH.1
    {{0x69,0x6c,0x5f,0x5f}, 0x00000742},   // il__ HEBREW.12
    {{0x69,0x6e,0x74,0x5f}, 0x0f060574},   // int_ FRENCH.9 GERMAN.8 SPANISH.3
    {{0x69,0x6f,0x5f,0x5f}, 0x11090fd5},   // io__ SPANISH.11 Japanese.5 Chinese.3
    {{0x69,0x71,0x5f,0x5f}, 0x60003744},   // iq__ ARABIC.12 KURDISH.3
    {{0x69,0x72,0x5f,0x5f}, 0x00004e42},   // ir__ PERSIAN.12
    {{0x69,0x73,0x5f,0x5f}, 0x00001442},   // is__ ICELANDIC.12
    {{0x69,0x74,0x5f,0x5f}, 0x00000842},   // it__ ITALIAN.12
    {{0x6a,0x65,0x5f,0x5f}, 0x29050328},   // je__ DUTCH.9 FRENCH.7 MALAY.5
    {{0x6a,0x6d,0x5f,0x5f}, 0x040f0576},   // jm__ FRENCH.9 SPANISH.8 FINNISH.5
    {{0x6a,0x6f,0x5f,0x5f}, 0x00003742},   // jo__ ARABIC.12
  // {{0x6a,0x6f,0x62,0x73}, 0x0f060329},   // jobs DUTCH.9 GERMAN.8 SPANISH.6
    {{0x6a,0x70,0x5f,0x5f}, 0x00000942},   // jp__ Japanese.12
    {{0x6b,0x65,0x5f,0x5f}, 0x040f3fc3},   // ke__ SWAHILI.9 SPANISH.5 FINNISH.4
    {{0x6b,0x69,0x5f,0x5f}, 0x04000643},   // ki__ GERMAN.12 FINNISH.2
    {{0x6b,0x6d,0x5f,0x5f}, 0x00000542},   // km__ FRENCH.12
    {{0x6b,0x70,0x5f,0x5f}, 0x00000a42},   // kp__ Korean.12
    {{0x6b,0x72,0x5f,0x5f}, 0x00000a42},   // kr__ Korean.12
    {{0x6b,0x77,0x5f,0x5f}, 0x00003742},   // kw__ ARABIC.12
    {{0x6b,0x79,0x5f,0x5f}, 0x0500083f},   // ky__ ITALIAN.11 FRENCH.9
    {{0x6b,0x7a,0x5f,0x5f}, 0x0000732d},   // kz__ KAZAKH.10
    {{0x6c,0x62,0x5f,0x5f}, 0x05003747},   // lb__ ARABIC.12 FRENCH.6
    {{0x6c,0x63,0x5f,0x5f}, 0x09000645},   // lc__ GERMAN.12 Japanese.4
    {{0x6c,0x69,0x5f,0x5f}, 0x1600063d},   // li__ GERMAN.11 LITHUANIAN.7
    {{0x6c,0x73,0x5f,0x5f}, 0x00005742},   // ls__ SESOTHO.12
    {{0x6c,0x74,0x5f,0x5f}, 0x00001642},   // lt__ LITHUANIAN.12
    {{0x6c,0x75,0x5f,0x5f}, 0x0600053d},   // lu__ FRENCH.11 GERMAN.7
    {{0x6c,0x76,0x5f,0x5f}, 0x00001542},   // lv__ LATVIAN.12
    {{0x6c,0x79,0x5f,0x5f}, 0x05003744},   // ly__ ARABIC.12 FRENCH.3
    {{0x6d,0x61,0x5f,0x5f}, 0x3700053d},   // ma__ FRENCH.11 ARABIC.7
    {{0x6d,0x63,0x5f,0x5f}, 0x00000542},   // mc__ FRENCH.12
    {{0x6d,0x64,0x5f,0x5f}, 0x00001724},   // md__ ROMANIAN.9
    {{0x6d,0x65,0x5f,0x5f}, 0x00001d42},   // me__ CROATIAN.12
    {{0x6d,0x67,0x5f,0x5f}, 0x00000542},   // mg__ FRENCH.12
    {{0x6d,0x6b,0x5f,0x5f}, 0x1c002543},   // mk__ MACEDONIAN.12 BULGARIAN.2
    {{0x6d,0x6c,0x5f,0x5f}, 0x00000542},   // ml__ FRENCH.12
    {{0x6d,0x6e,0x5f,0x5f}, 0x00006142},   // mn__ MONGOLIAN.12
    {{0x6d,0x6f,0x5f,0x5f}, 0x110d4631},   // mo__ ChineseT.10 PORTUGUESE.8 Chinese.5
    {{0x6d,0x71,0x5f,0x5f}, 0x00000542},   // mq__ FRENCH.12
    {{0x6d,0x72,0x5f,0x5f}, 0x37000535},   // mr__ FRENCH.10 ARABIC.9
    {{0x6d,0x73,0x5f,0x5f}, 0x090f06d5},   // ms__ GERMAN.11 SPANISH.5 Japanese.3
    {{0x6d,0x74,0x5f,0x5f}, 0x00004242},   // mt__ MALTESE.12
    {{0x6d,0x75,0x5f,0x5f}, 0x05000934},   // mu__ Japanese.10 FRENCH.8
    {{0x6d,0x76,0x5f,0x5f}, 0x28000436},   // mv__ FINNISH.10 LATIN.10
    {{0x6d,0x77,0x5f,0x5f}, 0x0611092a},   // mw__ Japanese.9 Chinese.8 GERMAN.7
    {{0x6d,0x78,0x5f,0x5f}, 0x00000f42},   // mx__ SPANISH.12
    {{0x6d,0x79,0x5f,0x5f}, 0x00002942},   // my__ MALAY.12
    {{0x6d,0x7a,0x5f,0x5f}, 0x00000d42},   // mz__ PORTUGUESE.12
    {{0x6e,0x61,0x5f,0x5f}, 0x06006644},   // na__ AFRIKAANS.12 GERMAN.3
    {{0x6e,0x63,0x5f,0x5f}, 0x00000542},   // nc__ FRENCH.12
    {{0x6e,0x65,0x5f,0x5f}, 0x8b000542},   // ne__ FRENCH.12 HAUSA.1
    {{0x6e,0x66,0x5f,0x5f}, 0x00000542},   // nf__ FRENCH.12
    {{0x6e,0x69,0x5f,0x5f}, 0x00000f42},   // ni__ SPANISH.12
    {{0x6e,0x6c,0x5f,0x5f}, 0x00000342},   // nl__ DUTCH.12
    {{0x6e,0x6f,0x5f,0x5f}, 0x51000b43},   // no__ NORWEGIAN.12 NORWEGIAN_N.2
    {{0x6e,0x75,0x5f,0x5f}, 0x0300103b},   // nu__ SWEDISH.11 DUTCH.5
    {{0x6f,0x6d,0x5f,0x5f}, 0x00003742},   // om__ ARABIC.12
    {{0x70,0x61,0x5f,0x5f}, 0x00000f42},   // pa__ SPANISH.12
    {{0x70,0x65,0x5f,0x5f}, 0x00000f42},   // pe__ SPANISH.12
    {{0x70,0x66,0x5f,0x5f}, 0x00000542},   // pf__ FRENCH.12
    {{0x70,0x67,0x5f,0x5f}, 0x00000f24},   // pg__ SPANISH.9
    {{0x70,0x68,0x5f,0x5f}, 0x00002142},   // ph__ TAGALOG.12
    {{0x70,0x6b,0x5f,0x5f}, 0x00003342},   // pk__ URDU.12
    {{0x70,0x6c,0x5f,0x5f}, 0x30000c42},   // pl__ POLISH.12 BELARUSIAN.1
    {{0x70,0x6e,0x5f,0x5f}, 0x04000644},   // pn__ GERMAN.12 FINNISH.3
    {{0x70,0x72,0x5f,0x5f}, 0x00000f42},   // pr__ SPANISH.12
    {{0x70,0x72,0x6f,0x5f}, 0x46050fd5},   // pro_ SPANISH.11 FRENCH.5 ChineseT.3
    {{0x70,0x73,0x5f,0x5f}, 0x00003742},   // ps__ ARABIC.12
    {{0x70,0x74,0x5f,0x5f}, 0x00000d42},   // pt__ PORTUGUESE.12
    {{0x70,0x79,0x5f,0x5f}, 0x00000f42},   // py__ SPANISH.12
    {{0x71,0x61,0x5f,0x5f}, 0x00003742},   // qa__ ARABIC.12
    {{0x72,0x65,0x5f,0x5f}, 0x00000542},   // re__ FRENCH.12
    {{0x72,0x6f,0x5f,0x5f}, 0x00001742},   // ro__ ROMANIAN.12
    {{0x72,0x73,0x5f,0x5f}, 0x00001d42},   // rs__ CROATIAN.12
    {{0x72,0x77,0x5f,0x5f}, 0x9000053e},   // rw__ FRENCH.11 KINYARWANDA.8
    {{0x73,0x61,0x5f,0x5f}, 0x00003742},   // sa__ ARABIC.12
    {{0x73,0x62,0x5f,0x5f}, 0x00000442},   // sb__ FINNISH.12
    {{0x73,0x63,0x5f,0x5f}, 0x060f092f},   // sc__ Japanese.10 SPANISH.7 GERMAN.3
    {{0x73,0x64,0x5f,0x5f}, 0x00003742},   // sd__ ARABIC.12
    {{0x73,0x65,0x5f,0x5f}, 0x00001042},   // se__ SWEDISH.12
    {{0x73,0x69,0x5f,0x5f}, 0x00004042},   // si__ SLOVENIAN.12
    {{0x73,0x6b,0x5f,0x5f}, 0x12004543},   // sk__ SLOVAK.12 CZECH.2
    {{0x73,0x6d,0x5f,0x5f}, 0x00000842},   // sm__ ITALIAN.12
    {{0x73,0x6e,0x5f,0x5f}, 0x00000542},   // sn__ FRENCH.12
    {{0x73,0x72,0x5f,0x5f}, 0x03001e44},   // sr__ SERBIAN.12 DUTCH.3
    {{0x73,0x76,0x5f,0x5f}, 0x00000f42},   // sv__ SPANISH.12
    {{0x73,0x79,0x5f,0x5f}, 0x00003742},   // sy__ ARABIC.12
    {{0x74,0x63,0x5f,0x5f}, 0x0a2206cd},   // tc__ GERMAN.10 TURKISH.6 Korean.5
    {{0x74,0x66,0x5f,0x5f}, 0x00000642},   // tf__ GERMAN.12
    {{0x74,0x67,0x5f,0x5f}, 0x00000542},   // tg__ FRENCH.12
    {{0x74,0x68,0x5f,0x5f}, 0x9e0936c9},   // th__ THAI.10 Japanese.3 SCOTS.1
    {{0x74,0x6a,0x5f,0x5f}, 0x00007924},   // tj__ TAJIK.9
    {{0x74,0x6c,0x5f,0x5f}, 0x060f0dcd},   // tl__ PORTUGUESE.10 SPANISH.6 GERMAN.5
    {{0x74,0x6e,0x5f,0x5f}, 0x3700053e},   // tn__ FRENCH.11 ARABIC.8
    {{0x74,0x6f,0x5f,0x5f}, 0x064609c5},   // to__ Japanese.9 ChineseT.7 GERMAN.6
    {{0x74,0x70,0x5f,0x5f}, 0x06000944},   // tp__ Japanese.12 GERMAN.3
    {{0x74,0x72,0x5f,0x5f}, 0x00002242},   // tr__ TURKISH.12
    {{0x74,0x72,0x61,0x76}, 0x064509c3},   // trav Japanese.9 SLOVAK.5 GERMAN.4
    {{0x74,0x74,0x5f,0x5f}, 0x0f00063e},   // tt__ GERMAN.11 SPANISH.8
    {{0x74,0x77,0x5f,0x5f}, 0x00004642},   // tw__ ChineseT.12
    {{0x74,0x7a,0x5f,0x5f}, 0x00003f42},   // tz__ SWAHILI.12
    {{0x75,0x61,0x5f,0x5f}, 0x0000232d},   // ua__ UKRAINIAN.10
    {{0x75,0x79,0x5f,0x5f}, 0x00000f42},   // uy__ SPANISH.12
    {{0x75,0x7a,0x5f,0x5f}, 0x0000492d},   // uz__ UZBEK.10
    {{0x76,0x61,0x5f,0x5f}, 0x060f0828},   // va__ ITALIAN.9 SPANISH.7 GERMAN.5
    {{0x76,0x63,0x5f,0x5f}, 0x0d000939},   // vc__ Japanese.11 PORTUGUESE.3
    {{0x76,0x65,0x5f,0x5f}, 0x00000f42},   // ve__ SPANISH.12
    {{0x76,0x67,0x5f,0x5f}, 0x09000f43},   // vg__ SPANISH.12 Japanese.2
    {{0x76,0x69,0x5f,0x5f}, 0x00002942},   // vi__ MALAY.12
    {{0x76,0x6e,0x5f,0x5f}, 0x00004342},   // vn__ VIETNAMESE.12
    {{0x76,0x75,0x5f,0x5f}, 0x00000642},   // vu__ GERMAN.12
    {{0x77,0x73,0x5f,0x5f}, 0x4b0f0624},   // ws__ GERMAN.9 SPANISH.5 AZERBAIJANI.1
    {{0x79,0x65,0x5f,0x5f}, 0x00003742},   // ye__ ARABIC.12
    {{0x79,0x75,0x5f,0x5f}, 0x1e001d3d},   // yu__ CROATIAN.11 SERBIAN.7
    {{0x7a,0x61,0x5f,0x5f}, 0x00006642},   // za__ AFRIKAANS.12
    {{0x7a,0x6d,0x5f,0x5f}, 0x0b000435},   // zm__ FINNISH.10 NORWEGIAN.9
    {{0x7a,0x77,0x5f,0x5f}, 0x3f00783e},   // zw__ SHONA.11 SWAHILI.8
  };
  
  
  // Statistically closest language, based on quadgram table
  // Those that are far from other languges map to UNKNOWN_LANGUAGE
  // Subscripted by Language
  //
  // From lang_correlation.txt and hand-edits
  // sed 's/^\([^ ]*\) \([^ ]*\) coef=0\.\(..\).*$/
  //   (\3 >= kMinCorrPercent) ? \2 : UNKNOWN_LANGUAGE,
  //   \/\/ \1/' lang_correlation.txt >/tmp/closest_lang_decl.txt
  //
  static const int kMinCorrPercent = 24;        // Pick off how close you want
                                                // 24 catches PERSIAN <== ARABIC
                                                // but not SPANISH <== PORTUGESE
  static Language Unknown = UNKNOWN_LANGUAGE;
  
  // Subscripted by Language
  static const Language kClosestAltLanguage[] = {
    (28 >= kMinCorrPercent) ? SCOTS : UNKNOWN_LANGUAGE,  // ENGLISH
    (36 >= kMinCorrPercent) ? NORWEGIAN : UNKNOWN_LANGUAGE,  // DANISH
    (31 >= kMinCorrPercent) ? AFRIKAANS : UNKNOWN_LANGUAGE,  // DUTCH
    (15 >= kMinCorrPercent) ? ESTONIAN : UNKNOWN_LANGUAGE,  // FINNISH
    (11 >= kMinCorrPercent) ? OCCITAN : UNKNOWN_LANGUAGE,  // FRENCH
    (17 >= kMinCorrPercent) ? LUXEMBOURGISH : UNKNOWN_LANGUAGE,  // GERMAN
    (27 >= kMinCorrPercent) ? YIDDISH : UNKNOWN_LANGUAGE,  // HEBREW
    (16 >= kMinCorrPercent) ? CORSICAN : UNKNOWN_LANGUAGE,  // ITALIAN
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // Japanese
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // Korean
    (41 >= kMinCorrPercent) ? NORWEGIAN_N : UNKNOWN_LANGUAGE,  // NORWEGIAN
    ( 5 >= kMinCorrPercent) ? SLOVAK : UNKNOWN_LANGUAGE,  // POLISH
    (23 >= kMinCorrPercent) ? SPANISH : UNKNOWN_LANGUAGE,  // PORTUGUESE
    (33 >= kMinCorrPercent) ? BULGARIAN : UNKNOWN_LANGUAGE,  // RUSSIAN
    (28 >= kMinCorrPercent) ? GALICIAN : UNKNOWN_LANGUAGE,  // SPANISH
    (17 >= kMinCorrPercent) ? NORWEGIAN : UNKNOWN_LANGUAGE,  // SWEDISH
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // Chinese
    (42 >= kMinCorrPercent) ? SLOVAK : UNKNOWN_LANGUAGE,  // CZECH
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // GREEK
    (35 >= kMinCorrPercent) ? FAROESE : UNKNOWN_LANGUAGE,  // ICELANDIC
    ( 7 >= kMinCorrPercent) ? LITHUANIAN : UNKNOWN_LANGUAGE,  // LATVIAN
    ( 7 >= kMinCorrPercent) ? LATVIAN : UNKNOWN_LANGUAGE,  // LITHUANIAN
    ( 4 >= kMinCorrPercent) ? LATIN : UNKNOWN_LANGUAGE,  // ROMANIAN
    ( 4 >= kMinCorrPercent) ? SLOVAK : UNKNOWN_LANGUAGE,  // HUNGARIAN
    (15 >= kMinCorrPercent) ? FINNISH : UNKNOWN_LANGUAGE,  // ESTONIAN
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // Ignore
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // Unknown
    (33 >= kMinCorrPercent) ? RUSSIAN : UNKNOWN_LANGUAGE,  // BULGARIAN
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // CROATIAN
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // SERBIAN
    (24 >= kMinCorrPercent) ? SCOTS_GAELIC : UNKNOWN_LANGUAGE,  // IRISH
    (28 >= kMinCorrPercent) ? SPANISH : UNKNOWN_LANGUAGE,  // GALICIAN
    ( 8 >= kMinCorrPercent) ? INDONESIAN : UNKNOWN_LANGUAGE,  // TAGALOG
    (29 >= kMinCorrPercent) ? AZERBAIJANI : UNKNOWN_LANGUAGE,  // TURKISH
    (28 >= kMinCorrPercent) ? RUSSIAN : UNKNOWN_LANGUAGE,  // UKRAINIAN
    (37 >= kMinCorrPercent) ? MARATHI : UNKNOWN_LANGUAGE,  // HINDI
    (29 >= kMinCorrPercent) ? BULGARIAN : UNKNOWN_LANGUAGE,  // MACEDONIAN
    (14 >= kMinCorrPercent) ? ASSAMESE : UNKNOWN_LANGUAGE,  // BENGALI
    (46 >= kMinCorrPercent) ? MALAY : UNKNOWN_LANGUAGE,  // INDONESIAN
    ( 9 >= kMinCorrPercent) ? INTERLINGUA : UNKNOWN_LANGUAGE,  // LATIN
    (46 >= kMinCorrPercent) ? INDONESIAN : UNKNOWN_LANGUAGE,  // MALAY
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // MALAYALAM
    ( 4 >= kMinCorrPercent) ? BRETON : UNKNOWN_LANGUAGE,  // WELSH
    ( 8 >= kMinCorrPercent) ? HINDI : UNKNOWN_LANGUAGE,  // NEPALI
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // TELUGU
    ( 3 >= kMinCorrPercent) ? ESPERANTO : UNKNOWN_LANGUAGE,  // ALBANIAN
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // TAMIL
    (22 >= kMinCorrPercent) ? UKRAINIAN : UNKNOWN_LANGUAGE,  // BELARUSIAN
    (15 >= kMinCorrPercent) ? SUNDANESE : UNKNOWN_LANGUAGE,  // JAVANESE
    (19 >= kMinCorrPercent) ? CATALAN : UNKNOWN_LANGUAGE,  // OCCITAN
    (27 >= kMinCorrPercent) ? PERSIAN : UNKNOWN_LANGUAGE,  // URDU
    (36 >= kMinCorrPercent) ? HINDI : UNKNOWN_LANGUAGE,  // BIHARI
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // GUJARATI
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // THAI
    (24 >= kMinCorrPercent) ? PERSIAN : UNKNOWN_LANGUAGE,  // ARABIC
    (19 >= kMinCorrPercent) ? OCCITAN : UNKNOWN_LANGUAGE,  // CATALAN
    ( 4 >= kMinCorrPercent) ? LATIN : UNKNOWN_LANGUAGE,  // ESPERANTO
    ( 3 >= kMinCorrPercent) ? GERMAN : UNKNOWN_LANGUAGE,  // BASQUE
    ( 9 >= kMinCorrPercent) ? LATIN : UNKNOWN_LANGUAGE,  // INTERLINGUA
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // KANNADA
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // PUNJABI
    (24 >= kMinCorrPercent) ? IRISH : UNKNOWN_LANGUAGE,  // SCOTS_GAELIC
    ( 7 >= kMinCorrPercent) ? KINYARWANDA : UNKNOWN_LANGUAGE,  // SWAHILI
    (28 >= kMinCorrPercent) ? SERBO_CROATIAN : UNKNOWN_LANGUAGE,  // SLOVENIAN
    (37 >= kMinCorrPercent) ? HINDI : UNKNOWN_LANGUAGE,  // MARATHI
    ( 3 >= kMinCorrPercent) ? ITALIAN : UNKNOWN_LANGUAGE,  // MALTESE
    ( 1 >= kMinCorrPercent) ? YORUBA : UNKNOWN_LANGUAGE,  // VIETNAMESE
    (15 >= kMinCorrPercent) ? DUTCH : UNKNOWN_LANGUAGE,  // FRISIAN
    (42 >= kMinCorrPercent) ? CZECH : UNKNOWN_LANGUAGE,  // SLOVAK
    // Original ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // ChineseT
    (24 >= kMinCorrPercent) ? CHINESE : UNKNOWN_LANGUAGE,  // ChineseT
    (35 >= kMinCorrPercent) ? ICELANDIC : UNKNOWN_LANGUAGE,  // FAROESE
    (15 >= kMinCorrPercent) ? JAVANESE : UNKNOWN_LANGUAGE,  // SUNDANESE
    (17 >= kMinCorrPercent) ? TAJIK : UNKNOWN_LANGUAGE,  // UZBEK
    ( 7 >= kMinCorrPercent) ? TIGRINYA : UNKNOWN_LANGUAGE,  // AMHARIC
    (29 >= kMinCorrPercent) ? TURKISH : UNKNOWN_LANGUAGE,  // AZERBAIJANI
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // GEORGIAN
    ( 7 >= kMinCorrPercent) ? AMHARIC : UNKNOWN_LANGUAGE,  // TIGRINYA
    (27 >= kMinCorrPercent) ? URDU : UNKNOWN_LANGUAGE,  // PERSIAN
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // BOSNIAN
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // SINHALESE
    (41 >= kMinCorrPercent) ? NORWEGIAN : UNKNOWN_LANGUAGE,  // NORWEGIAN_N
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // PORTUGUESE_P
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // PORTUGUESE_B
    (37 >= kMinCorrPercent) ? ZULU : UNKNOWN_LANGUAGE,  // XHOSA
    (37 >= kMinCorrPercent) ? XHOSA : UNKNOWN_LANGUAGE,  // ZULU
    ( 2 >= kMinCorrPercent) ? SPANISH : UNKNOWN_LANGUAGE,  // GUARANI
    (29 >= kMinCorrPercent) ? TSWANA : UNKNOWN_LANGUAGE,  // SESOTHO
    ( 7 >= kMinCorrPercent) ? TURKISH : UNKNOWN_LANGUAGE,  // TURKMEN
    ( 8 >= kMinCorrPercent) ? KAZAKH : UNKNOWN_LANGUAGE,  // KYRGYZ
    ( 5 >= kMinCorrPercent) ? FRENCH : UNKNOWN_LANGUAGE,  // BRETON
    ( 3 >= kMinCorrPercent) ? GANDA : UNKNOWN_LANGUAGE,  // TWI
    (27 >= kMinCorrPercent) ? HEBREW : UNKNOWN_LANGUAGE,  // YIDDISH
    (28 >= kMinCorrPercent) ? SLOVENIAN : UNKNOWN_LANGUAGE,  // SERBO_CROATIAN
    (12 >= kMinCorrPercent) ? OROMO : UNKNOWN_LANGUAGE,  // SOMALI
    ( 9 >= kMinCorrPercent) ? UZBEK : UNKNOWN_LANGUAGE,  // UIGHUR
    (15 >= kMinCorrPercent) ? PERSIAN : UNKNOWN_LANGUAGE,  // KURDISH
    ( 6 >= kMinCorrPercent) ? KYRGYZ : UNKNOWN_LANGUAGE,  // MONGOLIAN
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // ARMENIAN
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // LAOTHIAN
    ( 8 >= kMinCorrPercent) ? URDU : UNKNOWN_LANGUAGE,  // SINDHI
    (10 >= kMinCorrPercent) ? ITALIAN : UNKNOWN_LANGUAGE,  // RHAETO_ROMANCE
    (31 >= kMinCorrPercent) ? DUTCH : UNKNOWN_LANGUAGE,  // AFRIKAANS
    (17 >= kMinCorrPercent) ? GERMAN : UNKNOWN_LANGUAGE,  // LUXEMBOURGISH
    ( 2 >= kMinCorrPercent) ? SCOTS : UNKNOWN_LANGUAGE,  // BURMESE
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // KHMER
    (45 >= kMinCorrPercent) ? DZONGKHA : UNKNOWN_LANGUAGE,  // TIBETAN
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // DHIVEHI
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // CHEROKEE
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // SYRIAC
    ( 8 >= kMinCorrPercent) ? DUTCH : UNKNOWN_LANGUAGE,  // LIMBU
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // ORIYA
    (14 >= kMinCorrPercent) ? BENGALI : UNKNOWN_LANGUAGE,  // ASSAMESE
    (16 >= kMinCorrPercent) ? ITALIAN : UNKNOWN_LANGUAGE,  // CORSICAN
    ( 5 >= kMinCorrPercent) ? INTERLINGUA : UNKNOWN_LANGUAGE,  // INTERLINGUE
    ( 8 >= kMinCorrPercent) ? KYRGYZ : UNKNOWN_LANGUAGE,  // KAZAKH
    ( 4 >= kMinCorrPercent) ? SWAHILI : UNKNOWN_LANGUAGE,  // LINGALA
    (11 >= kMinCorrPercent) ? RUSSIAN : UNKNOWN_LANGUAGE,  // MOLDAVIAN
    (19 >= kMinCorrPercent) ? PERSIAN : UNKNOWN_LANGUAGE,  // PASHTO
    ( 5 >= kMinCorrPercent) ? AYMARA : UNKNOWN_LANGUAGE,  // QUECHUA
    ( 5 >= kMinCorrPercent) ? KINYARWANDA : UNKNOWN_LANGUAGE,  // SHONA
    (17 >= kMinCorrPercent) ? UZBEK : UNKNOWN_LANGUAGE,  // TAJIK
    (13 >= kMinCorrPercent) ? BASHKIR : UNKNOWN_LANGUAGE,  // TATAR
    (11 >= kMinCorrPercent) ? SAMOAN : UNKNOWN_LANGUAGE,  // TONGA
    ( 2 >= kMinCorrPercent) ? TWI : UNKNOWN_LANGUAGE,  // YORUBA
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // CREOLES_AND_PIDGINS_ENGLISH_BASED
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // CREOLES_AND_PIDGINS_FRENCH_BASED
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // CREOLES_AND_PIDGINS_PORTUGUESE_BASED
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // CREOLES_AND_PIDGINS_OTHER
    ( 6 >= kMinCorrPercent) ? TONGA : UNKNOWN_LANGUAGE,  // MAORI
    ( 3 >= kMinCorrPercent) ? OROMO : UNKNOWN_LANGUAGE,  // WOLOF
    ( 1 >= kMinCorrPercent) ? MONGOLIAN : UNKNOWN_LANGUAGE,  // ABKHAZIAN
    ( 8 >= kMinCorrPercent) ? SOMALI : UNKNOWN_LANGUAGE,  // AFAR
    ( 5 >= kMinCorrPercent) ? QUECHUA : UNKNOWN_LANGUAGE,  // AYMARA
    (13 >= kMinCorrPercent) ? TATAR : UNKNOWN_LANGUAGE,  // BASHKIR
    ( 3 >= kMinCorrPercent) ? ENGLISH : UNKNOWN_LANGUAGE,  // BISLAMA
    (45 >= kMinCorrPercent) ? TIBETAN : UNKNOWN_LANGUAGE,  // DZONGKHA
    ( 4 >= kMinCorrPercent) ? TONGA : UNKNOWN_LANGUAGE,  // FIJIAN
    ( 7 >= kMinCorrPercent) ? INUPIAK : UNKNOWN_LANGUAGE,  // GREENLANDIC
    ( 3 >= kMinCorrPercent) ? AFAR : UNKNOWN_LANGUAGE,  // HAUSA
    ( 3 >= kMinCorrPercent) ? OCCITAN : UNKNOWN_LANGUAGE,  // HAITIAN_CREOLE
    ( 7 >= kMinCorrPercent) ? GREENLANDIC : UNKNOWN_LANGUAGE,  // INUPIAK
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // INUKTITUT
    ( 4 >= kMinCorrPercent) ? HINDI : UNKNOWN_LANGUAGE,  // KASHMIRI
    (30 >= kMinCorrPercent) ? RUNDI : UNKNOWN_LANGUAGE,  // KINYARWANDA
    ( 2 >= kMinCorrPercent) ? TAGALOG : UNKNOWN_LANGUAGE,  // MALAGASY
    (17 >= kMinCorrPercent) ? GERMAN : UNKNOWN_LANGUAGE,  // NAURU
    (12 >= kMinCorrPercent) ? SOMALI : UNKNOWN_LANGUAGE,  // OROMO
    (30 >= kMinCorrPercent) ? KINYARWANDA : UNKNOWN_LANGUAGE,  // RUNDI
    (11 >= kMinCorrPercent) ? TONGA : UNKNOWN_LANGUAGE,  // SAMOAN
    ( 1 >= kMinCorrPercent) ? LINGALA : UNKNOWN_LANGUAGE,  // SANGO
    (32 >= kMinCorrPercent) ? MARATHI : UNKNOWN_LANGUAGE,  // SANSKRIT
    (16 >= kMinCorrPercent) ? ZULU : UNKNOWN_LANGUAGE,  // SISWANT
    ( 5 >= kMinCorrPercent) ? SISWANT : UNKNOWN_LANGUAGE,  // TSONGA
    (29 >= kMinCorrPercent) ? SESOTHO : UNKNOWN_LANGUAGE,  // TSWANA
    ( 2 >= kMinCorrPercent) ? ESTONIAN : UNKNOWN_LANGUAGE,  // VOLAPUK
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // ZHUANG
    ( 1 >= kMinCorrPercent) ? MALAY : UNKNOWN_LANGUAGE,  // KHASI
    (28 >= kMinCorrPercent) ? ENGLISH : UNKNOWN_LANGUAGE,  // SCOTS
    (15 >= kMinCorrPercent) ? KINYARWANDA : UNKNOWN_LANGUAGE,  // GANDA
    ( 7 >= kMinCorrPercent) ? ENGLISH : UNKNOWN_LANGUAGE,  // MANX
    ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE,  // MONTENEGRIN
  };
  
  COMPILE_ASSERT(arraysize(kClosestAltLanguage) == NUM_LANGUAGES,
                 kClosestAltLanguage_has_incorrect_size);
  
  
  inline bool FlagFinish(int flags) {return (flags & kCLDFlagFinish) != 0;}
  inline bool FlagSqueeze(int flags) {return (flags & kCLDFlagSqueeze) != 0;}
  inline bool FlagRepeats(int flags) {return (flags & kCLDFlagRepeats) != 0;}
  inline bool FlagTop40(int flags) {return (flags & kCLDFlagTop40) != 0;}
  inline bool FlagShort(int flags) {return (flags & kCLDFlagShort) != 0;}
  inline bool FlagHint(int flags) {return (flags & kCLDFlagHint) != 0;}
  inline bool FlagUseWords(int flags) {return (flags & kCLDFlagUseWords) != 0;}
  
  
  
  
  //------------------------------------------------------------------------------
  // For --cld_html debugging output. Not thread safe
  //------------------------------------------------------------------------------
  static Language prior_lang = UNKNOWN_LANGUAGE;
  static bool prior_unreliable = false;
  
  //------------------------------------------------------------------------------
  // End For --cld_html debugging output
  //------------------------------------------------------------------------------
  
  
  // Backscan to word boundary, returning how many bytes n to go back
  // so that src - n is non-space ans src - n - 1 is space.
  // If not found in kMaxSpaceScan bytes, return 0
  int BackscanToSpace(const char* src, int limit) {
    int n = 0;
    limit = cld::minint(limit, kMaxSpaceScan);
    while (n < limit) {
      if (src[-n - 1] == ' ') {return n;}    // We are at _X
      ++n;
    }
    return 0;
  }
  
  // Forwardscan to word boundary, returning how many bytes n to go forward
  // so that src + n is non-space ans src + n - 1 is space.
  // If not found in kMaxSpaceScan bytes, return 0
  int ForwardscanToSpace(const char* src, int limit) {
    int n = 0;
    limit = cld::minint(limit, kMaxSpaceScan);
    while (n < limit) {
      if (src[n] == ' ') {return n + 1;}    // We are at _X
      ++n;
    }
    return 0;
  }
  
  
  // This uses a cheap predictor to get a measure of compression, and
  // hence a measure of repetitiveness. It works on complete UTF-8 characters
  // instead of bytes, because three-byte UTF-8 Indic, etc. text compress highly
  // all the time when done with a byte-based count. Sigh.
  //
  // To allow running prediction across multiple chunks, caller passes in current
  // 12-bit hash value and int[4096] prediction table. Caller inits these to 0.
  //
  // Returns the number of *bytes* correctly predicted, increments by 1..4 for
  // each correctly-predicted character.
  //
  // NOTE: Overruns by up to three bytes. Not a problem with valid UTF-8 text
  //
  int CountPredictedBytes(const char* isrc, int srclen, int* hash, int* tbl) {
    int p_count = 0;
    const uint8* src = reinterpret_cast<const uint8*>(isrc);
    const uint8* srclimit = src + srclen;
    int local_hash = *hash;
  
    while (src < srclimit) {
      int c = src[0];
      int incr = 1;
  
      // Pick up one char and length
      if (c < 0xc0) {
        // One-byte or continuation byte: 00xxxxxx 01xxxxxx 10xxxxxx
        // Do nothing more
      } else if ((c & 0xe0) == 0xc0) {
        // Two-byte
        c = (c << 8) | src[1];
        incr = 2;
      } else if ((c & 0xf0) == 0xe0) {
        // Three-byte
        c = (c << 16) | (src[1] << 8) | src[2];
        incr = 3;
      } else {
        // Four-byte
        c = (c << 24) | (src[1] << 16) | (src[2] << 8) | src[3];
        incr = 4;
      }
      src += incr;
  
      int p = tbl[local_hash];            // Prediction
      tbl[local_hash] = c;                // Update prediction
      p_count += (c == p);                // Count good predictions
  
      local_hash = ((local_hash << 4) ^ c) & 0xfff;
    }
  
    *hash = local_hash;
    return p_count;
  }
  
  
  
  // Counts number of spaces; a little faster than one-at-a-time
  // Doesn't count odd bytes at end
  int CountSpaces4(const char* src, int src_len) {
    int s_count = 0;
    for (int i = 0; i < (src_len & ~3); i += 4) {
      s_count += (src[i] == ' ');
      s_count += (src[i+1] == ' ');
      s_count += (src[i+2] == ' ');
      s_count += (src[i+3] == ' ');
    }
    return s_count;
  }
  
  // Remove words of text that have more than half their letters predicted
  // correctly by our cheap predictor, moving the remaining words in-place
  // to the front of the input buffer.
  //
  // To allow running prediction across multiple chunks, caller passes in current
  // 12-bit hash value and int[4096] prediction table. Caller inits these to 0.
  //
  // Return the new, possibly-shorter length
  //
  // Result Buffer ALWAYS has leading space and trailing space space space NUL,
  // if input does
  //
  int CheapRepWordsInplace(char* isrc, int srclen, int* hash, int* tbl) {
    const uint8* src = reinterpret_cast<const uint8*>(isrc);
    const uint8* srclimit = src + srclen;
    char* dst = isrc;
    int local_hash = *hash;
    char* word_dst = dst;           // Start of next word
    int good_predict_bytes = 0;
    int word_length_bytes = 0;
  
    while (src < srclimit) {
      int c = src[0];
      int incr = 1;
      *dst++ = c;
  
      if (c == ' ') {
        if ((good_predict_bytes * 2) > word_length_bytes) {
          // Word is well-predicted: backup to start of this word
          dst = word_dst;
          if (FLAGS_cld_showme) {
            // Mark the deletion point with period
            // Don't repeat multiple periods
            // Cannot mark with more bytes or may overwrite unseen input
            if ((isrc < (dst - 2)) && (dst[-2] != '.')) {
              *dst++ = '.';
              *dst++ = ' ';
            }
          }
        }
        word_dst = dst;              // Start of next word
        good_predict_bytes = 0;
        word_length_bytes = 0;
      }
  
      // Pick up one char and length
      if (c < 0xc0) {
        // One-byte or continuation byte: 00xxxxxx 01xxxxxx 10xxxxxx
        // Do nothing more
      } else if ((c & 0xe0) == 0xc0) {
        // Two-byte
        *dst++ = src[1];
        c = (c << 8) | src[1];
        incr = 2;
      } else if ((c & 0xf0) == 0xe0) {
        // Three-byte
        *dst++ = src[1];
        *dst++ = src[2];
        c = (c << 16) | (src[1] << 8) | src[2];
        incr = 3;
      } else {
        // Four-byte
        *dst++ = src[1];
        *dst++ = src[2];
        *dst++ = src[3];
        c = (c << 24) | (src[1] << 16) | (src[2] << 8) | src[3];
        incr = 4;
      }
      src += incr;
      word_length_bytes += incr;
  
      int p = tbl[local_hash];            // Prediction
      tbl[local_hash] = c;                // Update prediction
      if (c == p) {
        good_predict_bytes += incr;       // Count good predictions
      }
  
      local_hash = ((local_hash << 4) ^ c) & 0xfff;
    }
  
    *hash = local_hash;
  
    if ((dst - isrc) < (srclen - 3)) {
      // Pad and make last char clean UTF-8 by putting following spaces
      dst[0] = ' ';
      dst[1] = ' ';
      dst[2] = ' ';
      dst[3] = '\0';
    } else   if ((dst - isrc) < srclen) {
      // Make last char clean UTF-8 by putting following space off the end
      dst[0] = ' ';
    }
  
    return static_cast<int>(dst - isrc);
  }
  
  
  // Remove portions of text that have a high density of spaces, or that are
  // overly repetitive, squeezing the remaining text in-place to the front of the
  // input buffer.
  //
  // Squeezing looks at density of space/prediced chars in fixed-size chunks,
  // specified by chunksize. A chunksize <= 0 uses the default size of 48 bytes.
  //
  // Return the new, possibly-shorter length
  //
  // Result Buffer ALWAYS has leading space and trailing space space space NUL,
  // if input does
  //
  int CompactLangDetImpl::CheapSqueezeInplace(char* isrc,
                                              int srclen,
                                              int ichunksize) {
    char* src = isrc;
    char* dst = src;
    char* srclimit = src + srclen;
    bool skipping = false;
  
    int hash = 0;
    // Allocate local prediction table.
    int* predict_tbl = new int[kPredictionTableSize];
    memset(predict_tbl, 0, kPredictionTableSize * sizeof(predict_tbl[0]));
  
    int chunksize = ichunksize;
    if (chunksize == 0) {chunksize = kChunksizeDefault;}
    int space_thresh = (chunksize * kSpacesThreshPercent) / 100;
    int predict_thresh = (chunksize * kPredictThreshPercent) / 100;
  
    while (src < srclimit) {
      int remaining_bytes = srclimit - src;
      int len = cld::minint(chunksize, remaining_bytes);
      int space_n = CountSpaces4(src, len);
      int predb_n = CountPredictedBytes(src, len, &hash, predict_tbl);
      if ((space_n >= space_thresh) || (predb_n >= predict_thresh)) {
        // Skip the text
        if (!skipping) {
          // Keeping-to-skipping transition; do it at a space
          int n = BackscanToSpace(dst, static_cast<int>(dst - isrc));
          dst -= n;
          skipping = true;
          if (FLAGS_cld_showme) {
            // Mark the deletion point with black square U+25A0
            *dst++ = 0xe2;
            *dst++ = 0x96;
            *dst++ = 0xa0;
            *dst++ = ' ';
          }
          if (dst == isrc) {
            // Force a leading space if the first chunk is deleted
            *dst++ = ' ';
          }
        }
      } else {
        // Keep the text
        if (skipping) {
          // Skipping-to-keeping transition; do it at a space
          int n = ForwardscanToSpace(src, len);
          src += n;
          remaining_bytes -= n;   // Shrink remaining length
          len -= n;
          skipping = false;
        }
        // "len" can be negative in some cases
        if (len > 0) {
          memcpy(dst, src, len);
          dst += len;
        }
      }
      src += len;
    }
  
    if ((dst - isrc) < (srclen - 3)) {
      // Pad and make last char clean UTF-8 by putting following spaces
      dst[0] = ' ';
      dst[1] = ' ';
      dst[2] = ' ';
      dst[3] = '\0';
    } else   if ((dst - isrc) < srclen) {
      // Make last char clean UTF-8 by putting following space off the end
      dst[0] = ' ';
    }
  
    // Deallocate local prediction table
    delete[] predict_tbl;
    return static_cast<int>(dst - isrc);
  }
  
  // Timing 2.8GHz P4 (dsites 2008.03.20) with 170KB input
  //  About 90 MB/sec, with or without memcpy, chunksize 48 or 4096
  //  Just CountSpaces is about 340 MB/sec
  //  Byte-only CountPredictedBytes is about 150 MB/sec
  //  Byte-only CountPredictedBytes, conditional tbl[] = is about 85! MB/sec
  //  Byte-only CountPredictedBytes is about 180 MB/sec, byte tbl, byte/int c
  //  Unjammed byte-only both = 170 MB/sec
  //  Jammed byte-only both = 120 MB/sec
  //  Back to original w/slight updates, 110 MB/sec
  //
  bool CheapSqueezeTriggerTest(const char* src, int srclen, int testsize) {
    // Don't trigger at all on short text
    if (srclen < testsize) {return false;}
    int space_thresh = (testsize * kSpacesTriggerPercent) / 100;
    int predict_thresh = (testsize * kPredictTriggerPercent) / 100;
    int hash = 0;
    // Allocate local prediction table.
    int* predict_tbl = new int[kPredictionTableSize];
    memset(predict_tbl, 0, kPredictionTableSize * sizeof(predict_tbl[0]));
  
    bool retval = false;
    if ((CountSpaces4(src, testsize) >= space_thresh) ||
        (CountPredictedBytes(src, testsize, &hash, predict_tbl) >=
         predict_thresh)) {
      retval = true;
    }
    // Deallocate local prediction table
    delete[] predict_tbl;
    return retval;
  }
  
  
  
  // Close pairs (correlation) language_enum/language_enum
  //  id/ms (0.47)    38/40    [1]
  //  bo/dz (0.46)    105/135  [2]
  //  cz/sk (0.43)    17/68    [3]
  //  no/nn (0.42)    10/80    [4]
  //  hi/mr (0.38)    35/64    [5]
  //  xh/zu (0.37)    83/84    [6]
  // Subscripted by packed language, gives 0 or a subscript in closepair
  // scoring array inside doc_tote
  static const uint8 kClosePair[EXT_NUM_LANGUAGES + 1] = {
    0,
    0,0,0,0,0,0,0,0, 0,0,4,0,0,0,0,0, 0,3,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,5,0,0,1,0, 1,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    5,0,0,0,3,0,0,0, 0,0,0,0,0,0,0,0, 4,0,0,6,6,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,2,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,2, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    // Add new language close-pair number just before here (just use 0)
  };
  
  
  // Delete any extended languages from doc_tote
  void RemoveExtendedLanguages(ToteWithReliability* doc_tote) {
    for (int sub = 0; sub < doc_tote->MaxSize(); ++sub) {
      if (cld::UnpackLanguage(doc_tote->Key(sub)) >= NUM_LANGUAGES) {
        // Effectively remove the extended language by setting key&score to zero
        if (FLAGS_dbgscore) {
          fprintf(stderr, "{-%s} ",
                  ExtLanguageCode(cld::UnpackLanguage(doc_tote->Key(sub))));
        }
  
        // Delete entry
        doc_tote->SetKey(sub, 0);
        doc_tote->SetValue(sub, 0);
        doc_tote->SetReliability(sub, 0);
      }
    }
  }
  
  static const int kMinReliableKeepPercent = 41;  // Remove lang if reli < this
  
  // For Tier3 languages, require a minimum number of bytes to be first-place lang
  static const int kGoodFirstT3MinBytes = 24;         // <this => no first
  
  // Move bytes for unreliable langs to another lang or UNKNOWN
  // doc_tote is sorted, so cannot Add
  //
  // If both CHINESE and CHINESET are present and unreliable, do not delete both;
  // merge both into CHINESE.
  //
  //dsites 2009.03.19
  // we also want to remove Tier3 languages as the first lang if there is very
  // little text like ej1 ej2 ej3 ej4
  // maybe fold this back in earlier
  //
  void RemoveUnreliableLanguages(ToteWithReliability* doc_tote, bool do_remove_weak_matches) {
    // Prepass to merge some low-reliablility languages
    int total_bytes = 0;
    for (int sub = 0; sub < doc_tote->MaxSize(); ++sub) {
      int plang = doc_tote->Key(sub);
      if (plang == 0) {continue;}                     // Empty slot
  
      Language lang = cld::UnpackLanguage(plang);
      int bytes = doc_tote->Value(sub);
      int reli = doc_tote->Reliability(sub);
      if (bytes == 0) {continue;}                     // Zero bytes
      total_bytes += bytes;
  
      // Reliable percent is stored reliable score over stored bytecount
      int reliable_percent = reli / bytes;
      if (reliable_percent >= kMinReliableKeepPercent) {continue;}   // Keeper
  
      // This language is too unreliable to keep, but we might merge it.
      Language altlang = UNKNOWN_LANGUAGE;
      if (lang < NUM_LANGUAGES) {altlang = kClosestAltLanguage[lang];}
      if (altlang == UNKNOWN_LANGUAGE) {continue;}    // No alternative
  
      // Look for alternative in doc_tote
      int altsub = doc_tote->Find(cld::PackLanguage(altlang));
      if (altsub < 0) {continue;}                     // No alternative text
  
      int bytes2 = doc_tote->Value(altsub);
      int reli2 = doc_tote->Reliability(altsub);
      if (bytes2 == 0) {continue;}                    // Zero bytes
  
      // Reliable percent is stored reliable score over stored bytecount
      int reliable_percent2 = reli2 / bytes2;
  
      // Merge one language into the other. Break ties toward lower lang #
      int tosub = altsub;
      int fromsub = sub;
      bool into_lang = false;
      if ((reliable_percent2 < reliable_percent) ||
          ((reliable_percent2 == reliable_percent) && (lang < altlang))) {
        tosub = sub;
        fromsub = altsub;
        into_lang = true;
      }
  
      // Make sure reliability doesn't drop and is enough to avoid delete
      int newpercent = cld::maxint(reliable_percent, reliable_percent2);
      newpercent = cld::maxint(newpercent, kMinReliableKeepPercent);
      int newbytes = bytes + bytes2;
      int newreli = newpercent * newbytes;
  
      doc_tote->SetKey(fromsub, 0);
      doc_tote->SetValue(fromsub, 0);
      doc_tote->SetReliability(fromsub, 0);
      doc_tote->SetValue(tosub, newbytes);
      doc_tote->SetReliability(tosub, newreli);
  
      // Show fate of unreliable languages if at least 10 bytes
      if (FLAGS_cld_html /*&& (newpercent >= 10)*/ && (newbytes >= 10)) {
        if (into_lang) {
          fprintf(stderr, "{Unreli %s.%d(%dB) => %s} ",
                  ExtLanguageCode(altlang), reliable_percent2, bytes2,
                  ExtLanguageCode(lang));
        } else {
          fprintf(stderr, "{Unreli %s.%d(%dB) => %s} ",
                  ExtLanguageCode(lang), reliable_percent, bytes,
                  ExtLanguageCode(altlang));
        }
      }
    }
  
  
    if (do_remove_weak_matches) {
      // Pass to delete any remaining unreliable languages
      for (int sub = 0; sub < doc_tote->MaxSize(); ++sub) {
        int plang = doc_tote->Key(sub);
        if (plang == 0) {continue;}                     // Empty slot
  
        Language lang = cld::UnpackLanguage(plang);
        int bytes = doc_tote->Value(sub);
        int reli = doc_tote->Reliability(sub);
        if (bytes == 0) {continue;}                     // Zero bytes
  
        bool is_tier3 = (cld::kIsPackedTop40[plang] == 0);
        if (is_tier3 &&
            (bytes < kGoodFirstT3MinBytes) &&
            (bytes < total_bytes)) {
          reli = 0;                                     // Too-short tier3
        }
  
        // Reliable percent is stored as reliable score over stored bytecount
        int reliable_percent = reli / bytes;
        if (reliable_percent >= kMinReliableKeepPercent) {continue;}  // Keeper
  
        // Delete unreliable entry
        doc_tote->SetKey(sub, 0);
        doc_tote->SetValue(sub, 0);
        doc_tote->SetReliability(sub, 0);
  
        // Show fate of unreliable languages if at least 10 bytes
        if (FLAGS_cld_html /*&& (reliable_percent >= 10)*/ && (bytes >= 10)) {
          fprintf(stderr, "{Unreli %s.%d(%dB)} ",
                  ExtLanguageCode(lang), reliable_percent, bytes);
        }
      }
    }
  
    if (FLAGS_cld_html) {fprintf(stderr, "<br>\n");}
  }
  
  
  // Move less likely byte count to more likely for close pairs of languages
  void RefineScoredClosePairs(ToteWithReliability* doc_tote) {
    for (int sub = 0; sub < doc_tote->MaxSize(); ++sub) {
      int close_packedlang = doc_tote->Key(sub);
      int subscr = kClosePair[close_packedlang];
      if (subscr == 0) {continue;}
  
      // We have a close pair language -- if the other one is also scored and the
      // longword score differs enough, put all our eggs into one basket
  
      // Nonzero longword score: Go look for the other of this pair
      for (int sub2 = sub + 1; sub2 < doc_tote->MaxSize(); ++sub2) {
        if (kClosePair[doc_tote->Key(sub2)] == subscr) {
          // We have a matching pair
          int close_packedlang2 = doc_tote->Key(sub2);
  
          // Move all the text bytes from lower byte-count to higher one
          int from_sub, to_sub;
          Language from_lang, to_lang;
          if (doc_tote->Value(sub) < doc_tote->Value(sub2)) {
            from_sub = sub;
            to_sub = sub2;
            from_lang = cld::UnpackLanguage(close_packedlang);
            to_lang = cld::UnpackLanguage(close_packedlang2);
          } else {
            from_sub = sub2;
            to_sub = sub;
            from_lang = cld::UnpackLanguage(close_packedlang2);
            to_lang = cld::UnpackLanguage(close_packedlang);
          }
  
          // Move all the bytes smaller => larger of the pair
          if (FLAGS_cld_html || FLAGS_dbgscore) {
            // Show fate of closepair language
            int val = doc_tote->Value(from_sub);
            int reli = doc_tote->Reliability(from_sub);
            int reliable_percent = reli / (val ? val : 1);  // avoid zdiv
            fprintf(stderr, "{CloseLangPair: %s.%d%%(%dB) => %s} ",
                    ExtLanguageCode(from_lang),
                    reliable_percent,
                    doc_tote->Value(from_sub),
                    ExtLanguageCode(to_lang));
          }
          int sum = doc_tote->Value(to_sub) + doc_tote->Value(from_sub);
          doc_tote->SetValue(to_sub, sum);
          doc_tote->SetReliability(to_sub, 100 * sum);
  
          // Delete old entry
          doc_tote->SetKey(from_sub, 0);
          doc_tote->SetValue(from_sub, 0);
          doc_tote->SetReliability(from_sub, 0);
  
          break;    // Exit inner for sub2 loop
        }
      }     // End for sub2
    }   // End for sub
  }
  
  
  void ApplyLanguageHints(Tote* chunk_tote, int tote_grams,
                          uint8* lang_hint_boost) {
    // Need 8 quad/unigrams to give full hint boost, else derate linearly
    if (tote_grams > 8) {
      tote_grams = 8;
    }
    for (int sub = 0; sub < chunk_tote->MaxSize(); ++sub) {
      // Hint boosts are per packed subscript
      int lang_sub = chunk_tote->Key(sub);
      int new_value = chunk_tote->Value(sub) +
        ((lang_hint_boost[lang_sub] * tote_grams) >> 3);
      chunk_tote->SetValue(sub, new_value);
      if (FLAGS_dbgscore && (lang_hint_boost[lang_sub] > 0)) {
        fprintf(stderr, "[%s+=%d*%d/8] ",
                ExtLanguageCode(cld::UnpackLanguage(lang_sub)),
                lang_hint_boost[lang_sub], tote_grams);
      }
    }
  }
  
  
  void PrintHtmlEscapedText(FILE* f, const char* txt, int len) {
    for (int i = 0; i < len; ++i) {
      char c = txt[i];
      if (c == '<') {
        fprintf(f, "&lt;");
      } else if (c == '>') {
        fprintf(f, "&gt;");
      } else if (c == '&') {
        fprintf(f, "&amp;");
      } else if (c == '\'') {
        fprintf(f, "&apos;");
      } else if (c == '"') {
        fprintf(f, "&quot;");
      } else {
        fprintf(f, "%c", c);
      }
    }
    fprintf(f, "<br>\n");
  }
  
  
  // Add one chunk's score to running document score
  // If the top language is UNKNOWN_LANGUAGE, score nothing. This is used to
  // positively identify text to be ignored, such as link farms.
  // Sort before scoring and reinit afterward
  //
  // src and srclen are just for debug output
  void ScoreChunkIntoDoc(const char* src, int srclen, int advance_by,
                         UnicodeLScript lscript,
                         Tote* chunk_tote,
                         ToteWithReliability* doc_tote,
                         int tote_grams,
                         uint8* lang_hint_boost) {
    // Apply hints before sorting
    if (lang_hint_boost) {
      ApplyLanguageHints(chunk_tote, tote_grams, lang_hint_boost);
    }
  
    // Sort to get top two languages
    chunk_tote->Sort(2);
    Language cur_lang = cld::UnpackLanguage(chunk_tote->Key(0));
  
    // Return if empty
    if (cur_lang < 0) {
      chunk_tote->Reinit();
      return;
    }
  
    bool cur_unreliable = false;
  
    // Reliability is a function of mean script score per KB of text
    int len = chunk_tote->GetByteCount();
    int reliability = cld::GetReliability((len * 2) / advance_by,
                                          lscript,
                                          chunk_tote);
    cur_unreliable = (reliability < cld::kMinReliable);
  
    // If tote_grams=0, always reliable
    // If tote_grams=1, always unreliable
    if (tote_grams == 0) {
      reliability = 100;
      cur_unreliable = false;
    } else if (tote_grams == 1) {
      reliability = 0;
      cur_unreliable = true;
    }
  
  #if 0
    // TEMP
    if (FLAGS_cld_html) {
      if (reliability >= kMinReliableKeepPercent) {
        fprintf(stderr, "R%d%% ", reliability);
      } else {
        fprintf(stderr, "--R%d%% ", reliability);
      }
    }
  #endif
  
    // Track the sequence of language fragments [result currently unused]
    ////if (reliability >= kMinReliableSeq) {
    ////  doc_tote->AddSeq(chunk_tote->Key(0));
    ////}
  
    if (cur_unreliable && (chunk_tote->Key(1) != 0)) {
      // Unreliable and two top contenders, split byte count 5/8 - 3/8
      int top_len = ((len * 5) + 4) >> 3;
      int second_len = len - top_len;
  
      doc_tote->Add(chunk_tote->Key(0),
                    top_len, chunk_tote->Value(0), reliability);
      doc_tote->Add(chunk_tote->Key(1),
                    second_len, chunk_tote->Value(1), reliability);
      if (FLAGS_dbgscore) {
        fprintf(stderr, "{+%s.%d.%dR(%dB) +%s.%d.%dR(%dB)} ",
                ExtLanguageCode(cld::UnpackLanguage(chunk_tote->Key(0))),
                chunk_tote->Value(0),
                reliability,
                top_len,
                ExtLanguageCode(cld::UnpackLanguage(chunk_tote->Key(1))),
                chunk_tote->Value(1),
                reliability,
                second_len);
      }
    } else {
      // Reliable or single contender
      doc_tote->Add(chunk_tote->Key(0),
                    len, chunk_tote->Value(0), reliability);
      if (FLAGS_dbgscore) {
        fprintf(stderr, "{+%s.%d.%dR(%dB)} ",
                ExtLanguageCode(cld::UnpackLanguage(chunk_tote->Key(0))),
                chunk_tote->Value(0),
                reliability,
                len);
      }
    }
  
    if (FLAGS_cld_html) {
      if (cur_lang < 0) {cur_lang = UNKNOWN_LANGUAGE;}
      cld::PrintLang(stderr, chunk_tote,
                cur_lang, cur_unreliable,
                prior_lang, prior_unreliable);
      prior_lang = cur_lang;
      prior_unreliable = cur_unreliable;
  
      string temp(src, srclen);
      if (temp[0] == '=') {
        // Rewrite =ScriptX= or =SwitchX= as =Xxxx= for script code Xxxx
        temp = "=Buffered_";
        temp.append(UnicodeLScriptCode(lscript));
        temp.append("=");
      }
      cld::PrintText(stderr, cur_lang, temp);
    }
  
    chunk_tote->Reinit();
  }
  
  
  void PrintTopLang(Language top_lang) {
    if ((top_lang == prior_lang) && (top_lang != UNKNOWN_LANGUAGE)) {
      fprintf(stderr, "[] ");
    } else {
      fprintf(stderr, "[%s] ", ExtLanguageName(top_lang));
      prior_lang = top_lang;
    }
  }
  
  void PrintTopLangSpeculative(Language top_lang) {
    fprintf(stderr, "<span style=\"color:#%06X;\">", 0xa0a0a0);
    if ((top_lang == prior_lang) && (top_lang != UNKNOWN_LANGUAGE)) {
      fprintf(stderr, "[] ");
    } else {
      fprintf(stderr, "[%s] ", ExtLanguageName(top_lang));
      prior_lang = top_lang;
    }
    fprintf(stderr, "</span>\n");
  }
  
  
  // Add one chunk's score to running document score
  // Convenience function with constant src text
  void ScoreChunkIntoDoc2(const char* src, int advance_by,
                         UnicodeLScript lscript,
                         Tote* chunk_tote,
                         ToteWithReliability* doc_tote,
                         int tote_grams,
                         uint8* lang_hint_boost) {
    int srclen = static_cast<int>(strlen(src));
    ScoreChunkIntoDoc(src, srclen, advance_by, lscript, chunk_tote,
                      doc_tote, tote_grams, lang_hint_boost);
  }
  
  
  // Score one scriptspan using the only language for that script
  void ScoreNilgrams(getone::LangSpan* scriptspan, int lang,
                    ToteWithReliability* doc_tote,
                    uint8* lang_hint_boost,
                    int flags, Language plus_one) {
    // For debugging only. Not thread-safe
    prior_lang = UNKNOWN_LANGUAGE;
    prior_unreliable = false;
  
    const char* src = scriptspan->text;
    int len = scriptspan->text_bytes;
  
    Tote chunk_tote;
    // Score 1000 for 1000 bytes
    chunk_tote.AddGram();
    chunk_tote.Add(lang, scriptspan->text_bytes);
    chunk_tote.AddBytes(scriptspan->text_bytes);
    int advance_by = 2;
    int tote_grams = 0;   // Indicates fully reliable
    ScoreChunkIntoDoc(src, len, advance_by,
                      scriptspan->script, &chunk_tote,
                      doc_tote, tote_grams, lang_hint_boost);
  }
  
  // Score one scriptspan using unigrams
  // Updates tote_grams
  static void ScoreUnigrams(const UTF8PropObj* unigram_obj,
                        getone::LangSpan* scriptspan,
                        int* tote_grams, int gram_limit,
                        Tote* chunk_tote,
                        ToteWithReliability* doc_tote,
                        uint8* lang_hint_boost,
                        int advance_by, int flags,
                     int* initial_word_span, Language plus_one) {
    // chunk_tote may have partial sum coming in
    const char* src = scriptspan->text;
    const char* srclimit = src + scriptspan->text_bytes;
  
    // For debugging only. Not thread-safe
    prior_lang = UNKNOWN_LANGUAGE;
    prior_unreliable = false;
  
    // Break text up into multiple chunks and score each
    while (src < srclimit) {
      // Updates tote_grams
      int len = cld::DoUniScoreV3(unigram_obj,
                                   src, srclimit - src, advance_by,
                                   tote_grams, gram_limit, chunk_tote);
      if (FlagUseWords(flags) || (*initial_word_span > 0)) {
        // Use bigram scoring in addition to quadgrams
        cld::DoBigramScoreV3(&kCjkBiTable_obj,
                             src, len, chunk_tote);
      }
      chunk_tote->AddBytes(len);
      *initial_word_span -= len;
  
      if (*tote_grams >= gram_limit) {
        // Add this chunk to doc totals
        // Remove all but top40 if asked
        if (FlagTop40(flags)) {
          cld::DemoteNotTop40(chunk_tote, cld::PackLanguage(plus_one));
        }
  
        // Sort, accumulate into doc total, reinit
        ScoreChunkIntoDoc(src, len, advance_by,
                          scriptspan->script, chunk_tote,
                          doc_tote, *tote_grams, lang_hint_boost);
        *tote_grams = 0;
      } else {
        if (FLAGS_cld_html) {
          string temp(src, len);
          Language top_lang = cld::UnpackLanguage(chunk_tote->CurrentTopKey());
          PrintTopLangSpeculative(top_lang);
          cld::PrintText(stderr, top_lang, temp);
        }
      }
      src += len;
    }
    // chunk_tote may have partial sum going out
  }
  
  // Back up one UTF-8 character
  const uint8* BackOneUTF8(const uint8* p) {
    const uint8* retval = p - 1;
    if ((*retval & 0xc0) == 0x80) {--retval;}
    if ((*retval & 0xc0) == 0x80) {--retval;}
    if ((*retval & 0xc0) == 0x80) {--retval;}
    return retval;
  }
  
  
  // Score one scriptspan using quadgrams
  // Incoming chunk_tote may have partial accumulation
  static void ScoreQuadgrams(const cld::CLDTableSummary* quadgram_obj,
                         getone::LangSpan* scriptspan,
                         int* tote_grams, int gram_limit,
                         Tote* chunk_tote,
                         ToteWithReliability* doc_tote,
                         uint8* lang_hint_boost,
                         int advance_by, int flags,
                         int* initial_word_span, Language plus_one) {
    // chunk_tote may have partial sum coming in
    const char* src = scriptspan->text;
    const char* srclimit = src + scriptspan->text_bytes;
    const char* lastscored_src = src;
  
    // For debugging only. Not thread-safe
    prior_lang = UNKNOWN_LANGUAGE;
    prior_unreliable = false;
  
    // Break text up into multiple chunks and score each
    while (src < srclimit) {
      // Updates tote_grams
      int len = cld::DoQuadScoreV3(quadgram_obj,
                                   src, srclimit - src, advance_by,
                                   tote_grams, gram_limit, chunk_tote);
      if (FlagUseWords(flags) || (*initial_word_span > 0)) {
        // Use word scoring in addition to quadgrams
        cld::DoOctaScoreV3(&kLongWord8Table_obj,
                           src, len, chunk_tote);
      }
      chunk_tote->AddBytes(len);
      *initial_word_span -= len;
  
      if (*tote_grams >= gram_limit) {
        // Remove all but top40 if asked
        if (FlagTop40(flags)) {
          cld::DemoteNotTop40(chunk_tote, cld::PackLanguage(plus_one));
        }
  
        // Sort, accumulate into doc total, reinit
        ScoreChunkIntoDoc(src, len, advance_by,
                          scriptspan->script, chunk_tote,
                          doc_tote, *tote_grams, lang_hint_boost);
        lastscored_src = src + len;
        *tote_grams = 0;
      } else {
        if (FLAGS_cld_html) {
          string temp(src, len);
          Language top_lang = cld::UnpackLanguage(chunk_tote->CurrentTopKey());
          PrintTopLangSpeculative(top_lang);
          cld::PrintText(stderr, top_lang, temp);
        }
      }
      src += len;
    }
  }
  
  
  
  void PrintLangs(FILE* f, const Language* language3, const int* percent3,
                  const int* text_bytes, const bool* is_reliable) {
    fprintf(f, "<br>&nbsp;&nbsp;Initial_Languages ");
    if (language3[0] != UNKNOWN_LANGUAGE) {
      fprintf(f, "%s%s(%d%%)  ",
              ExtLanguageName(language3[0]),
              *is_reliable ? "" : "*",
              percent3[0]);
    }
    if (language3[1] != UNKNOWN_LANGUAGE) {
      fprintf(f, "%s(%d%%)  ", ExtLanguageName(language3[1]), percent3[1]);
    }
    if (language3[2] != UNKNOWN_LANGUAGE) {
      fprintf(f, "%s(%d%%)  ", ExtLanguageName(language3[2]), percent3[2]);
    }
    fprintf(f, "%d bytes \n", *text_bytes);
  
    fprintf(f, "<br>\n");
  }
  
  
  // Start the tote with a count of one for the default language for script
  void InitScriptToteLang(Tote* script_tote, UnicodeLScript lscript) {
    Language defaultlang = cld::kDefaultLanguagePerLScript[lscript];
    script_tote->Add(cld::PackLanguage(defaultlang), 1);
    script_tote->AddBytes(1);
  #if 0
    if (FLAGS_cld_html) {
      cld::PrintLang(stderr, script_tote,
                defaultlang, false,
                UNKNOWN_LANGUAGE, false);
      prior_lang = cur_lang;
      string temp("+1");
      cld::PrintText(stderr, defaultlang, temp);
    }
  #endif
  }
  
  static const char* const kToteName[4] =
    {"=Latn=", "=Hani=", "=Script2=", "=Script3="};
  static const char* const kToteSwitch[4] =
    {"=Latn=", "=Hani=", "=Switch2=", "=Switch3="};
  
  
  
  // Upper to lower, keep digits, everything else to minus '-' (2d)
  static const char kCharsetToLowerTbl[256] = {
    0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
    0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
    0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
    0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37, 0x38,0x39,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
  
    0x2d,0x61,0x62,0x63,0x64,0x65,0x66,0x67, 0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
    0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77, 0x78,0x79,0x7a,0x2d,0x2d,0x2d,0x2d,0x2d,
    0x2d,0x61,0x62,0x63,0x64,0x65,0x66,0x67, 0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
    0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77, 0x78,0x79,0x7a,0x2d,0x2d,0x2d,0x2d,0x2d,
  
    0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
    0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
    0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
    0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
  
    0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
    0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
    0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
    0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
  };
  
  
  static const char kIsAlpha[256] = {
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,0,0,0,0,0,
    0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,0,0,0,0,0,
  
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
  };
  
  static const char kIsDigit[256] = {
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 1,1,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
  
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
  };
  
  // Normalize ASCII string to first 4 alphabetic/digit chars
  // Letters are forced to lowercase ASCII
  // Used to normalize TLD values
  void MakeChar4(const char* str, char* norm) {
    memcpy(norm, "____", 4);     // four underscores
    int l_ptr = 0;
    for (int i = 0; i < strlen(str); ++i) {
      uint8 uc = static_cast<uint8>(str[i]);
      if (kIsAlpha[uc] | kIsDigit[uc]) {
        if (l_ptr < 4) {                  // Else ignore
          norm[l_ptr] = kCharsetToLowerTbl[uc];
          l_ptr++;
        }
      }
    }
  }
  
  // Find subscript of matching key in first 4 bytes of sorted hint array, or -1
  static int HintBinaryLookup4(const HintEntry* hintprobs, int hintprobssize,
                       const char* norm_key) {
    // Key is always in range [lo..hi)
    int lo = 0;
    int hi = hintprobssize;
    while (lo < hi) {
      int mid = (lo + hi) >> 1;
      int comp = memcmp(&hintprobs[mid].key[0], norm_key, 4);
      if (comp < 0) {
        lo = mid + 1;
      } else if (comp > 0) {
        hi = mid;
      } else {
        return mid;
      }
    }
    return -1;
  }
  
  
  // Increment the initial probabilities based on a per-TLD probs entry
  void ApplyTLDHint(uint8* lang_hint_boost, const char* tld_hint) {
    if (FLAGS_dbgscore) {
      fprintf(stderr, "TLD hint %s\n", tld_hint);
    }
    char normalized_tld[8];
    MakeChar4(tld_hint, normalized_tld);
    int n = HintBinaryLookup4(kTLDHintProbs, kTLDHintProbsSize,
                             normalized_tld);
    // TLD is four bytes, probability entry is 4 bytes
    if (n >= 0) {
      uint32 probs = kTLDHintProbs[n].probs;
  
      uint8 prob123 = (probs >> 0) & 0xff;
      const uint8* prob123_entry = cld::LgProb2TblEntry(prob123);
      uint8 top1 = (probs >> 8) & 0xff;
      if (top1 > 0) {lang_hint_boost[top1] += cld::LgProb3(prob123_entry, 0);}
      uint8 top2 = (probs >> 16) & 0xff;
      if (top2 > 0) {lang_hint_boost[top2] += cld::LgProb3(prob123_entry, 1);}
      uint8 top3 = (probs >> 24) & 0xff;
      if (top3 > 0) {lang_hint_boost[top3] += cld::LgProb3(prob123_entry, 2);}
    }
  }
  
  
  // Increment the initial probabilities based on a per-encoding probs entry
  void ApplyEncodingHint(uint8* lang_hint_boost, int encoding_hint) {
    if (FLAGS_dbgscore) {
      Encoding tempenc = static_cast<Encoding>(encoding_hint);
      fprintf(stderr, "ENC hint %s\n", EncodingName(tempenc));
    }
    if (encoding_hint < ISO_8859_1) {return;}
    if (encoding_hint >= NUM_ENCODINGS) {return;}
    uint32 probs = kEncodingHintProbs[encoding_hint];
  
    uint8 prob123 = (probs >> 0) & 0xff;
    const uint8* prob123_entry = cld::LgProb2TblEntry(prob123);
    uint8 top1 = (probs >> 8) & 0xff;
    if (top1 > 0) {lang_hint_boost[top1] += cld::LgProb3(prob123_entry, 0);}
    uint8 top2 = (probs >> 16) & 0xff;
    if (top2 > 0) {lang_hint_boost[top2] += cld::LgProb3(prob123_entry, 1);}
    uint8 top3 = (probs >> 24) & 0xff;
    if (top3 > 0) {lang_hint_boost[top3] += cld::LgProb3(prob123_entry, 2);}
  }
  
  
  // Increment the initial probability for given language by fixed amount
  // Does not recognize extended languages as hints
  void ApplyLanguageHint(uint8* lang_hint_boost, Language language_hint) {
    if (FLAGS_dbgscore) {
      fprintf(stderr, "LANG hint %s\n", ExtLanguageName(language_hint));
    }
    if (language_hint < ENGLISH) {return;}
    if (language_hint >= NUM_LANGUAGES) {return;}
    uint32 probs = kLanguageHintProbs[language_hint];
  
    uint8 prob123 = (probs >> 0) & 0xff;
    const uint8* prob123_entry = cld::LgProb2TblEntry(prob123);
    uint8 top1 = (probs >> 8) & 0xff;
    if (top1 > 0) {lang_hint_boost[top1] += cld::LgProb3(prob123_entry, 0);}
    uint8 top2 = (probs >> 16) & 0xff;
    if (top2 > 0) {lang_hint_boost[top2] += cld::LgProb3(prob123_entry, 1);}
    uint8 top3 = (probs >> 24) & 0xff;
    if (top3 > 0) {lang_hint_boost[top3] += cld::LgProb3(prob123_entry, 2);}
  }
  
  // Extract return values before fixups
  void ExtractLangEtc(ToteWithReliability* doc_tote, int total_text_bytes,
                      int* reliable_percent3, Language* language3, int* percent3,
                      double*  normalized_score3,
                      int* text_bytes, bool* is_reliable) {
    reliable_percent3[0] = 0;
    reliable_percent3[1] = 0;
    reliable_percent3[2] = 0;
    language3[0] = UNKNOWN_LANGUAGE;
    language3[1] = UNKNOWN_LANGUAGE;
    language3[2] = UNKNOWN_LANGUAGE;
    percent3[0] = 100;
    percent3[1] = 0;
    percent3[2] = 0;
    normalized_score3[0] = 0.0;
    normalized_score3[1] = 0.0;
    normalized_score3[2] = 0.0;
  
    *text_bytes = total_text_bytes;
    *is_reliable = false;
  
    int bytecount1 = total_text_bytes;
    int bytecount2 = 0;
    int bytecount3 = 0;
  
    int lang1 = doc_tote->Key(0);
    if (lang1 != 0) {
      // We have a top language
      language3[0] = cld::UnpackLanguage(lang1);
      bytecount1 = doc_tote->Value(0);
      int reli1 = doc_tote->Reliability(0);
      reliable_percent3[0] = reli1 / (bytecount1 ? bytecount1 : 1);  // avoid zdiv
      normalized_score3[0] = cld::GetNormalizedScore(language3[0],
                                                    ULScript_Common,
                                                    bytecount1,
                                                    doc_tote->Score(0));
    }
  
    int lang2 = doc_tote->Key(1);
    if (lang2 != 0) {
      language3[1] = cld::UnpackLanguage(lang2);
      bytecount2 = doc_tote->Value(1);
      int reli2 = doc_tote->Reliability(1);
      reliable_percent3[1] = reli2 / (bytecount2 ? bytecount2 : 1);  // avoid zdiv
      normalized_score3[1] = cld::GetNormalizedScore(language3[1],
                                                    ULScript_Common,
                                                    bytecount2,
                                                    doc_tote->Score(1));
    }
  
    int lang3 = doc_tote->Key(2);
    if (lang3 != 0) {
      language3[2] = cld::UnpackLanguage(lang3);
      bytecount3 = doc_tote->Value(2);
      int reli3 = doc_tote->Reliability(2);
      reliable_percent3[2] = reli3 / (bytecount3 ? bytecount3 : 1);  // avoid zdiv
      normalized_score3[2] = cld::GetNormalizedScore(language3[2],
                                                    ULScript_Common,
                                                    bytecount3,
                                                    doc_tote->Score(2));
    }
  
    // Increase total bytes to sum (top 3) if low for some reason
    int total_bytecount12 = bytecount1 + bytecount2;
    int total_bytecount123 = total_bytecount12 + bytecount3;
    if (total_text_bytes < total_bytecount123) {
      total_text_bytes = total_bytecount123;
      *text_bytes = total_text_bytes;
    }
  
    // Sum minus previous % gives better roundoff behavior than bytecount/total
    int total_text_bytes_div = cld::maxint(1, total_text_bytes);    // Avoid zdiv
    percent3[0] = (bytecount1 * 100) / total_text_bytes_div;
    percent3[1] = (total_bytecount12 * 100) / total_text_bytes_div;
    percent3[2] = (total_bytecount123 * 100) / total_text_bytes_div;
    percent3[2] -= percent3[1];
    percent3[1] -= percent3[0];
  
    // Roundoff, say 96% 1.6% 1.4%, will produce non-obvious 96% 1% 2%
    // Fix this explicitly
    if (percent3[1] < percent3[2]) {
      ++percent3[1];
      --percent3[2];
    }
    if (percent3[0] < percent3[1]) {
      ++percent3[0];
      --percent3[1];
    }
  
    *text_bytes = total_text_bytes;
  
    if (lang1 != 0) {
      // We have a top language
      // Its reliability is overal result reliability
      int bytecount = doc_tote->Value(0);
      int reli = doc_tote->Reliability(0);
      int reliable_percent = reli / (bytecount ? bytecount : 1);  // avoid zdiv
      *is_reliable = reliable_percent >= cld::kMinReliable;
    } else {
      // No top language at all. This can happen with zero text or 100% Klingon
      // if extended=false. Just return all UNKNOWN_LANGUAGE, reliable.
      *is_reliable = true;
    }
  }
  
  bool IsFIGS(Language lang) {
    if (lang == FRENCH) {return true;}
    if (lang == ITALIAN) {return true;}
    if (lang == GERMAN) {return true;}
    if (lang == SPANISH) {return true;}
    return false;
  }
  
  bool IsEFIGS(Language lang) {
    if (lang == ENGLISH) {return true;}
    if (lang == FRENCH) {return true;}
    if (lang == ITALIAN) {return true;}
    if (lang == GERMAN) {return true;}
    if (lang == SPANISH) {return true;}
    return false;
  }
  
  static const int kNonEnBoilerplateMinPercent = 17;    // <this => no second
  static const int kNonFIGSBoilerplateMinPercent = 20;  // <this => no second
  static const int kGoodFirstMinPercent = 26;           // <this => UNK
  static const int kGoodFirstReliableMinPercent = 51;   // <this => unreli
  static const int kIgnoreMaxPercent = 95;              // >this => unreli
  static const int kKeepMinPercent = 2;                 // <this => unreli
  
  // For Tier3 languages, require more bytes of text to override
  // the first-place language
  static const int kGoodSecondT1T2MinBytes = 15;        // <this => no second
  static const int kGoodSecondT3MinBytes = 128;         // <this => no second
                                                        //
  
  // Calculate a single summary language for the document, and its reliability.
  // Returns language3[0] or language3[1] or ENGLISH or UNKNOWN_LANGUAGE
  // This is the heart of matching human-rater perception.
  // reliable_percent3[] is currently unused
  //
  // Do not return Tier3 second language unless there are at least 128 bytes
  void CalcSummaryLang(ToteWithReliability* doc_tote, int total_text_bytes,
                       const int* reliable_percent3,
                       const Language* language3,
                       const int* percent3,
                       Language* summary_lang, bool* is_reliable) {
    // Vector of active languages; changes if we delete some
    int slot_count = 3;
    int active_slot[3] = {0, 1, 2};
  
    int ignore_percent = 0;
    int return_percent = percent3[0];   // Default to top lang
    *summary_lang = language3[0];
    *is_reliable = true;
    if (percent3[0] < kKeepMinPercent) {*is_reliable = false;}
  
    // If any of top 3 is IGNORE, remove it and increment ignore_percent
    for (int i = 0; i < 3; ++i) {
      if (language3[i] == TG_UNKNOWN_LANGUAGE) {
        ignore_percent += percent3[i];
        // Move the rest up, levaing input vectors unchanged
        for (int j=i+1; j < 3; ++j) {
          active_slot[j - 1] = active_slot[j];
        }
        -- slot_count;
        // Logically remove Ignore from percentage-text calculation
        // (extra 1 in 101 avoids zdiv, biases slightly small)
        return_percent = (percent3[0] * 100) / (101 - ignore_percent);
        *summary_lang = language3[active_slot[0]];
        if (percent3[active_slot[0]] < kKeepMinPercent) {*is_reliable = false;}
      }
    }
  
  
    // If English and X, where X (not UNK) is big enough,
    // assume the English is boilerplate and return X.
    // Logically remove English from percentage-text calculation
    int second_bytes = (total_text_bytes * percent3[active_slot[1]]) / 100;
    // Require more bytes of text for Tier3 languages
    int minbytesneeded = kGoodSecondT1T2MinBytes;
    int plang_second =  cld::PackLanguage(language3[active_slot[1]]);
    bool is_tier3 = (cld::kIsPackedTop40[plang_second] == 0);
    if (is_tier3) {
      minbytesneeded = kGoodSecondT3MinBytes;
    }
  
    if ((language3[active_slot[0]] == ENGLISH) &&
        (language3[active_slot[1]] != ENGLISH) &&
        (language3[active_slot[1]] != UNKNOWN_LANGUAGE) &&
        (percent3[active_slot[1]] >= kNonEnBoilerplateMinPercent) &&
        (second_bytes >= minbytesneeded)) {
      ignore_percent += percent3[active_slot[0]];
      return_percent = (percent3[active_slot[1]] * 100) / (101 - ignore_percent);
      *summary_lang = language3[active_slot[1]];
      if (percent3[active_slot[1]] < kKeepMinPercent) {*is_reliable = false;}
  
    // Else If FIGS and X, where X (not UNK, EFIGS) is big enough,
    // assume the FIGS is boilerplate and return X.
    // Logically remove FIGS from percentage-text calculation
    } else if (IsFIGS(language3[active_slot[0]]) &&
               !IsEFIGS(language3[active_slot[1]]) &&
               (language3[active_slot[1]] != UNKNOWN_LANGUAGE) &&
               (percent3[active_slot[1]] >= kNonFIGSBoilerplateMinPercent) &&
               (second_bytes >= minbytesneeded)) {
      ignore_percent += percent3[active_slot[0]];
      return_percent = (percent3[active_slot[1]] * 100) / (101 - ignore_percent);
      *summary_lang = language3[active_slot[1]];
      if (percent3[active_slot[1]] < kKeepMinPercent) {*is_reliable = false;}
  
    // Else we are returning the first language, but want to improve its
    // return_percent if the second language should be ignored
    } else  if ((language3[active_slot[1]] == ENGLISH) &&
                (language3[active_slot[0]] != ENGLISH)) {
      ignore_percent += percent3[active_slot[1]];
      return_percent = (percent3[active_slot[0]] * 100) / (101 - ignore_percent);
    } else  if (IsFIGS(language3[active_slot[1]]) &&
                !IsEFIGS(language3[active_slot[0]])) {
      ignore_percent += percent3[active_slot[1]];
      return_percent = (percent3[active_slot[0]] * 100) / (101 - ignore_percent);
    }
  
    // If return percent is too small (too many languages), return UNKNOWN
    if ((return_percent < kGoodFirstMinPercent)) {
      *summary_lang = UNKNOWN_LANGUAGE;
      *is_reliable = false;
    }
  
    // If return percent is small, return language but set unreliable.
    if ((return_percent < kGoodFirstReliableMinPercent)) {
      *is_reliable = false;
    }
  
    // If ignore percent is too large, set unreliable.
    if ((ignore_percent > kIgnoreMaxPercent)) {
      *is_reliable = false;
    }
  
    // If we removed all the active languages, return UNKNOWN
    if (slot_count == 0) {
      *summary_lang = UNKNOWN_LANGUAGE;
      *is_reliable = false;
    }
  }
  
  
  
  // Result vector must be exactly three items
  Language CompactLangDetImpl::DetectLanguageSummaryV25(
                          const CompactLangDet::DetectionTables* tables,
                          const char* buffer,
                          int buffer_length,
                          bool is_plain_text,
                          bool do_pick_summary_language,
                          bool do_remove_weak_matches,
                          const char* tld_hint,       // "id" boosts Indonesian
                          int encoding_hint,          // SJS boosts Japanese
                          Language language_hint,     // ITALIAN boosts it
                          bool allow_extended_lang,
                          int flags,
                          Language plus_one,
                          Language* language3,
                          int* percent3,
                          double* normalized_score3,
                          int* text_bytes,
                          bool* is_reliable) {
    if (!tables) {
      static const CompactLangDet::DetectionTables default_cld_tables = {
        &kQuadTable_obj,
        &compact_lang_det_generated_ctjkvz_b1_obj
      };
      tables = &default_cld_tables;
    }
    language3[0] = UNKNOWN_LANGUAGE;
    language3[1] = UNKNOWN_LANGUAGE;
    language3[2] = UNKNOWN_LANGUAGE;
    percent3[0] = 100;
    percent3[1] = 0;
    percent3[2] = 0;
    normalized_score3[0] = 0.0;
    normalized_score3[1] = 0.0;
    normalized_score3[2] = 0.0;
    *text_bytes = 0;
    *is_reliable = false;
  
    // Document totals
    ToteWithReliability doc_tote;   // Reliability = 0..100
  
    // Vector of packed per-language boosts (just one filled in from hints)
    uint8 lang_hint_boost[EXT_NUM_LANGUAGES + 1];
    memset(lang_hint_boost, 0, sizeof(lang_hint_boost));
  
    // Apply hints,if any
    if ((tld_hint != NULL) && (tld_hint[0] != '\0')) {
      ApplyTLDHint(lang_hint_boost, tld_hint);
    }
    if (encoding_hint != UNKNOWN_ENCODING) {
      ApplyEncodingHint(lang_hint_boost, encoding_hint);
    }
    if (language_hint != UNKNOWN_LANGUAGE) {
      ApplyLanguageHint(lang_hint_boost, language_hint);
    }
  
  
    // Four individual script totals, Latin, Han, other2, other3
    int next_other_tote = 2;
  
    // Four totes for up to four different scripts pending at once
    Tote totes[4];                  // [0] Latn  [1] Hani  [2] other  [3] other
    bool tote_seen[4] = {false, false, false, false};
    int tote_grams[4] = {0, 0, 0, 0};     // Number in partial chunk
    UnicodeLScript tote_script[4] =
      {ULScript_Latin, ULScript_HanCJK, ULScript_Common, ULScript_Common};
  
    // Loop through text spans in a single script
    ScriptScanner ss(buffer, buffer_length, is_plain_text);
    getone::LangSpan scriptspan;
  
    scriptspan.text = NULL;
    scriptspan.text_bytes = 0;
    scriptspan.offset = 0;
    scriptspan.script = ULScript_Common;
    scriptspan.lang = UNKNOWN_LANGUAGE;
  
    int total_text_bytes = 0;
    int textlimit = FLAGS_cld_textlimit << 10;    // in KB
    if (textlimit == 0) {textlimit = 0x7fffffff;}
  
    int advance_by = 2;                   // Advance 2 bytes
    int advance_limit = textlimit >> 3;   // For first 1/8 of max document
  
    int initial_word_span = kDefaultWordSpan;
    if (FLAGS_cld_forcewords) {
      initial_word_span = kReallyBigWordSpan;
    }
  
    // Pick up chunk sizes
    // Smoothwidth is units of quadgrams, about 2.5 chars (unigrams) each
    // Sanity check -- force into a reasonable range
    int chunksizequads = FLAGS_cld_smoothwidth;
    chunksizequads = cld::minint(cld::maxint(chunksizequads, kMinChunkSizeQuads),
                                 kMaxChunkSizeQuads);
    int chunksizeunis = (chunksizequads * 5) >> 1;
  
    // Varying short-span limit doesn't work well -- skips too much beyond 20KB
    // int spantooshortlimit = advance_by * FLAGS_cld_smoothwidth;
    int spantooshortlimit = kShortSpanThresh;
  
    // For debugging only. Not thread-safe
    prior_lang = UNKNOWN_LANGUAGE;
    prior_unreliable = false;
  
    // Allocate full-document prediction table for finding repeating words
    int hash = 0;
    int* predict_tbl = new int[kPredictionTableSize];
    if (FlagRepeats(flags)) {
      memset(predict_tbl, 0, kPredictionTableSize * sizeof(predict_tbl[0]));
    }
  
    // Loop through scriptspans accumulating number of text bytes in each language
    while (ss.GetOneScriptSpanLower(&scriptspan)) {
      UnicodeLScript lscript = scriptspan.script;
  
      // Echo text if asked to
      if (FLAGS_cld_echotext) {
        PrintHtmlEscapedText(stderr, scriptspan.text, scriptspan.text_bytes);
      }
  
      // Squeeze out big chunks of text span if asked to
      if (FlagSqueeze(flags)) {
        // Remove repetitive or mostly-spaces chunks
        int newlen;
        int chunksize = 0;    // Use the default
        newlen = CheapSqueezeInplace(scriptspan.text, scriptspan.text_bytes,
                                     chunksize);
        scriptspan.text_bytes = newlen;
      } else {
        // Check now and then to see if we should be squeezing
        if ((total_text_bytes >= kCheapSqueezeTestThresh) &&
            !FlagFinish(flags) &&
            ((getone::kMaxScriptBuffer >> 1) < scriptspan.text_bytes) &&
            CheapSqueezeTriggerTest(scriptspan.text,
                                      scriptspan.text_bytes,
                                      kCheapSqueezeTestLen)) {
          // Recursive call with big-chunk squeezing set
          if (FLAGS_cld_html || FLAGS_dbgscore) {
            fprintf(stderr,
                    "<br>---text_bytes[%d] Recursive(Squeeze)---<br><br>\n",
                    total_text_bytes);
          }
          // Deallocate full-document prediction table
          delete[] predict_tbl;
  
          return DetectLanguageSummaryV25(
                            tables,
                            buffer,
                            buffer_length,
                            is_plain_text,
                            do_pick_summary_language,
                            do_remove_weak_matches,
                            tld_hint,               // "id" boosts Indonesian
                            encoding_hint,          // SJS boosts Japanese
                            language_hint,          // ITALIAN boosts it
                            allow_extended_lang,
                            flags | kCLDFlagSqueeze,
                            plus_one,
                            language3,
                            percent3,
                            normalized_score3,
                            text_bytes,
                            is_reliable);
        }
      }
  
      // Remove repetitive words if asked to
      if (FlagRepeats(flags)) {
        // Remove repetitive words
        int newlen;
        newlen = CheapRepWordsInplace(scriptspan.text, scriptspan.text_bytes,
                                      &hash, predict_tbl);
        scriptspan.text_bytes = newlen;
      }
  
      // The real scoring
      // Accumulate directly into the document total, or accmulate in one of four
      // chunk totals. The purpose of the multiple chunk totals is to piece
      // together short choppy pieces of text in alternating scripts. One total is
      // dedicated to Latin text, one to Han text, and the other two are dynamicly
      // assigned.
      Language onlylang = cld::kOnlyLanguagePerLScript[lscript];
  
      if (onlylang != UNKNOWN_LANGUAGE) {
        // This entire script run is in a single language.
        ScoreNilgrams(&scriptspan, cld::PackLanguage(onlylang), &doc_tote,
                     lang_hint_boost, flags, plus_one);
      } else if (cld::kScoreUniPerLScript[lscript] != 0) {
        // This entire script run's languages can be distinguished by uni-grams
        // Accumulate in hani_tote
        int tote_num = 1;
        if (!tote_seen[tote_num]) {
          tote_seen[tote_num] = true;
          // Default language gets 1 byte
          total_text_bytes += 1;
          InitScriptToteLang(&totes[tote_num], lscript);
        }
        ScoreUnigrams(tables->unigram_obj,
                         &scriptspan, &tote_grams[tote_num], chunksizeunis,
                         &totes[tote_num],
                         &doc_tote, lang_hint_boost,
                         advance_by, flags, &initial_word_span, plus_one);
      } else {
        // This entire script-run's languages can be distinguished by quad-grams
        // Accumulate in latn_tote or script0/1_tote
        int tote_num = -1;
        for (int t = 0; t < 4; ++t) {
          if (lscript == tote_script[t]) {
            tote_num = t;
            break;
          }
        }
        if (tote_num < 0) {
          // Need to allocate other0/1
          tote_num = next_other_tote;
          next_other_tote ^= 1;     // Round-robin
          if (tote_seen[tote_num]) {
            // Flush previous
            ScoreChunkIntoDoc2(kToteSwitch[tote_num], advance_by,
                               tote_script[tote_num], &totes[tote_num],
                               &doc_tote, tote_grams[tote_num], lang_hint_boost);
            totes[tote_num].Reinit();
          }
          tote_script[tote_num] = lscript;
        }
  
        if (!tote_seen[tote_num]) {
          tote_seen[tote_num] = true;
          // Default language gets 1 byte
          total_text_bytes += 1;
          InitScriptToteLang(&totes[tote_num], lscript);
        }
  
        // The actual accumulation, possibly with word scoring also
        ScoreQuadgrams(tables->quadgram_obj, &scriptspan, &tote_grams[tote_num],
                          chunksizequads,
                          &totes[tote_num],
                          &doc_tote, lang_hint_boost,
                          advance_by, flags, &initial_word_span, plus_one);
      }
  
      total_text_bytes += scriptspan.text_bytes;
  
      // For long documents, do less-dense samples the further along we go.
      // This is to keep speed sublinear in document size.
      if (total_text_bytes > advance_limit) {
        if (total_text_bytes > textlimit) {
          // Don't look at rest of doc
          if (FLAGS_cld_html || FLAGS_dbgscore) {
            fprintf(stderr, "<br>---text_bytes[%d] textlimit %d reached---<br>",
                    total_text_bytes, textlimit);
          }
          break;
        }
        advance_by <<= 1;         // Double advance bytes
        advance_limit <<= 1;      // Double limit until next change
        spantooshortlimit <<= 1;  // Double short-span size
        if (FLAGS_cld_html || FLAGS_dbgscore) {
          fprintf(stderr, "<br>---text_bytes[%d] advance_by doubled to %d---<br>",
                  total_text_bytes, advance_by);
        }
      }
    }     // End while (ss.GetOneScriptSpanLower())
  
    // Deallocate full-document prediction table
    delete[] predict_tbl;
  
    // Flush pending totals
    for (int tote_num = 0; tote_num < 4; ++tote_num) {
      if (tote_seen[tote_num]) {
        ScoreChunkIntoDoc2(kToteName[tote_num], advance_by,
                           tote_script[tote_num], &totes[tote_num], &doc_tote,
                           tote_grams[tote_num], lang_hint_boost);
      }
    }
  
    // If extended languages are disallowed, remove them here
    if (!allow_extended_lang) {
      RemoveExtendedLanguages(&doc_tote);
    }
  
    // Force close pairs to one or the other
    RefineScoredClosePairs(&doc_tote);
  
  
    // Calculate return results
    // Find top three byte counts in tote heap
    int reliable_percent3[3];
  
  
    // Cannot use Add, etc. after sorting
    doc_tote.Sort(3);
  
    ExtractLangEtc(&doc_tote, total_text_bytes,
                   reliable_percent3, language3, percent3, normalized_score3,
                   text_bytes, is_reliable);
  
    bool have_good_answer = false;
    if (FlagFinish(flags)) {
      // Force a result
      have_good_answer = true;
    } else if (total_text_bytes <= kShortTextThresh) {
      // Don't recurse on short text -- we already did word scores
      have_good_answer = true;
    } else if (*is_reliable &&
               (percent3[0] >= kGoodLang1Percent)) {
      have_good_answer = true;
    } else if (*is_reliable &&
               ((percent3[0] + percent3[1]) >= kGoodLang1and2Percent)) {
      have_good_answer = true;
    }
  
  
    if (have_good_answer) {
      // This is the real, non-recursive return
  
      // Move bytes for unreliable langs to another lang or
      // UNKNOWN
      RemoveUnreliableLanguages(&doc_tote, do_remove_weak_matches);
  
      // Redo the result extraction after the removal above
      doc_tote.Sort(3);
  
      ExtractLangEtc(&doc_tote, total_text_bytes,
                     reliable_percent3, language3, percent3, normalized_score3,
                     text_bytes, is_reliable);
  
  #if 0
      // OLD code, replaced by CalcSummaryLang
      //
      // Suppress ignore-me text, TG_UNKNOWN_LANGUAGE if 2nd or 3rd language
      // Force it to English if first language
      if (language3[2] == TG_UNKNOWN_LANGUAGE) {
        reliable_percent3[2] = 0;
        language3[2] = UNKNOWN_LANGUAGE;
        percent3[2] = 0;
      } else if (language3[1] == TG_UNKNOWN_LANGUAGE) {
        // Move up lower language
        reliable_percent3[1] = reliable_percent3[2];
        language3[1] = language3[2];
        percent3[1] = percent3[2];
        reliable_percent3[2] = 0;
        language3[2] = UNKNOWN_LANGUAGE;
        percent3[2] = 0;
      } else if (language3[0] == TG_UNKNOWN_LANGUAGE) {
        language3[0] = ENGLISH;
      }
  
      if (language3[0] == UNKNOWN_LANGUAGE) {
        // Last-ditch test for some result, but it is UNKNOWN_LANGUAGE
        // Force it to English (should not happen)
        language3[0] = ENGLISH;
        percent3[0] = 100;
        *is_reliable = true;
      }
  #endif
  
  
  #if 0
      // Scaffolding to reveal subset sequence lang distribution across doc text
      // Track the sequence of language fragments [result currently unused]
      if (FLAGS_cld_html) {
        static const int kMaxSubsetSeq = 12;
        uint8 subseq[kMaxSubsetSeq];
        doc_tote.ExtractSeq(kMaxSubsetSeq, subseq);
  
        fprintf(stderr, "<br>\nSubset Sequence[%d]: ", kMaxSubsetSeq);
        for (int i = 0; i < kMaxSubsetSeq; ++i) {
          fprintf(stderr, "%s ", ExtLanguageCode(cld::UnpackLanguage(subseq[i])));
          if ((i % 4) == 3) {fprintf(stderr, "&nbsp; ");}
        }
        fprintf(stderr, "&nbsp;&nbsp; ");
  
        for (int i = 0; i < 3; ++i) {
          if (language3[i] != UNKNOWN_LANGUAGE) {
            fprintf(stderr, "%s.%d(%d%%) ",
                    ExtLanguageCode(language3[i]),
                    reliable_percent3[i],
                    percent3[i]);
          }
        }
  
        fprintf(stderr, "%d B ", total_text_bytes);
        fprintf(stderr, "<br>\n");
      }
      // End Scaffolding to reveal subset sequence lang distribution
  #endif
  
      Language summary_lang;
      if (do_pick_summary_language) {
        CalcSummaryLang(&doc_tote, total_text_bytes,
                        reliable_percent3, language3, percent3,
                        &summary_lang, is_reliable);
      } else {
        summary_lang = language3[0];
      }
  
      if (FLAGS_cld_html) {
        for (int i = 0; i < 3; ++i) {
          if (language3[i] != UNKNOWN_LANGUAGE) {
            fprintf(stderr, "%s.%d(%d%%) ",
                    ExtLanguageCode(language3[i]),
                    reliable_percent3[i],
                    percent3[i]);
          }
        }
  
        fprintf(stderr, "%d B ", total_text_bytes);
        fprintf(stderr, "= %s%c ",
                ExtLanguageName(summary_lang), is_reliable ? ' ' : '*');
        fprintf(stderr, "<br>\n");
      }
  
      return summary_lang;
    }
  
    // Not a good answer -- do recursive call to refine
    if (FLAGS_cld_html || FLAGS_dbgscore) {
      // This is what we hope to improve on in the recursive call, if any
      PrintLangs(stderr, language3, percent3, text_bytes, is_reliable);
    }
  
    // For restriction to Top40 + one, the one is 1st/2nd lang that is not Top40
    // For this purpose, we treate "Ignore" as top40
    Language new_plus_one = UNKNOWN_LANGUAGE;
    if (cld::kIsPackedTop40[cld::PackLanguage(language3[0])] == 0) {
      new_plus_one = language3[0];
    } else if (cld::kIsPackedTop40[cld::PackLanguage(language3[1])] == 0) {
      new_plus_one = language3[1];
    }
  
    if (total_text_bytes < kShortTextThresh) {
        // Short text: Recursive call with top40 and short set
        if (FLAGS_cld_html || FLAGS_dbgscore) {
          fprintf(stderr, "&nbsp;&nbsp;---text_bytes[%d] "
                  "Recursive(Top40/Rep/Short/Words)---<br><br>\n",
                  total_text_bytes);
        }
        return DetectLanguageSummaryV25(
                          tables,
                          buffer,
                          buffer_length,
                          is_plain_text,
                          do_pick_summary_language,
                          do_remove_weak_matches,
                          tld_hint,               // "id" boosts Indonesian
                          encoding_hint,          // SJS boosts Japanese
                          language_hint,          // ITALIAN boosts it
                          allow_extended_lang,
                          flags | kCLDFlagTop40 | kCLDFlagRepeats |
                            kCLDFlagShort | kCLDFlagUseWords | kCLDFlagFinish,
                          new_plus_one,
                          language3,
                          percent3,
                          normalized_score3,
                          text_bytes,
                          is_reliable);
    }
  
    // Longer text: Recursive call with top40 set
    if (FLAGS_cld_html || FLAGS_dbgscore) {
      fprintf(stderr,
              "&nbsp;&nbsp;---text_bytes[%d] Recursive(Top40/Rep)---<br><br>\n",
              total_text_bytes);
    }
    return DetectLanguageSummaryV25(
                          tables,
                          buffer,
                          buffer_length,
                          is_plain_text,
                          do_pick_summary_language,
                          do_remove_weak_matches,
                          tld_hint,               // "id" boosts Indonesian
                          encoding_hint,          // SJS boosts Japanese
                          language_hint,          // ITALIAN boosts it
                          allow_extended_lang,
                          flags | kCLDFlagTop40 | kCLDFlagRepeats |
                            kCLDFlagFinish,
                          new_plus_one,
                          language3,
                          percent3,
                          normalized_score3,
                          text_bytes,
                          is_reliable);
  }   // End CompactLangDetImpl::DetectLanguageSummaryV25
  