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

package nu.validator.servlet;

import java.io.IOException;
import java.text.DecimalFormat;

import javax.servlet.http.HttpServletResponse;

import nu.validator.htmlparser.sax.HtmlSerializer;
import nu.validator.xml.EmptyAttributes;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

public class Statistics {

    public static final Statistics STATISTICS;

    private static final char[] VALIDATOR_STATISTICS = "Validator statistics".toCharArray();

    private static final char[] COUNTER_NAME = "Counter".toCharArray();

    private static final char[] COUNTER_VALUE = "Value".toCharArray();

    private static final char[] COUNTER_PROPORTION = "Proportion".toCharArray();

    private static final char[] TOTAL_VALIDATIONS = "Total number of validations".toCharArray();

    private static final char[] UPTIME_DAYS = "Uptime in days".toCharArray();

    private static final char[] VALIDATIONS_PER_SECOND = "Validations per second".toCharArray();

    private static final char[] SORT_LANGS_SCRIPT = (""
            + " var rows = document.querySelectorAll('tr');"
            + " var langRows = new Array();"
            + " for (var i=0; i < rows.length; i++) { var row = rows[i];"
            + "   if (row.textContent.indexOf('Detected language') > -1) {"
            + "     var sortnr = parseInt(row.cells[1].textContent"
            + "       || row.cells[0].innerText);"
            + "     if (sortnr == 0) {"
            + "       row.remove();"
            + "     } else if (!isNaN(sortnr)) {"
            + "       langRows.push([sortnr, row]);"
            + "     }"
            + "   }"
            + " } langRows.sort(function(x,y) { return x[0] - y[0]; });"
            + " langRows.reverse();"
            + " for (var i=0; i<langRows.length; i++) {"
            + "   document.querySelector('tbody').appendChild(langRows[i][1]);"
            + " }"
            + " var langValRows = new Array();"
            + " for (var i=0; i < rows.length; i++) { var row = rows[i];"
            + "   if (row.textContent.indexOf('<html lang>: ') > -1) {"
            + "     var sortnr = parseInt(row.cells[1].textContent"
            + "       || row.cells[0].innerText);"
            + "     if (sortnr == 0) {"
            + "       row.remove();"
            + "     } else if (!isNaN(sortnr)) {"
            + "       langValRows.push([sortnr, row]);"
            + "     }"
            + "   }"
            + " } langValRows.sort(function(x,y) { return x[0] - y[0]; });"
            + " langValRows.reverse();"
            + " for (var i=0; i<langValRows.length; i++) {"
            + "   document.querySelector('tbody').appendChild(langValRows[i][1]);"
            + " }").toCharArray();

    private static final char[] STYLESHEET = (""
            + " body { font-family: sans-serif; }"
            + " td { padding: 4px 8px 4px 8px; }"
            + " tr:nth-child(even) { background-color: #cde; }").toCharArray();

    public enum Field {
        // @formatter:off
        INPUT_GET("Input: GET-based"), //
        INPUT_POST("Input: POST-based"), //
        INPUT_ENTITY_BODY("\u2514 Entity-body input"), //
        INPUT_TEXT_FIELD("\u2514 Text-field input"), //
        INPUT_FILE_UPLOAD("\u2514 File-upload input"), //
        INPUT_HTML("Input: text/html"), //
        INPUT_CSS("Input: text/css"), //
        INPUT_SVG("Input: image/svg+xml"), //
        INPUT_UNSUPPORTED("Input: unsupported content type (error)"), //
        INPUT_XML("Input: application/xhtml+xml or other XML content type"), //
        OUTPUT_XML("Output: XML"), //
        OUTPUT_HTML("Output: HTML"), //
        OUTPUT_JSON("Output: JSON"), //
        OUTPUT_GNU("Output: GNU"), //
        OUTPUT_TEXT("Output: Text"), //
        OUTPUT_XHTML("Output: XHTML"), //
        SHOW_SOURCE("Show: source"), //
        SHOW_OUTLINE("Show: outline"), //
        IMAGE_REPORT("Show: image report"), //
        HTML5_SCHEMA("Schema: HTML5 schema"), //
        HTML5_RDFA_LITE_SCHEMA("Schema: HTML5+RDFa Lite schema"), //
        XHTML1_DOCTYPE("Doctype: XHTML1"), //
        HTML4_DOCTYPE("Doctype: HTML4"), //
        ABOUT_LEGACY_COMPAT("Doctype: about:legacy-compat"), //
        AUTO_SCHEMA("Schema: automatically chosen"), //
        PRESET_SCHEMA("Schema: preset"), //
        HTML4_STRICT_SCHEMA("Schema: legacy Strict"), //
        HTML4_TRANSITIONAL_SCHEMA("Schema: legacy Transitional"), //
        HTML4_FRAMESET_SCHEMA("Schema: legacy Frameset"), //
        XHTML1_COMPOUND_SCHEMA("Schema: legacy XHTML+SVG+MathML"), //
        SVG_SCHEMA("Schema: SVG"), //
        BUILT_IN_NON_PRESET("Schema: custom combined from built-ins"), //
        EXTERNAL_SCHEMA_NON_SCHEMATRON("Schema: non-schematron custom"), //
        EXTERNAL_SCHEMA_SCHEMATRON("Schema: schematron custom"), //
        LAX_TYPE("Content type: being lax"), //
        CUSTOM_ENC("Encoding: manually set"), //
        PARSER_XML_EXTERNAL("Parser: set to XML with external entities"), //
        XMLNS_FILTER("Parser: XMLNS filter set"), //
        HAS_ERRORS("Conformance: Documents with errors"), //
        HAS_WARNINGS("Conformance: Documents with warnings"), //
        HAS_FATAL_ERRORS("Fatal errors"), //
        LOGIC_ERROR("Logic errors in schema stats"), //
        SELF_CLOSING_TAG_FOUND("self-closing tag found"), //
        ARIA_LABEL_MISUSE_FOUND("aria-label misuse found"), //
        HGROUP_FOUND("<hgroup> found"), //
        STYLE_ELEMENT_ERRORS_FOUND("<style> element with CSS errors found"), //
        STYLE_ATTRIBUTE_ERRORS_FOUND("\"style\" attribute with CSS errors found"), //
        LANG_FOUND("<html lang> found"), //
        LANG_WRONG("<html lang> does not match detected language"), //
        LANG_EMPTY("<html lang>: empty (lang=\"\")"), //
        LANG_OTHER("<html lang>: other (unrecognized/invalid)"), //
        LANG_AF("<html lang>: af"), //
        LANG_AM("<html lang>: am"), //
        LANG_AR("<html lang>: ar"), //
        LANG_AR_AA("<html lang>: ar-aa"), //
        LANG_AR_AE("<html lang>: ar-ae"), //
        LANG_AR_AR("<html lang>: ar-ar"), //
        LANG_AR_DZ("<html lang>: ar-dz"), //
        LANG_AR_EG("<html lang>: ar-eg"), //
        LANG_AR_JO("<html lang>: ar-jo"), //
        LANG_AR_KW("<html lang>: ar-kw"), //
        LANG_AR_OM("<html lang>: ar-om"), //
        LANG_AR_QA("<html lang>: ar-qa"), //
        LANG_AR_SA("<html lang>: ar-sa"), //
        LANG_AR_SY("<html lang>: ar-sy"), //
        LANG_AT("<html lang>: at"), //
        LANG_AZ("<html lang>: az"), //
        LANG_AZ_AZ("<html lang>: az-az"), //
        LANG_BA("<html lang>: ba"), //
        LANG_BE("<html lang>: be"), //
        LANG_BG("<html lang>: bg"), //
        LANG_BG_BG("<html lang>: bg-bg"), //
        LANG_BN("<html lang>: bn"), //
        LANG_BN_BD("<html lang>: bn-bd"), //
        LANG_BR("<html lang>: br"), //
        LANG_BS("<html lang>: bs"), //
        LANG_BS_BA("<html lang>: bs-ba"), //
        LANG_BS_LATN("<html lang>: bs-latn"), //
        LANG_CA("<html lang>: ca"), //
        LANG_CA_ES("<html lang>: ca-es"), //
        LANG_CE("<html lang>: ce"), //
        LANG_CH("<html lang>: ch"), //
        LANG_CHR("<html lang>: chr"), //
        LANG_CKB("<html lang>: ckb"), //
        LANG_CN("<html lang>: cn"), //
        LANG_CR("<html lang>: cr"), //
        LANG_CS("<html lang>: cs"), //
        LANG_CS_CZ("<html lang>: cs-cz"), //
        LANG_CY("<html lang>: cy"), //
        LANG_CZ("<html lang>: cz"), //
        LANG_DA("<html lang>: da"), //
        LANG_DA_DK("<html lang>: da-dk"), //
        LANG_DE("<html lang>: de"), //
        LANG_DE_AT("<html lang>: de-at"), //
        LANG_DE_CH("<html lang>: de-ch"), //
        LANG_DE_DE("<html lang>: de-de"), //
        LANG_DE__DE("<html lang>: de_de"), //
        LANG_DK("<html lang>: dk"), //
        LANG_EL("<html lang>: el"), //
        LANG_EL_GR("<html lang>: el-gr"), //
        LANG_EN("<html lang>: en"), //
        LANG_ENG("<html lang>: eng"), //
        LANG_EN_AE("<html lang>: en-ae"), //
        LANG_EN_AU("<html lang>: en-au"), //
        LANG_EN__AU("<html lang>: en_au"), //
        LANG_EN_CA("<html lang>: en-ca"), //
        LANG_EN__CA("<html lang>: en_ca"), //
        LANG_EN_DE("<html lang>: en-de"), //
        LANG_EN_EG("<html lang>: en-eg"), //
        LANG_EN_EN("<html lang>: en-en"), //
        LANG_EN__EN("<html lang>: en_en"), //
        LANG_EN_EU("<html lang>: en-eu"), //
        LANG_EN_GB("<html lang>: en-gb"), //
        LANG_EN__GB("<html lang>: en_gb"), //
        LANG_EN_HK("<html lang>: en-hk"), //
        LANG_EN_ID("<html lang>: en-id"), //
        LANG_EN_IE("<html lang>: en-ie"), //
        LANG_EN_IN("<html lang>: en-in"), //
        LANG_EN_MY("<html lang>: en-my"), //
        LANG_EN_NG("<html lang>: en-ng"), //
        LANG_EN_NZ("<html lang>: en-nz"), //
        LANG_EN_PH("<html lang>: en-ph"), //
        LANG_EN_PK("<html lang>: en-pk"), //
        LANG_EN_SG("<html lang>: en-sg"), //
        LANG_EN_UK("<html lang>: en-uk"), //
        LANG_EN_US("<html lang>: en-us"), //
        LANG_EN__US("<html lang>: en_us"), //
        LANG_EN_VN("<html lang>: en-vn"), //
        LANG_EN_ZA("<html lang>: en-za"), //
        LANG_ES("<html lang>: es"), //
        LANG_ES_AR("<html lang>: es-ar"), //
        LANG_ES__AR("<html lang>: es_ar"), //
        LANG_ES_CL("<html lang>: es-cl"), //
        LANG_ES_CO("<html lang>: es-co"), //
        LANG_ES_DO("<html lang>: es-do"), //
        LANG_ES_EC("<html lang>: es-ec"), //
        LANG_ES_ES("<html lang>: es-es"), //
        LANG_ES__ES("<html lang>: es_es"), //
        LANG_ES_LA("<html lang>: es-la"), //
        LANG_ES_MX("<html lang>: es-mx"), //
        LANG_ES_PE("<html lang>: es-pe"), //
        LANG_ES_PR("<html lang>: es-pr"), //
        LANG_ES_US("<html lang>: es-us"), //
        LANG_ES_VE("<html lang>: es-ve"), //
        LANG_ET("<html lang>: et"), //
        LANG_ET_EE("<html lang>: et-ee"), //
        LANG_EU("<html lang>: eu"), //
        LANG_FA("<html lang>: fa"), //
        LANG_FA_IR("<html lang>: fa-ir"), //
        LANG_FA__IR("<html lang>: fa_ir"), //
        LANG_FI("<html lang>: fi"), //
        LANG_FI_FI("<html lang>: fi-fi"), //
        LANG_FO("<html lang>: fo"), //
        LANG_FR("<html lang>: fr"), //
        LANG_FR_BE("<html lang>: fr-be"), //
        LANG_FR_CA("<html lang>: fr-ca"), //
        LANG_FR_CH("<html lang>: fr-ch"), //
        LANG_FR_FR("<html lang>: fr-fr"), //
        LANG_FR__FR("<html lang>: fr_fr"), //
        LANG_FR_MA("<html lang>: fr-ma"), //
        LANG_FR_US("<html lang>: fr-us"), //
        LANG_GA("<html lang>: ga"), //
        LANG_GB("<html lang>: gb"), //
        LANG_GE("<html lang>: ge"), //
        LANG_GL("<html lang>: gl"), //
        LANG_GL_ES("<html lang>: gl-es"), //
        LANG_GR("<html lang>: gr"), //
        LANG_GU("<html lang>: gu"), //
        LANG_HE("<html lang>: he"), //
        LANG_HE_IL("<html lang>: he-il"), //
        LANG_HI("<html lang>: hi"), //
        LANG_HR_HR("<html lang>: hr-hr"), //
        LANG_HU("<html lang>: hu"), //
        LANG_HU_HU("<html lang>: hu-hu"), //
        LANG_HY("<html lang>: hy"), //
        LANG_HY_AM("<html lang>: hy-am"), //
        LANG_ID("<html lang>: id"), //
        LANG_ID_ID("<html lang>: id-id"), //
        LANG_IG("<html lang>: ig"), //
        LANG_IN("<html lang>: in"), //
        LANG_IN_ID("<html lang>: in-id"), //
        LANG_IR("<html lang>: ir"), //
        LANG_IS("<html lang>: is"), //
        LANG_IT("<html lang>: it"), //
        LANG_IT_IT("<html lang>: it-it"), //
        LANG_IT__IT("<html lang>: it_it"), //
        LANG_IU("<html lang>: iu"), //
        LANG_IW("<html lang>: iw"), //
        LANG_JA("<html lang>: ja"), //
        LANG_JA_JP("<html lang>: ja-jp"), //
        LANG_JA__JP("<html lang>: ja_jp"), //
        LANG_JP("<html lang>: jp"), //
        LANG_JV("<html lang>: jv"), //
        LANG_KA("<html lang>: ka"), //
        LANG_KA_GE("<html lang>: ka-ge"), //
        LANG_KK("<html lang>: kk"), //
        LANG_KK_KK("<html lang>: kk-kk"), //
        LANG_KK_KZ("<html lang>: kk-kz"), //
        LANG_KM("<html lang>: km"), //
        LANG_KN("<html lang>: kn"), //
        LANG_KO("<html lang>: ko"), //
        LANG_KO_KR("<html lang>: ko-kr"), //
        LANG_KR("<html lang>: kr"), //
        LANG_KU("<html lang>: ku"), //
        LANG_KY("<html lang>: ky"), //
        LANG_KZ("<html lang>: kz"), //
        LANG_KZ_KZ("<html lang>: kz-kz"), //
        LANG_LANG("<html lang>: lang"), //
        LANG_LO("<html lang>: lo"), //
        LANG_LT("<html lang>: lt"), //
        LANG_LT_LT("<html lang>: lt-lt"), //
        LANG_LV("<html lang>: lv"), //
        LANG_LV_LV("<html lang>: lv-lv"), //
        LANG_MG("<html lang>: mg"), //
        LANG_MHR("<html lang>: mhr"), //
        LANG_MI("<html lang>: mi"), //
        LANG_MK("<html lang>: mk"), //
        LANG_MK_MK("<html lang>: mk-mk"), //
        LANG_ML("<html lang>: ml"), //
        LANG_MN("<html lang>: mn"), //
        LANG_MN_MN("<html lang>: mn-mn"), //
        LANG_MR("<html lang>: mr"), //
        LANG_MRJ("<html lang>: mrj"), //
        LANG_MR_IN("<html lang>: mr-in"), //
        LANG_MS("<html lang>: ms"), //
        LANG_MS_MY("<html lang>: ms-my"), //
        LANG_MT("<html lang>: mt"), //
        LANG_MUL("<html lang>: mul"), //
        LANG_MX("<html lang>: mx"), //
        LANG_MY("<html lang>: my"), //
        LANG_NAH("<html lang>: nah"), //
        LANG_NB_NO("<html lang>: nb-no"), //
        LANG_NE("<html lang>: ne"), //
        LANG_NL("<html lang>: nl"), //
        LANG_NL_BE("<html lang>: nl-be"), //
        LANG_NL_NL("<html lang>: nl-nl"), //
        LANG_NL__NL("<html lang>: nl_nl"), //
        LANG_NN("<html lang>: nn"), //
        LANG_NO("<html lang>: no"), //
        LANG_NO_NB("<html lang>: no-nb"), //
        LANG_NO_NO("<html lang>: no-no"), //
        LANG_NY("<html lang>: ny"), //
        LANG_OC("<html lang>: oc"), //
        LANG_OR("<html lang>: or"), //
        LANG_OS("<html lang>: os"), //
        LANG_PA("<html lang>: pa"), //
        LANG_PL("<html lang>: pl"), //
        LANG_PL_PL("<html lang>: pl-pl"), //
        LANG_PL__PL("<html lang>: pl_pl"), //
        LANG_PNB("<html lang>: pnb"), //
        LANG_PS("<html lang>: ps"), //
        LANG_PT("<html lang>: pt"), //
        LANG_PT_BR("<html lang>: pt-br"), //
        LANG_PT__BR("<html lang>: pt_br"), //
        LANG_PT_PT("<html lang>: pt-pt"), //
        LANG_RO("<html lang>: ro"), //
        LANG_RO_RO("<html lang>: ro-ro"), //
        LANG_RO__RO("<html lang>: ro_ro"), //
        LANG_RS("<html lang>: rs"), //
        LANG_RU("<html lang>: ru"), //
        LANG_RU_RU("<html lang>: ru-ru"), //
        LANG_RU__RU("<html lang>: ru_ru"), //
        LANG_RU_UA("<html lang>: ru-ua"), //
        LANG_RW("<html lang>: rw"), //
        LANG_SAH("<html lang>: sah"), //
        LANG_SD("<html lang>: sd"), //
        LANG_SE("<html lang>: se"), //
        LANG_SH("<html lang>: sh"), //
        LANG_SI("<html lang>: si"), //
        LANG_SK("<html lang>: sk"), //
        LANG_SK_SK("<html lang>: sk-sk"), //
        LANG_SL("<html lang>: sl"), //
        LANG_SL_SI("<html lang>: sl-si"), //
        LANG_SN("<html lang>: sn"), //
        LANG_SP("<html lang>: sp"), //
        LANG_SQ("<html lang>: sq"), //
        LANG_SQ_AL("<html lang>: sq-al"), //
        LANG_SR("<html lang>: sr"), //
        LANG_SR_LATN("<html lang>: sr-latn"), //
        LANG_SR_RS("<html lang>: sr-rs"), //
        LANG_SR_SR("<html lang>: sr-sr"), //
        LANG_SR_YU("<html lang>: sr-yu"), //
        LANG_ST("<html lang>: st"), //
        LANG_SU("<html lang>: su"), //
        LANG_SV("<html lang>: sv"), //
        LANG_SV_SE("<html lang>: sv-se"), //
        LANG_SW("<html lang>: sw"), //
        LANG_TA("<html lang>: ta"), //
        LANG_TE("<html lang>: te"), //
        LANG_TG("<html lang>: tg"), //
        LANG_TH("<html lang>: th"), //
        LANG_TH_TH("<html lang>: th-th"), //
        LANG_TL("<html lang>: tl"), //
        LANG_TR("<html lang>: tr"), //
        LANG_TR_TR("<html lang>: tr-tr"), //
        LANG_TR__TR("<html lang>: tr_tr"), //
        LANG_TT("<html lang>: tt"), //
        LANG_TW("<html lang>: tw"), //
        LANG_UA("<html lang>: ua"), //
        LANG_UG("<html lang>: ug"), //
        LANG_UK("<html lang>: uk"), //
        LANG_UK_UA("<html lang>: uk-ua"), //
        LANG_UR("<html lang>: ur"), //
        LANG_UR_PK("<html lang>: ur-pk"), //
        LANG_US("<html lang>: us"), //
        LANG_US_EN("<html lang>: us-en"), //
        LANG_UZ("<html lang>: uz"), //
        LANG_VI("<html lang>: vi"), //
        LANG_VI_VN("<html lang>: vi-vn"), //
        LANG_VI__VN("<html lang>: vi_vn"), //
        LANG_VN("<html lang>: vn"), //
        LANG_XH("<html lang>: xh"), //
        LANG_ZH("<html lang>: zh"), //
        LANG_ZH_CH("<html lang>: zh-ch"), //
        LANG_ZH_CMN("<html lang>: zh-cmn"), //
        LANG_ZH_CMN_HANS("<html lang>: zh-cmn-hans"), //
        LANG_ZH_CMN_HANT("<html lang>: zh-cmn-hant"), //
        LANG_ZH_CN("<html lang>: zh-cn"), //
        LANG_ZH__CN("<html lang>: zh_cn"), //
        LANG_ZH_HANS("<html lang>: zh-hans"), //
        LANG_ZH_HANS_CN("<html lang>: zh-hans-cn"), //
        LANG_ZH_HANT("<html lang>: zh-hant"), //
        LANG_ZH_HANT_HK("<html lang>: zh-hant-hk"), //
        LANG_ZH_HANT_TW("<html lang>: zh-hant-tw"), //
        LANG_ZH_HK("<html lang>: zh-hk"), //
        LANG_ZH__HK("<html lang>: zh_hk"), //
        LANG_ZH_TW("<html lang>: zh-tw"), //
        LANG_ZH__TW("<html lang>: zh_tw"), //
        LANG_ZU("<html lang>: zu"), //
        LANG_ZXX("<html lang>: zxx"), //
        DETECTEDLANG_AF("Detected language: Afrikaans"), //
        DETECTEDLANG_AZB("Detected language: South Azerbaijani"), //
        DETECTEDLANG_AM("Detected language: Amharic"), //
        DETECTEDLANG_AR("Detected language: Arabic"), //
        DETECTEDLANG_AZ("Detected language: Azerbaijani"), //
        DETECTEDLANG_BA("Detected language: Bashkir"), //
        DETECTEDLANG_BE("Detected language: Belarusian"), //
        DETECTEDLANG_BN("Detected language: Bengali"), //
        DETECTEDLANG_BO("Detected language: Tibetan"), //
        DETECTEDLANG_BS("Detected language: Bosnian"), //
        DETECTEDLANG_CA("Detected language: Catalan"), //
        DETECTEDLANG_CE("Detected language: Chechen"), //
        DETECTEDLANG_CHR("Detected language: Cherokee"), //
        DETECTEDLANG_CKB("Detected language: Sorani Kurdish"), //
        DETECTEDLANG_CR("Detected language: Cree"), //
        DETECTEDLANG_CS("Detected language: Czech"), //
        DETECTEDLANG_CY("Detected language: Welsh"), //
        DETECTEDLANG_DA("Detected language: Danish"), //
        DETECTEDLANG_DE("Detected language: German"), //
        DETECTEDLANG_DV("Detected language: Divehi"), //
        DETECTEDLANG_EL("Detected language: Greek"), //
        DETECTEDLANG_EN("Detected language: English"), //
        DETECTEDLANG_ES("Detected language: Spanish"), //
        DETECTEDLANG_ET("Detected language: Estonian"), //
        DETECTEDLANG_EU("Detected language: Basque"), //
        DETECTEDLANG_FA("Detected language: Persian"), //
        DETECTEDLANG_FI("Detected language: Finnish"), //
        DETECTEDLANG_FO("Detected language: Faroese"), //
        DETECTEDLANG_FR("Detected language: French"), //
        DETECTEDLANG_GA("Detected language: Irish"), //
        DETECTEDLANG_GU("Detected language: Gujarati"), //
        DETECTEDLANG_HA("Detected language: Hausa"), //
        DETECTEDLANG_HE("Detected language: Hebrew"), //
        DETECTEDLANG_HI("Detected language: Hindi"), //
        DETECTEDLANG_HR("Detected language: Croatian"), //
        DETECTEDLANG_HU("Detected language: Hungarian"), //
        DETECTEDLANG_HY("Detected language: Armenian"), //
        DETECTEDLANG_ID("Detected language: Indonesian"), //
        DETECTEDLANG_IG("Detected language: Igbo"), //
        DETECTEDLANG_IS("Detected language: Icelandic"), //
        DETECTEDLANG_IT("Detected language: Italian"), //
        DETECTEDLANG_IU("Detected language: Inuktitut"), //
        DETECTEDLANG_JA("Detected language: Japanese"), //
        DETECTEDLANG_JV("Detected language: Javanese"), //
        DETECTEDLANG_KA("Detected language: Georgian"), //
        DETECTEDLANG_KM("Detected language: Khmer"), //
        DETECTEDLANG_KK("Detected language: Kazakh"), //
        DETECTEDLANG_KN("Detected language: Kannada"), //
        DETECTEDLANG_KO("Detected language: Korean"), //
        DETECTEDLANG_KU("Detected language: Kurdish"), //
        DETECTEDLANG_KY("Detected language: Kyrgyz"), //
        DETECTEDLANG_LO("Detected language: Lao"), //
        DETECTEDLANG_LT("Detected language: Lithuanian"), //
        DETECTEDLANG_LV("Detected language: Latvian"), //
        DETECTEDLANG_MG("Detected language: Malagasy"), //
        DETECTEDLANG_MHR("Detected language: Meadow Mari"), //
        DETECTEDLANG_MI("Detected language: Maori"), //
        DETECTEDLANG_MK("Detected language: Macedonian"), //
        DETECTEDLANG_ML("Detected language: Malayalam"), //
        DETECTEDLANG_MN("Detected language: Mongolian"), //
        DETECTEDLANG_MR("Detected language: Marathi"), //
        DETECTEDLANG_MRJ("Detected language: Hill Mari"), //
        DETECTEDLANG_MS("Detected language: Malay"), //
        DETECTEDLANG_MT("Detected language: Maltese"), //
        DETECTEDLANG_MY("Detected language: Burmese"), //
        DETECTEDLANG_NAH("Detected language: Nahuatl"), //
        DETECTEDLANG_NE("Detected language: Nepali"), //
        DETECTEDLANG_NL("Detected language: Dutch"), //
        DETECTEDLANG_NO("Detected language: Norwegian"), //
        DETECTEDLANG_NY("Detected language: Nyanja"), //
        DETECTEDLANG_OC("Detected language: Occitan"), //
        DETECTEDLANG_OM("Detected language: Oromo"), //
        DETECTEDLANG_OR("Detected language: Oriya"), //
        DETECTEDLANG_OS("Detected language: Ossetian"), //
        DETECTEDLANG_PA("Detected language: Punjabi"), //
        DETECTEDLANG_PL("Detected language: Polish"), //
        DETECTEDLANG_PNB("Detected language: Western Panjabi"), //
        DETECTEDLANG_PS("Detected language: Pashto"), //
        DETECTEDLANG_PT("Detected language: Portuguese"), //
        DETECTEDLANG_RO("Detected language: Romanian"), //
        DETECTEDLANG_RU("Detected language: Russian"), //
        DETECTEDLANG_RW("Detected language: Kinyarwanda"), //
        DETECTEDLANG_SAH("Detected language: Sakha"), //
        DETECTEDLANG_SD("Detected language: Sindhi"), //
        DETECTEDLANG_SH("Detected language: Croatian, Serbian, or Bosnian"), //
        DETECTEDLANG_SI("Detected language: Sinhala"), //
        DETECTEDLANG_SK("Detected language: Slovak"), //
        DETECTEDLANG_SL("Detected language: Slovenian"), //
        DETECTEDLANG_SN("Detected language: Shona"), //
        DETECTEDLANG_SO("Detected language: Somali"), //
        DETECTEDLANG_SQ("Detected language: Albanian"), //
        DETECTEDLANG_SR_CYRL("Detected language: Serbian (Cyrillic)"), //
        DETECTEDLANG_SR_LATN("Detected language: Serbian (Latin)"), //
        DETECTEDLANG_ST("Detected language: Southern Sotho"), //
        DETECTEDLANG_SU("Detected language: Sundanese"), //
        DETECTEDLANG_SV("Detected language: Swedish"), //
        DETECTEDLANG_SW("Detected language: Swahili"), //
        DETECTEDLANG_TA("Detected language: Tamil"), //
        DETECTEDLANG_TE("Detected language: Telugu"), //
        DETECTEDLANG_TG("Detected language: Tajik"), //
        DETECTEDLANG_TH("Detected language: Thai"), //
        DETECTEDLANG_TI("Detected language: Tigrinya"), //
        DETECTEDLANG_TL("Detected language: Tagalog"), //
        DETECTEDLANG_TR("Detected language: Turkish"), //
        DETECTEDLANG_TT("Detected language: Tatar"), //
        DETECTEDLANG_UG("Detected language: Uyghur"), //
        DETECTEDLANG_UK("Detected language: Ukrainian"), //
        DETECTEDLANG_UR("Detected language: Urdu"), //
        DETECTEDLANG_UZ_CYRL("Detected language: Uzbek (Cyrillic)"), //
        DETECTEDLANG_UZ_LATN("Detected language: Uzbek (Latin)"), //
        DETECTEDLANG_VI("Detected language: Vietnamese"), //
        DETECTEDLANG_XH("Detected language: Xhosa"), //
        DETECTEDLANG_ZH_HANS("Detected language: Simplified Chinese"), //
        DETECTEDLANG_ZH_HANT("Detected language: Traditional Chinese"), //
        DETECTEDLANG_ZU("Detected language: Zulu"), //
        DETECTEDLANG_ZXX("Detected language: Lorem ipsum text"); //
        // @formatter:on

        Field(String description) {
            this.description = description;
        }

        private final String description;

        /**
         * @see java.lang.Enum#toString()
         */
        @Override
        public String toString() {
            return description;
        }
    }

    public Field getFieldFromName(String name) {
        for (Field field : Field.class.getEnumConstants()) {
            if (field.name().equals(name)) {
                return field;
            }
        }
        throw new IllegalArgumentException(
                "No statistics field with name " + name);
    }

    static {
        if ("1".equals(System.getProperty("nu.validator.servlet.statistics"))) {
            STATISTICS = new Statistics();
        } else {
            STATISTICS = null;
        }
    }

    private final long startTime = System.currentTimeMillis();

    private long total = 0;

    private final long[] counters;

    private Statistics() {
        counters = new long[Field.values().length];
    }

    public void incrementTotal() {
        total++;
    }

    public void incrementField(Field field) {
        counters[field.ordinal()]++;
    }

    public void writeToResponse(HttpServletResponse response)
            throws IOException {
        try {
            long totalCopy;
            long[] countersCopy = new long[counters.length];
            synchronized (this) {
                totalCopy = total;
                System.arraycopy(counters, 0, countersCopy, 0, counters.length);
            }
            double totalDouble = totalCopy;
            double uptimeMillis = System.currentTimeMillis() - startTime;
            response.setContentType("text/html; charset=utf-8");
            ContentHandler ch = new HtmlSerializer(response.getOutputStream());
            try {
                ch.startDocument();
                startElement(ch, "html");
                startElement(ch, "head");
                startElement(ch, "title");
                characters(ch, VALIDATOR_STATISTICS);
                endElement(ch, "title");
                startElement(ch, "style");
                characters(ch, STYLESHEET);
                endElement(ch, "style");
                endElement(ch, "head");
                startElement(ch, "body");
                startElement(ch, "h1");
                characters(ch, VALIDATOR_STATISTICS);
                endElement(ch, "h1");

                startElement(ch, "dl");
                startElement(ch, "dt");
                characters(ch, TOTAL_VALIDATIONS);
                endElement(ch, "dt");
                startElement(ch, "dd");
                characters(ch, totalCopy);
                endElement(ch, "dd");

                startElement(ch, "dt");
                characters(ch, UPTIME_DAYS);
                endElement(ch, "dt");
                startElement(ch, "dd");
                characters(ch, uptimeMillis / (1000 * 60 * 60 * 24));
                endElement(ch, "dd");

                startElement(ch, "dt");
                characters(ch, VALIDATIONS_PER_SECOND);
                endElement(ch, "dt");
                startElement(ch, "dd");
                characters(ch, totalDouble / (uptimeMillis / 1000.0));
                endElement(ch, "dd");

                endElement(ch, "dl");

                startElement(ch, "table");
                startElement(ch, "thead");
                startElement(ch, "tr");
                startElement(ch, "th");
                characters(ch, COUNTER_NAME);
                endElement(ch, "th");
                startElement(ch, "th");
                characters(ch, COUNTER_VALUE);
                endElement(ch, "th");
                startElement(ch, "th");
                characters(ch, COUNTER_PROPORTION);
                endElement(ch, "th");
                endElement(ch, "tr");
                endElement(ch, "thead");
                startElement(ch, "tbody");
                for (int i = 0; i < countersCopy.length; i++) {
                    long count = countersCopy[i];
                    startElement(ch, "tr");
                    startElement(ch, "td");

                    characters(ch, Field.values()[i].toString());

                    endElement(ch, "td");
                    startElement(ch, "td");

                    characters(ch, count);

                    endElement(ch, "td");
                    startElement(ch, "td");

                    characters(ch, count / totalDouble);

                    endElement(ch, "td");
                    endElement(ch, "tr");
                }
                endElement(ch, "tbody");
                endElement(ch, "table");
                startElement(ch, "script");
                characters(ch, SORT_LANGS_SCRIPT);
                endElement(ch, "script");
                endElement(ch, "body");
                endElement(ch, "html");
            } finally {
                ch.endDocument();
            }
        } catch (SAXException e) {
            throw new IOException(e);
        }
    }

    private void characters(ContentHandler ch, double d) throws SAXException {
        // Let's just create a new DecimalFormat each time to avoid the
        // complexity of recycling an instance correctly without threading
        // hazards.
        DecimalFormat df = new DecimalFormat("#,###,##0.000000");
        characters(ch, df.format(d));
    }

    private void characters(ContentHandler ch, long l) throws SAXException {
        characters(ch, Long.toString(l));
    }

    private void characters(ContentHandler ch, String str) throws SAXException {
        characters(ch, str.toCharArray());
    }

    private void characters(ContentHandler ch, char[] cs) throws SAXException {
        ch.characters(cs, 0, cs.length);
    }

    private void endElement(ContentHandler ch, String name)
            throws SAXException {
        ch.endElement("http://www.w3.org/1999/xhtml", name, name);
    }

    private void startElement(ContentHandler ch, String name)
            throws SAXException {
        ch.startElement("http://www.w3.org/1999/xhtml", name, name,
                EmptyAttributes.EMPTY_ATTRIBUTES);
    }

}
