/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "bmhParser.h"
#include "includeParser.h"
#include "mdOut.h"

#include "SkOSFile.h"
#include "SkOSPath.h"

class SubtopicKeys {
public:
    static constexpr const char* kClasses = "Classes";
    static constexpr const char* kConstants = "Constants";
    static constexpr const char* kConstructors = "Constructors";
    static constexpr const char* kDefines = "Defines";
    static constexpr const char* kMemberFunctions = "Member_Functions";
    static constexpr const char* kMembers = "Members";
    static constexpr const char* kOperators = "Operators";
    static constexpr const char* kOverview = "Overview";
    static constexpr const char* kRelatedFunctions = "Related_Functions";
    static constexpr const char* kStructs = "Structs";
    static constexpr const char* kTypedefs = "Typedefs";

    static const char* kGeneratedSubtopics[];
};

const char* SubtopicKeys::kGeneratedSubtopics[] = {
    kConstants, kDefines, kTypedefs, kMembers, kClasses, kStructs, kConstructors,
    kOperators, kMemberFunctions, kRelatedFunctions
};

const char* kConstTableStyle =
"<style>"                                                                                      "\n"
    ".td_const td, th { border: 2px solid #dddddd; text-align: left; padding: 8px; }"          "\n"
    ".tr_const tr:nth-child(even) { background-color: #f0f0f0; }"                              "\n"
    ".td2_const td:first-child + td { text-align: center; }"                                   "\n"
"</style>"                                                                                     "\n";

const char* kTableDeclaration = "<table style='border-collapse: collapse; width: 62.5em'>";

#define kTD_Base         "border: 2px solid #dddddd; padding: 8px; "
#define kTH_Left         "<th style='text-align: left; "   kTD_Base "'>"
#define kTH_Center       "<th style='text-align: center; " kTD_Base "'>"

string kTD_Left    = "    <td style='text-align: left; "   kTD_Base "'>";
string kTD_Center  = "    <td style='text-align: center; " kTD_Base "'>";
string kTR_Dark    =   "  <tr style='background-color: #f0f0f0; '>";

const char* kAllConstTableHeader =  "  <tr>" kTH_Left   "Const</th>"                            "\n"
                                             kTH_Center "Value</th>"                            "\n"
                                             kTH_Left   "Description</th>" "</tr>";
const char* kSubConstTableHeader =  "  <tr>" kTH_Left   "Const</th>"                            "\n"
                                             kTH_Center "Value</th>"                            "\n"
                                             kTH_Left   "Details</th>"                          "\n"
                                             kTH_Left   "Description</th>" "</tr>";
const char* kAllMemberTableHeader = "  <tr>" kTH_Left   "Type</th>"                             "\n"
                                             kTH_Left   "Member</th>"                           "\n"
                                             kTH_Left   "Description</th>" "</tr>";
const char* kSubMemberTableHeader = "  <tr>" kTH_Left   "Type</th>"                             "\n"
                                             kTH_Left   "Member</th>"                           "\n"
                                             kTH_Left   "Details</th>"                          "\n"
                                             kTH_Left   "Description</th>" "</tr>";
const char* kTopicsTableHeader    = "  <tr>" kTH_Left   "Topic</th>"                            "\n"
                                             kTH_Left   "Description</th>" "</tr>";

string MdOut::anchorDef(string str, string name) {
    if (fValidate) {
        string htmlName = ParserCommon::HtmlFileName(fFileName);
        vector<AnchorDef>& allDefs = fAllAnchorDefs[htmlName];
        if (!std::any_of(allDefs.begin(), allDefs.end(),
                [str](AnchorDef compare) { return compare.fDef == str; } )) {
            MarkType markType = fLastDef->fMarkType;
            if (MarkType::kMethod == markType && fLastDef->fClone) {
                SkASSERT(0);  // incomplete
            }
            allDefs.push_back( { str, markType } );
        }
    }
    return "<a name='" + str + "'>" + name + "</a>";
}

string MdOut::anchorRef(string ref, string name) {
    if (fValidate) {
        string htmlName;
        size_t hashIndex = ref.find('#');
        if (string::npos != hashIndex && "https://" != ref.substr(0, 8)) {
            if (0 == hashIndex) {
                htmlName = ParserCommon::HtmlFileName(fFileName);
            } else {
                htmlName = ref.substr(0, hashIndex);
            }
            vector<string>& allRefs = fAllAnchorRefs[htmlName];
            string refPart = ref.substr(hashIndex + 1);
            if (allRefs.end() == std::find(allRefs.begin(), allRefs.end(), refPart)) {
                allRefs.push_back(refPart);
            }
        }
    }
    SkASSERT(string::npos != ref.find('#') || string::npos != ref.find("https://"));
    return "<a href='" + ref + "'>" + name + "</a>";
}

string MdOut::anchorLocalRef(string ref, string name) {
    return this->anchorRef("#" + ref, name);
}

string MdOut::tableDataCodeRef(string ref, string name) {
    return kTD_Left + this->anchorRef(ref, "<code>" + name + "</code>") + "</td>";
}

string MdOut::tableDataCodeLocalRef(string ref, string name) {
    return this->tableDataCodeRef("#" + ref, name);
}

string MdOut::tableDataCodeLocalRef(string name) {
    return this->tableDataCodeLocalRef(name, name);
}

string MdOut::tableDataCodeRef(const Definition* ref) {
    return this->tableDataCodeLocalRef(ref->fFiddle, ref->fName);
}

string MdOut::tableDataCodeDef(string def, string name) {
    return kTD_Left + this->anchorDef(def, "<code>" + name + "</code>") + "</td>";
}

string MdOut::tableDataCodeDef(const Definition* def) {
    return this->tableDataCodeDef(def->fFiddle, def->fName);
}

static string table_data_const(const Definition* def, const char** textStartPtr) {
    TextParser parser(def);
    SkAssertResult(parser.skipToEndBracket('\n'));
    string constant = string(def->fContentStart, (int) (parser.fChar - def->fContentStart));
    if (textStartPtr) {
        *textStartPtr = parser.fChar;
    }
    return kTD_Center + constant + "</td>";
}

static string out_table_data_description_start() {
    return kTD_Left;
}

static string out_table_data_description(string str) {
    return kTD_Left + str + "</td>";
}

static string out_table_data_description(const Definition* def) {
    return out_table_data_description(string(def->fContentStart,
            (int) (def->fContentEnd - def->fContentStart)));
}

static string out_table_data_details(string details) {
    return kTD_Left + details + "</td>";
}

#undef kConstTDBase
#undef kTH_Center

static string preformat(string orig) {
    string result;
    for (auto c : orig) {
        if ('<' == c) {
          result += "&lt;";
        } else if ('>' == c) {
          result += "&gt;";
        } else {
            result += c;
        }
    }
    return result;
}

// from https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string
void replace_all(string& str, const string& from, const string& to) {
    SkASSERT(!from.empty());
    size_t start_pos = 0;
    while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
        str.replace(start_pos, from.length(), to);
        start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
    }
}

// detail strings are preceded by an example comment to check readability
void MdOut::addPopulators() {
    auto populator = [this](string key, string singular, string plural, string oneLiner,
            string details) -> void {
        fPopulators[key].fSingular = singular;
        fPopulators[key].fPlural = plural;
        fPopulators[key].fOneLiner = oneLiner;
        fPopulators[key].fDetails = details;
    };
    populator(SubtopicKeys::kClasses, "Class", "Class Declarations",
            "embedded class members",
            /* SkImageInfo */ "uses <code>class</code> to declare the public data structures"
                              " and interfaces.");
    populator(SubtopicKeys::kConstants, "Constant", "Constants",
            "enum and enum class, and their const values",
            /* SkImageInfo */ "defines related constants are using <code>enum</code>,"
                              " <code>enum class</code>,  <code>#define</code>,"
                              " <code>const</code>, and <code>constexpr</code>.");
    populator(SubtopicKeys::kConstructors, "Constructor", "Constructors",
            "functions that construct",
            /* SkImageInfo */ "can be constructed or initialized by these functions,"
                              " including <code>class</code> constructors.");
    populator(SubtopicKeys::kDefines, "Define", "Defines",
            "preprocessor definitions of functions, values",
            /* SkImageInfo */ "uses preprocessor definitions to inline code and constants,"
                              " and to abstract platform-specific functionality.");
    populator(SubtopicKeys::kMemberFunctions, "Member Function", "Member Functions",
            "static and local functions",
            /* SkImageInfo */ "uses member functions to read and modify structure properties.");
    populator(SubtopicKeys::kMembers, "Member", "Members",
            "member values",
            /* SkImageInfo */ "contains members that may be read and written directly without using"
                              " a member function.");
    populator(SubtopicKeys::kOperators, "Operator", "Operators",
            "operator overloading functions",
            /* SkImageInfo */ "defines member functions with arithmetic equivalents.");
    populator(SubtopicKeys::kRelatedFunctions, "Related Function", "Related Functions",
            "similar functions grouped together",
            /* SkImageInfo */ "defines related functions that share a topic.");
    populator(SubtopicKeys::kStructs, "Struct", "Struct Declarations",
            "embedded struct members",
            /* SkImageInfo */ "uses <code>struct</code> to declare the public data"
                              " structures and interfaces.");
    populator(SubtopicKeys::kTypedefs, "Typedef", "Typedef Declarations",
            "types defined in terms of other types",
            /* SkImageInfo */ "uses <code>typedef</code> to define a data type.");
}

Definition* MdOut::checkParentsForMatch(Definition* test, string ref) const {
    bool isSubtopic = MarkType::kSubtopic == test->fMarkType
            || MarkType::kTopic == test->fMarkType;
    do {
        if (!test->isRoot()) {
            continue;
        }
        bool localTopic = MarkType::kSubtopic == test->fMarkType
                || MarkType::kTopic == test->fMarkType;
        if (localTopic != isSubtopic) {
            continue;
        }
        string prefix(isSubtopic ? "_" : "::");
        RootDefinition* root = test->asRoot();
        string prefixed = root->fName + prefix + ref;
        if (Definition* def = root->find(prefixed, RootDefinition::AllowParens::kYes)) {
            return def;
        }
    } while ((test = test->fParent));
    return nullptr;
}

struct BraceState {
    BraceState(RootDefinition* root, string name, const char* ch, KeyWord last, KeyWord keyWord,
            int count)
        : fRoot(root)
        , fName(name)
        , fChar(ch)
        , fLastKey(last)
        , fKeyWord(keyWord)
        , fBraceCount(count) {
    }

    RootDefinition* fRoot;
    string fName;
    const char* fChar;
    KeyWord fLastKey;
    KeyWord fKeyWord;
    int fBraceCount;
};

bool MdOut::DefinedState::hasWordSpace(string wordSpace) const {
    if (!fNames->fRefMap.size()) {
        return false;
    }
    for (const NameMap* names = fNames; names; names = names->fParent) {
        if (names->fRefMap.end() != names->fRefMap.find(wordSpace)) {
            return true;
        }
    }
    return false;
}

bool MdOut::DefinedState::phraseContinues(string phrase, string* priorWord,
        string* priorLink) const {
    for (const NameMap* names = fNames; names; names = names->fParent) {
        if (names->fRefMap.end() != names->fRefMap.find(phrase + ' ')) {
            *priorWord = phrase;
            return true;
        }
        if (names->fRefMap.end() != names->fRefMap.find(phrase)) {
            *priorWord = phrase;
            auto linkIter = names->fLinkMap.find(phrase);
            *priorLink = names->fLinkMap.end() == linkIter ? "" : linkIter->second;
            return true;
        }
    }
    return false;
}

void MdOut::DefinedState::setLink() {
    fLink = "";
    fPriorDef = nullptr;
    // TODO: operators have complicated parsing possibilities; handle the easiest for now
    // TODO: constructors also have complicated parsing possibilities; handle the easiest
    bool isOperator = "operator" == fPriorWord;
    if (((fRoot && fRoot->isStructOrClass() && fRoot->fName == fPriorWord) || isOperator)
            && '(' == fSeparator.back()) {
        SkASSERT(fSubtopic);
        TextParser parser(fSubtopic->fFileName, fSeparatorStart, fRefEnd, fSubtopic->fLineCount);
        parser.skipToEndBracket('(');
        const char* parenStart = parser.fChar;
        parser.skipToBalancedEndBracket('(', ')');
        (void) parser.skipExact(" const");
        string methodName = fPriorWord + fSeparator
                + string(parenStart + 1, parser.fChar - parenStart - 1);
        string testLink;
        if (this->findLink(methodName, &testLink, false)) {
            // consume only if we find it
            if (isOperator) {
                fPriorWord += fSeparator.substr(0, fSeparator.length() - 1);  // strip paren
                fPriorSeparator = "(";
            }
            fWord = "";
            fPriorLink = testLink;
            fEnd = parenStart + 1;
            return;
        }
    }
    // look to see if text following ref is method qualifier
    else if ((Resolvable::kYes == fResolvable || Resolvable::kClone == fResolvable)
            && "(" == fSeparator && "" != fPriorLink) {
        TextParser parser(fLastDef->fFileName, fSeparatorStart, fRefEnd, fLastDef->fLineCount);
        parser.skipToBalancedEndBracket('(', ')');
        string fullMethod = fPriorWord + string(parser.fStart, parser.fChar - parser.fStart);
        string trimmed = trim_inline_spaces(fullMethod);
        string testLink;
        if (findLink(trimmed, &testLink, false)) {
            fMethodName = fullMethod;
            fWord = trimmed;
            fLink = testLink;
            fEnd = parser.fChar;
            this->backup();
            return;
        }
    }
    if ("." == fSeparator || "->" == fSeparator || "()." == fSeparator || "()->" == fSeparator) {
        bool foundField = fWord.length() >= 2 && (('f' == fWord[0] && isupper(fWord[1]))
                || "()" == fWord.substr(fWord.length() - 2)
                || (fEnd + 2 <= fRefEnd && "()" == string(fEnd, 2)));
        if (foundField) {
            if (fMethod && fNames->fRefMap.end() != fNames->fRefMap.find(fPriorWord)) {
        // find prior fWord's type in fMethod
                TextParser parser(fMethod);
                SkAssertResult(parser.containsWord(fPriorWord.c_str(), parser.fEnd,
                        &parser.fChar));
        // look up class or struct; trival lookup only class/struct [& * const]
                while (parser.back(" ") || parser.back("&") || parser.back("*")
                        || parser.back("const"))
                    ;
                const char* structEnd = parser.fChar;
                parser.backupWord();
                if (structEnd != parser.fChar) {
                    string structName(parser.fChar, structEnd - parser.fChar);
                    if ("SkVector" == structName) {
                        // TODO: populate global refmap with typedefs as well as structs
                        structName = "SkPoint";
                    } else if ("SkIVector" == structName) {
                        structName = "SkIPoint";
                    }
                    structName += "::" + fWord;
        // look for fWord as member of class or struct
                    auto defIter = fGlobals->fRefMap.find(structName);
                    if (fGlobals->fRefMap.end() == defIter) {
                        structName += "()";
                        defIter = fGlobals->fRefMap.find(structName);
                    }
                    if (fGlobals->fRefMap.end() != defIter) {
                        // example: dstInfo.width()
                        auto structIter = fGlobals->fLinkMap.find(structName);
                        SkASSERT(fGlobals->fLinkMap.end() != structIter);
                        fLink = structIter->second;
                        fPriorDef = defIter->second;
                        return;
                    } else {
                        SkDebugf("probably missing struct or class member in bmh: ");
                        SkDebugf("%s\n", structName.c_str());
                    }
                }
            }
            auto& parentRefMap = fNames->fParent->fRefMap;
            auto priorIter = parentRefMap.find(fPriorWord);
            if (parentRefMap.end() == priorIter) {
                priorIter = parentRefMap.find(fPriorWord + "()");
            }
            if (parentRefMap.end() != priorIter) {
                Definition* priorDef = priorIter->second;
                if (priorDef) {
                    TextParser parser(priorDef->fFileName, priorDef->fStart,
                            priorDef->fContentStart, priorDef->fLineCount);
                    parser.skipExact("#Method ");
                    parser.skipSpace();
                    parser.skipExact("const ");  // optional
                    parser.skipSpace();
                    const char* start = parser.fChar;
                    parser.skipToNonAlphaNum();
                    string structName(start, parser.fChar - start);
                    structName += "::" + fWord;
                    auto defIter = fGlobals->fRefMap.find(structName);
                    if (fGlobals->fRefMap.end() != defIter) {
                        // example: imageInfo().width()
                        auto globalIter = fGlobals->fLinkMap.find(structName);
                        SkASSERT(fGlobals->fLinkMap.end() != globalIter);
                        fLink = globalIter->second;
                        fPriorDef = defIter->second;
                        return;
                    }
                }
            }
        } else {
            string fullRef = fPriorWord + fSeparator + fWord;
            if (this->findLink(fullRef, &fLink, false)) {
                return;
            }
            if (Resolvable::kCode != fResolvable) {
                SkDebugf("probably missing () after function:");
                const char* debugStart = fEnd - 20 < fRefStart ? fRefStart : fEnd - 20;
                const char* debugEnd = fEnd + 10 > fRefEnd ? fRefEnd : fEnd + 10;
                SkDebugf("%.*s\n", debugEnd - debugStart, debugStart);
                SkDebugf(""); // convenient place to set a breakpoint
            }
        }
    }
    // example: SkCanvas::restoreToCount
    if ("::" == fSeparator) {
        string fullRef = fPriorWord + "::" + fWord;
        if (this->findLink(fullRef, &fLink, fAddParens)) {
            return;
        }
    }
    // look in parent fNames and above for match
    if (fNames) {
        if (this->findLink(fWord, &fLink, (Resolvable::kClone == fResolvable && fAddParens)
                || (Resolvable::kCode == fResolvable && '(' == fEnd[0]))) {
            return;
        }
    }
    // example : sqrt as in "sqrt(x * x + y * y)"
    // example : erase in seeAlso
    if (Resolvable::kClone == fResolvable || (fEnd + 1 < fRefEnd && '(' == fEnd[0])) {
        if ((fAddParens || '~' == fWord.front()) && this->findLink(fWord + "()", &fLink, false)) {
            return;
        }
    }
    // example: Color_Type
    if (this->findLink(fWord, &fLink, fBmhParser->fAliasMap)) {
        return;
    }
    if (Resolvable::kInclude != fResolvable && string::npos != fWord.find('_')) {
        // example: Blend_Mode
        if (this->findLink(fWord, &fLink, fBmhParser->fTopicMap)) {
            return;
        }
        if (fSubtopic) {
            // example: Fake_Bold
            if (fSubtopic->fName == fWord) {
                fLink = '#' + fSubtopic->fFiddle;
                fPriorDef = fSubtopic;
                return;
            }
            const Definition* rootTopic = fSubtopic->subtopicParent();
            if (rootTopic) {
                if (rootTopic->fFiddle == fWord) {
                    fLink = '#' + rootTopic->fFiddle;
                    fPriorDef = rootTopic;
                    return;
                }
                string globName = rootTopic->fFiddle + '_' + fWord;
                if (this->findLink(globName, &fLink, fBmhParser->fTopicMap)) {
                    return;
                }
            }
        }
        if (fRoot) {
            string test = fRoot->fName + "::" + fWord;
            auto rootIter = fRoot->fLeaves.find(test);
            // example: restoreToCount in subtopic State_Stack
            if (fRoot->fLeaves.end() != rootIter) {
                fLink = '#' + rootIter->second.fFiddle;
                fPriorDef = &rootIter->second;
                return;
            }
        }
    }
    if (isupper(fWord[0]) && string::npos != fWord.find('_')) {
        const Definition* topical = fSubtopic;
        do {
            string subtopic = topical->fName + '_' + fWord;
            // example: Stroke_Width
            if (this->findLink(subtopic, &fLink, fBmhParser->fTopicMap)) {
                return;
            }
        } while ((topical = topical->topicParent()));
    }
    // treat hex constants as known words
    if (fSeparator.size() > 0 && '0' == fSeparator.back() && 'x' == fWord[0]) {
        bool allHex = true;
        for (size_t index = 1; index < fWord.size(); ++index) {
            char c = fWord[index];
            if (('0' > c || '9' < c) && ('A' > c || 'F' < c)) {
                allHex = false;
                break;
            }
        }
        if (allHex) {
            return;
        }
    }
    // treat floating constants as known words
    if ("e" == fWord) {
        if (std::all_of(fSeparator.begin(), fSeparator.end(), [](char c) {
            return isdigit(c) || '.' == c || '-' == c || ' ' >= c;
        })) {
            return;
        }
    }
    // stop short of parsing example; just look to see if it contains fWord in description
    if (fLastDef && MarkType::kDescription == fLastDef->fMarkType) {
        Definition* example = fLastDef->fParent;
        if (MarkType::kExample == example->fMarkType) {
            // example text is blocked by last child before std out, if it exists
            const char* exStart = example->fChildren.back()->fContentEnd;
            const char* exEnd = example->fContentEnd;
            if (MarkType::kStdOut == example->fChildren.back()->fMarkType) {
                exStart = example->fChildren[example->fChildren.size() - 2]->fContentEnd;
                exEnd = example->fChildren.back()->fContentStart;
            }
            // maybe need a general function that searches block text excluding children
            TextParser exParse(example->fFileName, exStart, exEnd, example->fLineCount);
            if (exParse.containsWord(fWord.c_str(), exParse.fEnd, nullptr)) {
                return;
            }
        }
    }
    // example: (x1, y1) after arcTo(SkScalar x1, ...
    if (Resolvable::kYes == fResolvable && "" != fSeparator
            && ('(' == fSeparator.back() || ',' == fSeparator[0])
            && string::npos != fMethodName.find(fWord)) {
        return;
    }
    // example: <sup>  (skip html)
    if (Resolvable::kYes == fResolvable && fEnd + 1 < fRefEnd && '>' == fEnd[0] && "" != fSeparator
            && ('<' == fSeparator.back() || (fSeparator.size() >= 2
            && "</" == fSeparator.substr(fSeparator.size() - 2)))) {
        return;
    }
    bool paramName = islower(fWord[0]) && (Resolvable::kCode == fResolvable
            || Resolvable::kClone == fResolvable);
    // TODO: can probably resolve formulae, but need a way for formula to define new reference
    // for example: Given: #Formula # Sa ## as source Alpha,
    // for example: where #Formula # m = Da > 0 ? Dc / Da : 0 ##;
    if (!fInProgress && Resolvable::kSimple != fResolvable
            && !paramName && Resolvable::kFormula != fResolvable) {
        // example: Coons as in "Coons patch"
        bool withSpace = fEnd + 1 < fRefEnd && ' ' == fEnd[0]
                && fGlobals->fRefMap.end() != fGlobals->fRefMap.find(fWord + ' ');
        if (!withSpace && (Resolvable::kInclude == fResolvable ? !fInMatrix :
                '"' != fPriorSeparator.back() || '"' != fSeparator.back())) {
            SkDebugf("word %s not found\n", fWord.c_str());
            fBmhParser->fGlobalNames.fRefMap[fWord] = nullptr;
        }
    }
}


string MdOut::addReferences(const char* refStart, const char* refEnd, Resolvable resolvable) {
    DefinedState s(*this, refStart, refEnd, resolvable);
    string result;
    const char* start = refStart;
    do {
        s.fSeparatorStart = start;
        start = s.skipWhiteSpace();
        s.skipParens();
        string separator = s.nextSeparator(start);
        if (fDebugWriteCodeBlock) {
            SkDebugf("%s", separator.c_str());
        }
        result += separator;
        if (s.findEnd(start)) {
            break;
        }
        s.fWord = string(start, s.fEnd - start);
        if ("TODO" == s.fWord) {
            while('\n' != *s.fEnd++)
                ;
            start = s.fEnd;
            continue;
        }
        s.setLower();
        if (s.setPriorSpaceWord(&start)) {
            continue;
        }
        s.setLink();
        string link = "" == s.fPriorLink ? s.fPriorWord :
                this->anchorRef(s.fPriorLink, s.fPriorWord);
        if (fDebugWriteCodeBlock) {
            SkDebugf("%s", link.c_str());
        }
        result += link;
        start = s.nextWord();
    } while (true);
    string finalLink = "" == s.fPriorLink ? s.fPriorWord :
            this->anchorRef(s.fPriorLink, s.fPriorWord);
    if (fDebugWriteCodeBlock) {
        SkDebugf("%s", finalLink.c_str());
    }
    result += finalLink;
    if (fDebugWriteCodeBlock) {
        SkDebugf("%s", s.fPriorSeparator.c_str());
    }
    result += s.fPriorSeparator;
    return result;
}

bool MdOut::buildReferences(const char* docDir, const char* mdFileOrPath) {
    if (!sk_isdir(mdFileOrPath)) {
        SkDebugf("must pass directory %s\n", mdFileOrPath);
        SkDebugf("pass -i SkXXX.h to build references for a single include\n");
        return false;
    }
    fInProgress = true;
    SkOSFile::Iter it(docDir, ".bmh");
    for (SkString file; it.next(&file); ) {
        if (!fIncludeParser.references(file)) {
            continue;
        }
        SkString p = SkOSPath::Join(docDir, file.c_str());
        if (!this->buildRefFromFile(p.c_str(), mdFileOrPath)) {
            SkDebugf("failed to parse %s\n", p.c_str());
            return false;
        }
    }
    return true;
}

bool MdOut::buildStatus(const char* statusFile, const char* outDir) {
    StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress);
    StatusFilter filter;
    for (string file; iter.next(&file, &filter); ) {
        SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str());
        const char* hunk = p.c_str();
        fInProgress = StatusFilter::kInProgress == filter;
        if (!this->buildRefFromFile(hunk, outDir)) {
            SkDebugf("failed to parse %s\n", hunk);
            return false;
        }
    }
    return true;
}

bool MdOut::buildRefFromFile(const char* name, const char* outDir) {
    if (!SkStrEndsWith(name, ".bmh")) {
        return true;
    }
    if (SkStrEndsWith(name, "markup.bmh")) {  // don't look inside this for now
        return true;
    }
    if (SkStrEndsWith(name, "illustrations.bmh")) {  // don't look inside this for now
        return true;
    }
    if (SkStrEndsWith(name, "undocumented.bmh")) {  // don't look inside this for now
        return true;
    }
    fFileName = string(name);
    string filename(name);
    if (filename.substr(filename.length() - 4) == ".bmh") {
        filename = filename.substr(0, filename.length() - 4);
    }
    size_t start = filename.length();
    while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
        --start;
    }
    string match = filename.substr(start);
    string header = match;
    filename = match + ".md";
    match += ".bmh";
    fOut = nullptr;
    string fullName;

    vector<string> keys;
    keys.reserve(fBmhParser.fTopicMap.size());
    for (const auto& it : fBmhParser.fTopicMap) {
        keys.push_back(it.first);
    }
    std::sort(keys.begin(), keys.end());
    for (auto key : keys) {
        string s(key);
        auto topicDef = fBmhParser.fTopicMap.at(s);
        if (topicDef->fParent) {
            continue;
        }
        if (string::npos == topicDef->fFileName.rfind(match)) {
            continue;
        }
        if (!fOut) {
            fullName = outDir;
            if ('/' != fullName.back()) {
                fullName += '/';
            }
            fullName += filename;
            fOut = fopen(filename.c_str(), "wb");
            if (!fOut) {
                SkDebugf("could not open output file %s\n", fullName.c_str());
                return false;
            }
            if (false) {    // try inlining the style
                FPRINTF("%s", kConstTableStyle);
            }
            size_t underscorePos = header.find('_');
            if (string::npos != underscorePos) {
                header.replace(underscorePos, 1, " ");
            }
            SkASSERT(string::npos == header.find('_'));
            this->writeString(header);
            this->lfAlways(1);
            this->writeString("===");
            this->lfAlways(1);
        }
        const Definition* prior = nullptr;
        this->markTypeOut(topicDef, &prior);
    }
    if (fOut) {
        this->writePending();
        fclose(fOut);
        fflush(fOut);
        if (ParserCommon::WrittenFileDiffers(fullName, filename)) {
            ParserCommon::CopyToFile(fullName, filename);
            SkDebugf("wrote %s\n", fullName.c_str());
        } else {
            remove(filename.c_str());
        }
        fOut = nullptr;
    }
    return !fAddRefFailed;
}

static bool contains_referenced_child(const Definition* found, const vector<string>& refs) {
    for (auto child : found->fChildren) {
        if (refs.end() != std::find_if(refs.begin(), refs.end(),
                    [child](string def) { return child->fName == def; } )) {
            return true;
        }
        if (contains_referenced_child(child, refs)) {
            return true;
        }
    }
    return false;
}

void MdOut::checkAnchors() {
    int missing = 0;
    for (auto bmhFile : fAllAnchorRefs) {
        auto defIter = fAllAnchorDefs.find(bmhFile.first);
        SkASSERT(fAllAnchorDefs.end() != defIter);
        vector<AnchorDef>& allDefs = defIter->second;
        std::sort(allDefs.begin(), allDefs.end(),
                [](const AnchorDef& a, const AnchorDef& b) { return a.fDef < b.fDef; } );
        std::sort(bmhFile.second.begin(), bmhFile.second.end());
        auto allDefsIter = allDefs.begin();
        auto allRefsIter = bmhFile.second.begin();
        for (;;) {
            bool allDefsEnded = allDefsIter == allDefs.end();
            bool allRefsEnded = allRefsIter == bmhFile.second.end();
            if (allDefsEnded && allRefsEnded) {
                break;
            }
            if (allRefsEnded || (!allDefsEnded && allDefsIter->fDef < *allRefsIter)) {
                if (MarkType::kParam != allDefsIter->fMarkType) {
                    // If undocumented but parent or child is referred to: good enough for now
                    bool goodEnough = false;
                    if ("undocumented" == defIter->first) {
                        auto iter = fBmhParser.fTopicMap.find(allDefsIter->fDef);
                        if (fBmhParser.fTopicMap.end() != iter) {
                            const Definition* found = iter->second;
                            if (string::npos != found->fFileName.find("undocumented")) {
                                const Definition* parent = found;
                                while ((parent = parent->fParent)) {
                                    if (bmhFile.second.end() != std::find_if(bmhFile.second.begin(),
                                            bmhFile.second.end(),
                                            [parent](string def) {
                                            return parent->fName == def; } )) {
                                        goodEnough = true;
                                        break;
                                    }
                                }
                                if (!goodEnough) {
                                    goodEnough = contains_referenced_child(found, bmhFile.second);
                                }
                            }
                        }
                    }
                    if (!goodEnough) {
                        SkDebugf("missing ref %s %s\n", defIter->first.c_str(),
                                allDefsIter->fDef.c_str());
                        missing++;
                    }
                }
                allDefsIter++;
            } else if (allDefsEnded || (!allRefsEnded && allDefsIter->fDef > *allRefsIter)) {
                if (fBmhParser.fExternals.end() == std::find_if(fBmhParser.fExternals.begin(),
                        fBmhParser.fExternals.end(), [allRefsIter](const RootDefinition& root) {
                        return *allRefsIter != root.fName; } )) {
                    SkDebugf("missing def %s %s\n", bmhFile.first.c_str(), allRefsIter->c_str());
                    missing++;
                }
                allRefsIter++;
            } else {
                SkASSERT(!allDefsEnded);
                SkASSERT(!allRefsEnded);
                SkASSERT(allDefsIter->fDef == *allRefsIter);
                allDefsIter++;
                allRefsIter++;
            }
            if (missing >= 10) {
                missing = 0;
            }
        }
    }
}

bool MdOut::checkParamReturnBody(const Definition* def) {
    TextParser paramBody(def);
    const char* descriptionStart = paramBody.fChar;
    if (!islower(descriptionStart[0]) && !isdigit(descriptionStart[0])) {
        paramBody.skipToNonName();
        string ref = string(descriptionStart, paramBody.fChar - descriptionStart);
        if (!std::all_of(ref.begin(), ref.end(), [](char c) { return isupper(c); })
                && !this->isDefined(paramBody, Resolvable::kYes)) {
            string errorStr = MarkType::kReturn == def->fMarkType ? "return" : "param";
            errorStr += " description must start with lower case";
            paramBody.reportError(errorStr.c_str());
            fAddRefFailed = true;
            return false;
        }
    }
    if ('.' == paramBody.fEnd[-1]) {
        paramBody.reportError("make param description a phrase; should not end with period");
        fAddRefFailed = true;
        return false;
    }
    return true;
}

void MdOut::childrenOut(Definition* def, const char* start) {
    const char* end;
    fLineCount = def->fLineCount;
    if (MarkType::kEnumClass == def->fMarkType) {
        fEnumClass = def;
    }
    Resolvable resolvable = this->resolvable(def);
    const Definition* prior = nullptr;
    for (auto& child : def->fChildren) {
        if (MarkType::kPhraseParam == child->fMarkType) {
            continue;
        }
        end = child->fStart;
        if (Resolvable::kNo != resolvable) {
            if (def->isStructOrClass() || MarkType::kEnumClass == def->fMarkType) {
                fNames = &def->asRoot()->fNames;
            }
            this->resolveOut(start, end, resolvable);
        }
        this->markTypeOut(child, &prior);
        start = child->fTerminator;
    }
    if (Resolvable::kNo != resolvable) {
        end = def->fContentEnd;
        if (MarkType::kFormula == def->fMarkType && ' ' == start[0]) {
            this->writeSpace();
        }
        this->resolveOut(start, end, resolvable);
    }
    if (MarkType::kEnumClass == def->fMarkType) {
        fEnumClass = nullptr;
    }
}

// output header for subtopic for all consts: name, value, short descriptions (#Line)
// output link to in context #Const with moderate description
void MdOut::summaryOut(const Definition* def, MarkType markType, string name) {
    this->writePending();
    SkASSERT(TableState::kNone == fTableState);
    this->mdHeaderOut(3);
    FPRINTF("%s", name.c_str());
    this->lfAlways(2);
    FPRINTF("%s", kTableDeclaration);  // <table> with style info
    this->lfAlways(1);
    FPRINTF("%s", MarkType::kConst == markType ? kAllConstTableHeader : kAllMemberTableHeader);
    this->lfAlways(1);
    bool odd = true;
    for (auto child : def->fChildren) {
        if (markType != child->fMarkType) {
            continue;
        }
        auto oneLiner = std::find_if(child->fChildren.begin(), child->fChildren.end(),
                [](const Definition* test){ return MarkType::kLine == test->fMarkType; } );
        if (child->fChildren.end() == oneLiner) {
            child->reportError<void>("missing #Line");
            continue;
        }
        FPRINTF("%s", odd ? kTR_Dark.c_str() : "  <tr>");
        this->lfAlways(1);
        if (MarkType::kConst == markType) {
            FPRINTF("%s", tableDataCodeRef(child).c_str());
            this->lfAlways(1);
            FPRINTF("%s", table_data_const(child, nullptr).c_str());
        } else {
            string memberType;
            string memberName = this->getMemberTypeName(child, &memberType);
            SkASSERT(MarkType::kMember == markType);
            FPRINTF("%s", out_table_data_description(memberType).c_str());
            this->lfAlways(1);
            FPRINTF("%s", tableDataCodeLocalRef(memberName).c_str());
        }
        this->lfAlways(1);
        FPRINTF("%s", out_table_data_description(*oneLiner).c_str());
        this->lfAlways(1);
        FPRINTF("%s", "  </tr>");
        this->lfAlways(1);
        odd = !odd;
    }
    FPRINTF("</table>");
    this->lfAlways(1);
}

Definition* MdOut::csParent() {
    if (!fRoot) {
        return nullptr;
    }
    Definition* csParent = fRoot->csParent();
    if (!csParent) {
        const Definition* topic = fRoot;
        while (topic && MarkType::kTopic != topic->fMarkType) {
            topic = topic->fParent;
        }
        for (auto child : topic->fChildren) {
            if (child->isStructOrClass() || MarkType::kTypedef == child->fMarkType) {
                csParent = child;
                break;
            }
        }
        SkASSERT(csParent || string::npos == fRoot->fFileName.find("Sk")
                || string::npos != fRoot->fFileName.find("SkBlendMode_Reference.bmh"));
    }
    return csParent;
}

bool MdOut::DefinedState::findLink(string word, string* linkPtr, bool addParens) {
    const NameMap* names = fNames;
    do {
        auto localIter = names->fRefMap.find(word);
        if (names->fRefMap.end() != localIter) {
            if ((fPriorDef = localIter->second)) {
                auto linkIter = names->fLinkMap.find(word);
                SkAssertResult(names->fLinkMap.end() != linkIter);
                *linkPtr = linkIter->second;
            }
            return true;
        }
        if (!names->fParent && isupper(word[0])) {
            SkASSERT(names == &fBmhParser->fGlobalNames);
            string lower = (char) tolower(word[0]) + word.substr(1);
            auto globalIter = names->fRefMap.find(lower);
            if (names->fRefMap.end() != globalIter) {
                if ((fPriorDef = globalIter->second)) {
                    auto lowerIter = names->fLinkMap.find(lower);
                    SkAssertResult(names->fLinkMap.end() != lowerIter);
                    *linkPtr = lowerIter->second;
                }
                return true;
            }
        }
        if (addParens) {
            string parenWord = word + "()";
            auto paramIter = names->fRefMap.find(parenWord);
            if (names->fRefMap.end() != paramIter) {
                if ((fPriorDef = paramIter->second)) {
                    auto parenIter = names->fLinkMap.find(parenWord);
                    SkAssertResult(names->fLinkMap.end() != parenIter);
                    *linkPtr = parenIter->second;
                }
                return true;
            }
        }
    } while ((names = names->fParent));
    return false;
}

bool MdOut::DefinedState::findLink(string word, string* linkPtr,
        unordered_map<string, Definition*>& map) {
    auto mapIter = map.find(word);
    if (map.end() != mapIter) {
        if ((fPriorDef = mapIter->second)) {
            *linkPtr = '#' + mapIter->second->fFiddle;
        }
        return true;
    }
    return false;
}

const Definition* MdOut::findParamType() {
    SkASSERT(fMethod);
    TextParser parser(fMethod->fFileName, fMethod->fStart, fMethod->fContentStart,
            fMethod->fLineCount);
    string lastFull;
    do {
        parser.skipToAlpha();
        if (parser.eof()) {
            return nullptr;
        }
        const char* word = parser.fChar;
        parser.skipFullName();
        SkASSERT(!parser.eof());
        string name = string(word, parser.fChar - word);
        if (fLastParam->fName == name) {
            const Definition* paramType = this->isDefined(parser, Resolvable::kOut);
            return paramType;
        }
        if (isupper(name[0])) {
            lastFull = name;
        }
    } while (true);
    return nullptr;
}

string MdOut::getMemberTypeName(const Definition* def, string* memberType) {
    TextParser parser(def->fFileName, def->fStart, def->fContentStart,
            def->fLineCount);
    parser.skipExact("#Member");
    parser.skipWhiteSpace();
    const char* typeStart = parser.fChar;
    const char* typeEnd = nullptr;
    const char* nameStart = nullptr;
    const char* nameEnd = nullptr;
    do {
        parser.skipToWhiteSpace();
        if (nameStart) {
            nameEnd = parser.fChar;
        }
        if (parser.eof()) {
            break;
        }
        const char* spaceLoc = parser.fChar;
        if (parser.skipWhiteSpace()) {
            typeEnd = spaceLoc;
            nameStart = parser.fChar;
        }
    } while (!parser.eof());
    SkASSERT(typeEnd);
    *memberType = string(typeStart, (int) (typeEnd - typeStart));
    replace_all(*memberType, " ", "&nbsp;");
    SkASSERT(nameStart);
    SkASSERT(nameEnd);
    return string(nameStart, (int) (nameEnd - nameStart));
}

bool MdOut::HasDetails(const Definition* def) {
    for (auto child : def->fChildren) {
        if (MarkType::kDetails == child->fMarkType) {
            return true;
        }
        if (MdOut::HasDetails(child)) {
            return true;
        }
    }
    return false;
}

void MdOut::htmlOut(string s) {
    SkASSERT(string::npos != s.find('<'));
    FPRINTF("%s", s.c_str());
}

const Definition* MdOut::isDefined(const TextParser& parser, Resolvable resolvable) {
    DefinedState s(*this, parser.fStart, parser.fEnd, resolvable);
    const char* start = parser.fStart;
    do {
        s.fSeparatorStart = start;
        start = s.skipWhiteSpace();
        s.skipParens();
        (void) s.nextSeparator(start);
        if (s.findEnd(start)) {
            return nullptr;
        }
        s.fWord = string(start, s.fEnd - start);
        s.setLower();
    } while (s.setPriorSpaceWord(&start));
    s.setLink();
    return s.fPriorDef;
}

string MdOut::linkName(const Definition* ref) const {
    string result = ref->fName;
    size_t under = result.find('_');
    if (string::npos != under) {
        string classPart = result.substr(0, under);
        string namePart = result.substr(under + 1, result.length());
        if (fRoot && (fRoot->fName == classPart
                || (fRoot->fParent && fRoot->fParent->fName == classPart))) {
            result = namePart;
        }
    }
    replace_all(result, "::", "_");
    return result;
}

static bool writeTableEnd(MarkType markType, Definition* def, const Definition** prior) {
    return markType != def->fMarkType && *prior && markType == (*prior)->fMarkType;
}

// Recursively build string with declarative code. Skip structs, classes, that
// have been built directly.
void MdOut::addCodeBlock(const Definition* def, string& result) const {
    const Definition* last = nullptr;
    bool wroteFunction = false;
    for (auto member : def->fChildren) {
        const Definition* prior = last;
        const char* priorTerminator = nullptr;
        if (prior) {
            priorTerminator = prior->fTerminator ? prior->fTerminator : prior->fContentEnd;
        }
        last = member;
        if (KeyWord::kIfndef == member->fKeyWord) {
            this->addCodeBlock(member, result);
            continue;
        }
        if (KeyWord::kClass == member->fKeyWord || KeyWord::kStruct == member->fKeyWord
                || KeyWord::kTemplate == member->fKeyWord) {
            if (!member->fChildren.size()) {
                continue;
            }
            // todo: Make sure this was written non-elided somewhere else
            // todo: provide indent value?
            string block = fIncludeParser.elidedCodeBlock(*member);
            // add italic link for elided body
            size_t brace = block.find('{');
            if (string::npos != brace) {
                string name = member->fName;
                if ("" == name) {
                    for (auto child : member->fChildren) {
                        if ("" != (name = child->fName)) {
                            break;
                        }
                    }
                }
                SkASSERT("" != name);
                string body = "\n    // <i>" + name + " interface</i>";
                block = block.substr(0, brace + 1) + body + block.substr(brace + 1);
            }
            this->stringAppend(result, block);
            continue;
        }
        if (KeyWord::kEnum == member->fKeyWord) {
            if (member->fChildren.empty()) {
                continue;
            }
            auto tokenIter = member->fTokens.begin();
            if (KeyWord::kEnum == member->fKeyWord && KeyWord::kClass == tokenIter->fKeyWord) {
                tokenIter = tokenIter->fTokens.begin();
            }
            while (Definition::Type::kWord != tokenIter->fType) {
                std::advance(tokenIter, 1);
            }
            const auto& token = *tokenIter;
            string name = string(token.fContentStart, token.length());
            SkASSERT(name.length() > 0);
            MarkType markType = KeyWord::kClass == member->fKeyWord
                    || KeyWord::kStruct == member->fKeyWord ? MarkType::kClass : MarkType::kEnum;
            // find bmh def or just find name of class / struct / enum ? (what if enum is nameless?)
            if (wroteFunction) {
                this->stringAppend(result, '\n');
                wroteFunction = false;
            }
            this->stringAppend(result,
                    fIncludeParser.codeBlock(markType, name, fInProgress));
            this->stringAppend(result, '\n');
            continue;
        }
        // Global function declarations are not preparsed very well;
        // make do by using the prior position to find the start
        if (Bracket::kParen == member->fBracket && prior) {
            TextParser function(member->fFileName, priorTerminator, member->fTerminator + 1,
                    member->fLineCount);
            this->stringAppend(result,
                    fIncludeParser.writeCodeBlock(function, MarkType::kFunction, 0));
            this->stringAppend(result, ";\n");
            wroteFunction = true;
            continue;
        }
        if (KeyWord::kTypedef == member->fKeyWord) {
            this->stringAppend(result, member);
            this->stringAppend(result, ";\n");
            continue;
        }
        if (KeyWord::kDefine == member->fKeyWord) {
            string body(member->fContentStart, member->length());
            if (string::npos != body.find('(')) {
                this->stringAppend(result, body);
                this->stringAppend(result, '\n');
            }
            continue;
        }
        if (KeyWord::kConstExpr == member->fKeyWord) {
            this->stringAppend(result, member);
            auto nextMember = def->fTokens.begin();
            unsigned tokenPos = member->fParentIndex + 1;
            SkASSERT(tokenPos < def->fTokens.size());
            std::advance(nextMember, tokenPos);
            while (member->fContentEnd >= nextMember->fContentStart) {
                std::advance(nextMember, 1);
                SkASSERT(++tokenPos < def->fTokens.size());
            }
            while (Punctuation::kSemicolon != nextMember->fPunctuation) {
                std::advance(nextMember, 1);
                SkASSERT(++tokenPos < def->fTokens.size());
            }
            TextParser between(member->fFileName, member->fContentEnd,
                    nextMember->fContentStart, member->fLineCount);
            between.skipWhiteSpace();
            if ('=' == between.peek()) {
                this->stringAppend(result, ' ');
                string middle(between.fChar, nextMember->fContentStart);
                this->stringAppend(result, middle);
                last = nullptr;
            } else {
                SkAssertResult(';' == between.peek());
            }
            this->stringAppend(result, ';');
            this->stringAppend(result, '\n');
            continue;
        }
    }
}

void MdOut::markTypeOut(Definition* def, const Definition** prior) {
    string printable = def->printableName();
    const char* textStart = def->fContentStart;
    bool lookForOneLiner = false;
    // #Param and #Const don't have markers to say when the last is seen, so detect that by looking
    // for a change in type.
    if (writeTableEnd(MarkType::kParam, def, prior) || writeTableEnd(MarkType::kConst, def, prior)
                || writeTableEnd(MarkType::kMember, def, prior)) {
        this->writePending();
        FPRINTF("</table>");
        this->lf(2);
        fTableState = TableState::kNone;
    }
    fLastDef = def;
    NameMap paramMap;
    switch (def->fMarkType) {
        case MarkType::kAlias:
            break;
        case MarkType::kAnchor: {
            if (fColumn > 0) {
                this->writeSpace();
            }
            this->writePending();
            TextParser parser(def);
            const char* start = parser.fChar;
            parser.skipToEndBracket((string(" ") + def->fMC + " ").c_str());
            string anchorText(start, parser.fChar - start);
            parser.skipExact((string(" ") + def->fMC + " ").c_str());
            string anchorLink(parser.fChar, parser.fEnd - parser.fChar);
            this->htmlOut(anchorRef(anchorLink, anchorText));
            } break;
        case MarkType::kBug:
            break;
        case MarkType::kClass:
        case MarkType::kStruct:
            fRoot = def->asRoot();
            this->lfAlways(2);
            if (MarkType::kStruct == def->fMarkType) {
                this->htmlOut(anchorDef(def->fFiddle, ""));
            } else {
                this->htmlOut(anchorDef(this->linkName(def), ""));
            }
            this->lfAlways(2);
            FPRINTF("---");
            this->lf(2);
            break;
        case MarkType::kCode:
            this->lfAlways(2);
            FPRINTF("<pre style=\"padding: 1em 1em 1em 1em;"
                    "width: 62.5em; background-color: #f0f0f0\">");
            this->lf(1);
            fResolveAndIndent = true;
            break;
        case MarkType::kColumn:
            this->writePending();
            if (fInList) {
                FPRINTF("    <td>");
            } else {
                FPRINTF("| ");
            }
            break;
        case MarkType::kComment:
            break;
        case MarkType::kMember:
        case MarkType::kConst: {
            bool isConst = MarkType::kConst == def->fMarkType;
            lookForOneLiner = false;
            fWroteSomething = false;
        // output consts for one parent with moderate descriptions
        // optional link to subtopic with longer descriptions, examples
            if (TableState::kNone == fTableState) {
                SkASSERT(!*prior || (isConst && MarkType::kConst != (*prior)->fMarkType)
                        || (!isConst && MarkType::kMember != (*prior)->fMarkType));
                if (isConst) {
                    this->mdHeaderOut(3);
                    this->writeString(this->fPopulators[SubtopicKeys::kConstants].fPlural);
                    this->lfAlways(2);
                }
                FPRINTF("%s", kTableDeclaration);
                fTableState = TableState::kRow;
                fOddRow = true;
                this->lfAlways(1);
                // look ahead to see if the details column has data or not
                fHasDetails = MdOut::HasDetails(def->fParent);
                FPRINTF("%s", fHasDetails ? \
                        (isConst ? kSubConstTableHeader : kSubMemberTableHeader) : \
                        (isConst ? kAllConstTableHeader : kAllMemberTableHeader));
                this->lfAlways(1);
            }
            if (TableState::kRow == fTableState) {
                this->writePending();
                FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : "  <tr>");
                fOddRow = !fOddRow;
                this->lfAlways(1);
                fTableState = TableState::kColumn;
            }
            this->writePending();
            if (isConst) {
                // TODO: if fHasDetails is true, could defer def and issue a ref instead
                // unclear if this is a good idea or not
                FPRINTF("%s", this->tableDataCodeDef(def).c_str());
                this->lfAlways(1);
                FPRINTF("%s", table_data_const(def, &textStart).c_str());
            } else {
                string memberType;
                string memberName = this->getMemberTypeName(def, &memberType);
                FPRINTF("%s", out_table_data_description(memberType).c_str());
                this->lfAlways(1);
                FPRINTF("%s", tableDataCodeDef(def->fFiddle, memberName).c_str());
            }
            this->lfAlways(1);
            if (fHasDetails) {
                string details;
                auto subtopic = std::find_if(def->fChildren.begin(), def->fChildren.end(),
                        [](const Definition* test){
                        return MarkType::kDetails == test->fMarkType; } );
                if (def->fChildren.end() != subtopic) {
                    string subtopicName = string((*subtopic)->fContentStart,
                            (int) ((*subtopic)->fContentEnd - (*subtopic)->fContentStart));
                    const Definition* parentSubtopic = def->subtopicParent();
                    SkASSERT(parentSubtopic);
                    string fullName = parentSubtopic->fFiddle + '_' + subtopicName;
                    if (fBmhParser.fTopicMap.end() == fBmhParser.fTopicMap.find(fullName)) {
                        (*subtopic)->reportError<void>("missing #Details subtopic");
                    }
             //       subtopicName = parentSubtopic->fName + '_' + subtopicName;
                    string noUnderscores = subtopicName;
                    replace_all(noUnderscores, "_", "&nbsp;");
                    details = this->anchorLocalRef(subtopicName, noUnderscores) + "&nbsp;";
                }
                FPRINTF("%s", out_table_data_details(details).c_str());
                this->lfAlways(1);
            }
            lookForOneLiner = true;  // if description is empty, use oneLiner data
            FPRINTF("%s", out_table_data_description_start().c_str()); // start of Description
            this->lfAlways(1);
        } break;
        case MarkType::kDescription:
            fInDescription = true;
            this->writePending();
            FPRINTF("%s", "<div>");
            break;
        case MarkType::kDetails:
            break;
        case MarkType::kDuration:
            break;
        case MarkType::kDefine:
        case MarkType::kEnum:
        case MarkType::kEnumClass:
            this->lfAlways(2);
            this->htmlOut(anchorDef(def->fFiddle, ""));
            this->lfAlways(2);
            FPRINTF("---");
            this->lf(2);
            break;
        case MarkType::kExample: {
            this->mdHeaderOut(3);
            FPRINTF("%s", "Example\n"
                            "\n");
            fHasFiddle = true;
            bool showGpu = false;
            bool gpuAndCpu = false;
            const Definition* platform = def->hasChild(MarkType::kPlatform);
            if (platform) {
                TextParser platParse(platform);
                fHasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
                showGpu = platParse.strnstr("gpu", platParse.fEnd);
                if (showGpu) {
                    gpuAndCpu = platParse.strnstr("cpu", platParse.fEnd);
                }
            }
            if (fHasFiddle) {
                SkASSERT(def->fHash.length() > 0);
                FPRINTF("<div><fiddle-embed name=\"%s\"", def->fHash.c_str());
                if (showGpu) {
                    FPRINTF("%s", " gpu=\"true\"");
                    if (gpuAndCpu) {
                        FPRINTF("%s", " cpu=\"true\"");
                    }
                }
                FPRINTF("%s", ">");
            } else {
                SkASSERT(def->fHash.length() == 0);
                FPRINTF("%s", "<pre style=\"padding: 1em 1em 1em 1em; font-size: 13px"
                        " width: 62.5em; background-color: #f0f0f0\">");
                this->lfAlways(1);
                if (def->fWrapper.length() > 0) {
                    FPRINTF("%s", def->fWrapper.c_str());
                }
                fLiteralAndIndent = true;
            }
            } break;
        case MarkType::kExternal:
            break;
        case MarkType::kFile:
            break;
        case MarkType::kFilter:
            break;
        case MarkType::kFormula:
            break;
        case MarkType::kFunction:
            break;
        case MarkType::kHeight:
            break;
        case MarkType::kIllustration: {
            string illustName = "Illustrations_" + def->fParent->fFiddle;
            string number = string(def->fContentStart, def->length());
            if (number.length() && "1" != number) {
                illustName += "_" + number;
            }
            auto illustIter = fBmhParser.fTopicMap.find(illustName);
            SkASSERT(fBmhParser.fTopicMap.end() != illustIter);
            Definition* illustDef = illustIter->second;
            SkASSERT(MarkType::kSubtopic == illustDef->fMarkType);
            SkASSERT(1 == illustDef->fChildren.size());
            Definition* illustExample = illustDef->fChildren[0];
            SkASSERT(MarkType::kExample == illustExample->fMarkType);
            string hash = illustExample->fHash;
            SkASSERT("" != hash);
            string title;
            this->writePending();
            FPRINTF("![%s](https://fiddle.skia.org/i/%s_raster.png \"%s\")",
                    def->fName.c_str(), hash.c_str(), title.c_str());
            this->lf(2);
        } break;
        case MarkType::kImage:
            break;
        case MarkType::kIn:
            break;
        case MarkType::kLegend:
            break;
        case MarkType::kLine:
            break;
        case MarkType::kLink:
            break;
        case MarkType::kList:
            fInList = true;
            fTableState = TableState::kRow;
            this->lfAlways(2);
            FPRINTF("%s", "<table>");
            this->lf(1);
            break;
        case MarkType::kLiteral:
            break;
        case MarkType::kMarkChar:
            fBmhParser.fMC = def->fContentStart[0];
            break;
        case MarkType::kMethod: {
            this->lfAlways(2);
			if (false && !def->isClone()) {
                string method_name = def->methodName();
                this->mdHeaderOutLF(2, 1);
                this->htmlOut(this->anchorDef(def->fFiddle, method_name));
			} else {
                this->htmlOut(this->anchorDef(def->fFiddle, ""));
            }
            this->lfAlways(2);
            FPRINTF("---");
			this->lf(2);

            // TODO: put in css spec that we can define somewhere else (if markup supports that)
            // TODO: 50em below should match limit = 80 in formatFunction()
            this->writePending();
            string formattedStr = def->formatFunction(Definition::Format::kIncludeReturn);
            string preformattedStr = preformat(formattedStr);
            string references = this->addReferences(&preformattedStr.front(),
                    &preformattedStr.back() + 1, Resolvable::kSimple);
            preformattedStr = references;
            this->htmlOut("<pre style=\"padding: 1em 1em 1em 1em; width: 62.5em;"
                    "background-color: #f0f0f0\">\n" + preformattedStr + "\n" + "</pre>");
            this->lf(2);
            fTableState = TableState::kNone;
            fMethod = def;
            Definition* iMethod = fIncludeParser.findMethod(*def);
            if (iMethod) {
                fMethod = iMethod;
                paramMap.fParent = &fBmhParser.fGlobalNames;
                paramMap.setParams(def, iMethod);
                fNames = &paramMap;
            }
            } break;
        case MarkType::kNoExample:
            break;
        case MarkType::kNoJustify:
            break;
        case MarkType::kOutdent:
            break;
        case MarkType::kParam: {
            TextParser paramParser(def->fFileName, def->fStart, def->fContentStart,
                    def->fLineCount);
            paramParser.skipWhiteSpace();
            SkASSERT(paramParser.startsWith("#Param"));
            paramParser.next(); // skip hash
            paramParser.skipToNonName(); // skip Param
            this->parameterHeaderOut(paramParser, prior, def);
        } break;
        case MarkType::kPhraseDef:
            // skip text and children
            *prior = def;
            return;
        case MarkType::kPhraseParam:
            SkDebugf(""); // convenient place to set a breakpoint
            break;
        case MarkType::kPhraseRef:
            if (fPhraseParams.end() != fPhraseParams.find(def->fName)) {
                if (fColumn > 0) {
                    this->writeSpace();
                }
                this->writeString(fPhraseParams[def->fName]);
                if (isspace(def->fContentStart[0])) {
                    this->writeSpace();
                }
            } else if (fBmhParser.fPhraseMap.end() == fBmhParser.fPhraseMap.find(def->fName)) {
                def->reportError<void>("missing phrase definition");
                fAddRefFailed = true;
            } else {
                if (fColumn) {
                    SkASSERT(' ' >= def->fStart[0]);
                    this->writeSpace();
                }
                Definition* phraseRef = fBmhParser.fPhraseMap.find(def->fName)->second;
                // def->fChildren are parameters to substitute phraseRef->fChildren,
                // phraseRef->fChildren has both param defines and references
                // def->fChildren must have the same number of entries as phaseRef->fChildren
                // which are kPhraseParam, and substitute one for one
                // Then, each kPhraseRef in phaseRef looks up the key and value
                fPhraseParams.clear();
                auto refKidsIter = phraseRef->fChildren.begin();
                for (auto child : def->fChildren) {
                    if (MarkType::kPhraseParam != child->fMarkType) {
                        // more work to do to support other types
                        this->reportError("phrase ref child must be param");
                    }
                    do {
                        if (refKidsIter == phraseRef->fChildren.end()) {
                            this->reportError("phrase def missing param");
                            break;
                        }
                        if (MarkType::kPhraseRef == (*refKidsIter)->fMarkType) {
                            continue;
                        }
                        if (MarkType::kPhraseParam != (*refKidsIter)->fMarkType) {
                            this->reportError("unexpected type in phrase def children");
                            break;
                        }
                        fPhraseParams[(*refKidsIter)->fName] = child->fName;
                        break;
                    } while (true);
                }
                this->childrenOut(phraseRef, phraseRef->fContentStart);
                fPhraseParams.clear();
                if (' ' >= def->fContentStart[0] && !fPendingLF) {
                    this->writeSpace();
                }
            }
            break;
        case MarkType::kPlatform:
            break;
        case MarkType::kPopulate: {
            Definition* parent = def->fParent;
            SkASSERT(parent);
            if (MarkType::kCode == parent->fMarkType) {
                auto inDef = std::find_if(parent->fChildren.begin(), parent->fChildren.end(),
                        [](const Definition* child) { return MarkType::kIn == child->fMarkType; });
                if (parent->fChildren.end() != inDef) {
                    auto filterDef = std::find_if(parent->fChildren.begin(),
                            parent->fChildren.end(), [](const Definition* child) {
                            return MarkType::kFilter == child->fMarkType; });
                    SkASSERT(parent->fChildren.end() != filterDef);
                    string codeBlock = fIncludeParser.filteredBlock(
                            string((*inDef)->fContentStart, (*inDef)->length()),
                            string((*filterDef)->fContentStart, (*filterDef)->length()));
                    this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
                            this->resolvable(parent));
                    break;
                }
                // find include matching code parent
                Definition* grand = parent->fParent;
                SkASSERT(grand);
                if (MarkType::kClass == grand->fMarkType
                        || MarkType::kStruct == grand->fMarkType
                        || MarkType::kEnum == grand->fMarkType
                        || MarkType::kEnumClass == grand->fMarkType
                        || MarkType::kTypedef == grand->fMarkType
                        || MarkType::kDefine == grand->fMarkType) {
                    string codeBlock = fIncludeParser.codeBlock(*grand, fInProgress);
                    this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
                            this->resolvable(parent));
                } else if (MarkType::kTopic == grand->fMarkType) {
                    // use bmh file name to find include file name
                    size_t start = grand->fFileName.rfind("Sk");
                    SkASSERT(start != string::npos);
                    size_t end = grand->fFileName.rfind("_Reference");
                    SkASSERT(end != string::npos && end > start);
                    string incName(grand->fFileName.substr(start, end - start));
                    const Definition* includeDef = fIncludeParser.include(incName + ".h");
                    SkASSERT(includeDef);
                    string codeBlock;
                    this->addCodeBlock(includeDef, codeBlock);
                    this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
                            this->resolvable(parent));
                } else {
                    SkASSERT(MarkType::kSubtopic == grand->fMarkType);
                    auto inTag = std::find_if(grand->fChildren.begin(), grand->fChildren.end(),
                            [](Definition* child){return MarkType::kIn == child->fMarkType;});
                    SkASSERT(grand->fChildren.end() != inTag);
                    auto filterTag = std::find_if(grand->fChildren.begin(), grand->fChildren.end(),
                            [](Definition* child){return MarkType::kFilter == child->fMarkType;});
                    SkASSERT(grand->fChildren.end() != filterTag);
                    string inContents((*inTag)->fContentStart, (*inTag)->length());
                    string filterContents((*filterTag)->fContentStart, (*filterTag)->length());
                    string filteredBlock = fIncludeParser.filteredBlock(inContents, filterContents);
                    this->resolveOut(filteredBlock.c_str(), filteredBlock.c_str()
                            + filteredBlock.length(), this->resolvable(parent));
                }
            } else {
                SkASSERT(MarkType::kMethod == parent->fMarkType);
                // retrieve parameters, return, description from include
                Definition* iMethod = fIncludeParser.findMethod(*parent);
                if (!iMethod) {  // deprecated or 'in progress' functions should not include populate
                    SkDebugf("#Populate found in deprecated or missing method %s\n", def->fName.c_str());
                    def->fParent->reportError<void>("Remove #Method");
                }
                bool wroteParam = false;
                SkASSERT(fMethod == iMethod);
                for (auto& entry : iMethod->fTokens) {
                    if (MarkType::kComment != entry.fMarkType) {
                        continue;
                    }
                    TextParser parser(&entry);
                    if (parser.skipExact("@param ")) { // write parameters, if any
                        this->parameterHeaderOut(parser, prior, def);
                        this->resolveOut(parser.fChar, parser.fEnd,
                                Resolvable::kInclude);
                        this->parameterTrailerOut();
                        wroteParam = true;
                        continue;
                    }
                    if (wroteParam) {
                        this->writePending();
                        FPRINTF("</table>");
                        this->lf(2);
                        fTableState = TableState::kNone;
                        wroteParam = false;
                    }
                    if (parser.skipExact("@return ")) { // write return, if any
                        this->returnHeaderOut(prior, def);
                        this->resolveOut(parser.fChar, parser.fEnd,
                                Resolvable::kInclude);
                        this->lf(2);
                        continue;
                    }
                    if (1 == entry.length() && '/' == entry.fContentStart[0]) {
                        continue;
                    }
                    if ("/!< " == string(entry.fContentStart, entry.length()).substr(0, 4)) {
                        continue;
                    }
                    const char* backwards = entry.fContentStart;
                    while (' ' == *--backwards)
                        ;
                    if ('\n' == backwards[0] && '\n' == backwards[-1]) {
                        this->lf(2);
                    }
                    this->resolveOut(entry.fContentStart, entry.fContentEnd,
                            Resolvable::kInclude);  // write description
                    this->lf(1);
                }
            }
            } break;
        case MarkType::kReturn:
            this->returnHeaderOut(prior, def);
            break;
        case MarkType::kRow:
            if (fInList) {
                FPRINTF("  <tr>");
                this->lf(1);
            }
            break;
        case MarkType::kSeeAlso:
            this->mdHeaderOut(3);
            FPRINTF("See Also");
            this->lf(2);
            break;
        case MarkType::kSet:
            break;
        case MarkType::kStdOut: {
            TextParser code(def);
            this->mdHeaderOut(4);
            FPRINTF(
                    "Example Output\n"
                    "\n"
                    "~~~~");
            this->lfAlways(1);
            code.skipSpace();
            while (!code.eof()) {
                const char* end = code.trimmedLineEnd();
                FPRINTF("%.*s\n", (int) (end - code.fChar), code.fChar);
                code.skipToLineStart();
            }
            FPRINTF("~~~~");
            this->lf(2);
            } break;
        case MarkType::kSubstitute:
            break;
        case MarkType::kSubtopic:
            fSubtopic = def->asRoot();
            if (false && SubtopicKeys::kOverview == def->fName) {
                this->writeString(def->fName);
            } else {
                this->lfAlways(2);
                this->htmlOut(anchorDef(def->fName, ""));
            }
            if (std::any_of(def->fChildren.begin(), def->fChildren.end(),
                    [](Definition* child) {
                    return MarkType::kSeeAlso == child->fMarkType
                    || MarkType::kExample == child->fMarkType
                    || MarkType::kNoExample == child->fMarkType;
            })) {
                this->lfAlways(2);
                FPRINTF("---");
            }
            this->lf(2);
#if 0
            // if a subtopic child is const, generate short table of const name, value, line desc
            if (std::any_of(def->fChildren.begin(), def->fChildren.end(),
                    [](Definition* child){return MarkType::kConst == child->fMarkType;})) {
                this->summaryOut(def, MarkType::kConst, fPopulators[SubtopicKeys::kConstants].fPlural);
            }
#endif
            // if a subtopic child is member, generate short table of const name, value, line desc
            if (std::any_of(def->fChildren.begin(), def->fChildren.end(),
                    [](Definition* child){return MarkType::kMember == child->fMarkType;})) {
                this->summaryOut(def, MarkType::kMember, fPopulators[SubtopicKeys::kMembers].fPlural);
            }
            break;
        case MarkType::kTable:
            this->lf(2);
            break;
        case MarkType::kTemplate:
            break;
        case MarkType::kText:
            if (def->fParent && MarkType::kFormula == def->fParent->fMarkType) {
                if (fColumn > 0) {
                    this->writeSpace();
                }
                this->writePending();
                this->htmlOut("<code>");
                this->resolveOut(def->fContentStart, def->fContentEnd,
                        Resolvable::kFormula);
                this->htmlOut("</code>");
            }
            break;
        case MarkType::kToDo:
            break;
        case MarkType::kTopic: {
            auto found = std::find_if(def->fChildren.begin(), def->fChildren.end(),
                    [](Definition* test) { return test->isStructOrClass(); } );
            bool hasClassOrStruct = def->fChildren.end() != found;
            fRoot = hasClassOrStruct ? (*found)->asRoot() : def->asRoot();
            fSubtopic = def->asRoot();
            bool isUndocumented = string::npos != def->fFileName.find("undocumented");
            if (!isUndocumented) {
                this->populateTables(def, fRoot);
            }
//            this->mdHeaderOut(1);
//            this->htmlOut(anchorDef(this->linkName(def), printable));
//            this->lf(1);
            } break;
        case MarkType::kTypedef:
            this->lfAlways(2);
            this->htmlOut(anchorDef(def->fFiddle, ""));
            this->lfAlways(2);
            FPRINTF("---");
            this->lf(2);
            break;
        case MarkType::kUnion:
            break;
        case MarkType::kVolatile:
            break;
        case MarkType::kWidth:
            break;
        default:
            SkDebugf("fatal error: MarkType::k%s unhandled in %s()\n",
                    BmhParser::kMarkProps[(int) def->fMarkType].fName, __func__);
            SkASSERT(0); // handle everything
            break;
    }
    this->childrenOut(def, textStart);
    switch (def->fMarkType) {  // post child work, at least for tables
        case MarkType::kAnchor:
            if (fColumn > 0) {
                this->writeSpace();
            }
            break;
        case MarkType::kClass:
        case MarkType::kStruct:
            if (TableState::kNone != fTableState) {
                this->writePending();
                FPRINTF("</table>");
                this->lf(2);
                fTableState = TableState::kNone;
            }
            if (def->csParent()) {
                fRoot = def->csParent()->asRoot();
            }
            break;
        case MarkType::kCode:
            fIndent = 0;
            this->lf(1);
            this->writePending();
            FPRINTF("</pre>");
            this->lf(2);
            fResolveAndIndent = false;
            break;
        case MarkType::kColumn:
            if (fInList) {
                this->writePending();
                FPRINTF("</td>");
                this->lfAlways(1);
            } else {
                FPRINTF(" ");
            }
            break;
        case MarkType::kDescription:
            this->writePending();
            FPRINTF("</div>");
            fInDescription = false;
            break;
        case MarkType::kEnum:
        case MarkType::kEnumClass:
            if (TableState::kNone != fTableState) {
                this->writePending();
                FPRINTF("</table>");
                this->lf(2);
                fTableState = TableState::kNone;
            }
            break;
        case MarkType::kExample:
            this->writePending();
            if (fHasFiddle) {
                FPRINTF("</fiddle-embed></div>");
            } else {
                this->lfAlways(1);
                if (def->fWrapper.length() > 0) {
                    FPRINTF("}");
                    this->lfAlways(1);
                }
                FPRINTF("</pre>");
            }
            this->lf(2);
            fLiteralAndIndent = false;
            break;
        case MarkType::kLink:
            this->writeString("</a>");
            this->writeSpace();
            break;
        case MarkType::kList:
            fInList = false;
            this->writePending();
            SkASSERT(TableState::kNone != fTableState);
            FPRINTF("</table>");
            this->lf(2);
            fTableState = TableState::kNone;
            break;
        case MarkType::kLegend: {
            SkASSERT(def->fChildren.size() == 1);
            const Definition* row = def->fChildren[0];
            SkASSERT(MarkType::kRow == row->fMarkType);
            size_t columnCount = row->fChildren.size();
            SkASSERT(columnCount > 0);
            this->writePending();
            for (size_t index = 0; index < columnCount; ++index) {
                FPRINTF("| --- ");
            }
            FPRINTF(" |");
            this->lf(1);
            } break;
        case MarkType::kMethod:
            fMethod = nullptr;
            fNames = fNames->fParent;
            break;
        case MarkType::kConst:
        case MarkType::kMember:
            if (lookForOneLiner && !fWroteSomething) {
                auto oneLiner = std::find_if(def->fChildren.begin(), def->fChildren.end(),
                        [](const Definition* test){ return MarkType::kLine == test->fMarkType; } );
                if (def->fChildren.end() != oneLiner) {
                    TextParser parser(*oneLiner);
                    parser.skipWhiteSpace();
                    parser.trimEnd();
                    FPRINTF("%.*s", (int) (parser.fEnd - parser.fChar), parser.fChar);
                }
                lookForOneLiner = false;
            }
        case MarkType::kParam:
            this->parameterTrailerOut();
            break;
        case MarkType::kReturn:
        case MarkType::kSeeAlso:
            this->lf(2);
            break;
        case MarkType::kRow:
            if (fInList) {
                FPRINTF("  </tr>");
            } else {
                FPRINTF("|");
            }
            this->lf(1);
            break;
        case MarkType::kTable:
            this->lf(2);
            break;
        case MarkType::kPhraseDef:
            break;
        case MarkType::kSubtopic:
            SkASSERT(def);
            do {
                def = def->fParent;
            } while (def && MarkType::kTopic != def->fMarkType
                    && MarkType::kSubtopic != def->fMarkType);
            SkASSERT(def);
            fSubtopic = def->asRoot();
            break;
        case MarkType::kTopic:
            fSubtopic = nullptr;
            break;
        default:
            break;
    }
    *prior = def;
}

void MdOut::mdHeaderOutLF(int depth, int lf) {
    this->lfAlways(lf);
    for (int index = 0; index < depth; ++index) {
        FPRINTF("#");
    }
    FPRINTF(" ");
}

void MdOut::parameterHeaderOut(TextParser& paramParser, const Definition** prior, Definition* def) {
    if (TableState::kNone == fTableState) {
        SkASSERT(!*prior || MarkType::kParam != (*prior)->fMarkType);
        this->mdHeaderOut(3);
        this->htmlOut(
                "Parameters\n"
                "\n"
                "<table>"
                );
        this->lf(1);
        fTableState = TableState::kRow;
    }
    if (TableState::kRow == fTableState) {
        FPRINTF("  <tr>");
        this->lf(1);
        fTableState = TableState::kColumn;
    }
    paramParser.skipSpace();
    const char* paramName = paramParser.fChar;
    paramParser.skipToSpace();
    string paramNameStr(paramName, (int) (paramParser.fChar - paramName));
    if (MarkType::kPopulate != def->fMarkType && !this->checkParamReturnBody(def)) {
        *prior = def;
        return;
    }
    string refNameStr = def->fParent->fFiddle + "_" + paramNameStr;
    this->htmlOut("    <td>" + this->anchorDef(refNameStr,
            "<code><strong>" + paramNameStr + "</strong></code>") + "</td>");
    this->lfAlways(1);
    FPRINTF("    <td>");
}

void MdOut::parameterTrailerOut() {
    SkASSERT(TableState::kColumn == fTableState);
    fTableState = TableState::kRow;
    this->writePending();
    FPRINTF("</td>");
    this->lfAlways(1);
    FPRINTF("  </tr>");
    this->lfAlways(1);
}

void MdOut::populateOne(Definition* def,
        unordered_map<string, RootDefinition::SubtopicContents>& populator) {
    if (MarkType::kConst == def->fMarkType) {
        populator[SubtopicKeys::kConstants].fMembers.push_back(def);
        return;
    }
    if (MarkType::kEnum == def->fMarkType || MarkType::kEnumClass == def->fMarkType) {
        populator[SubtopicKeys::kConstants].fMembers.push_back(def);
        return;
    }
    if (MarkType::kDefine == def->fMarkType) {
        populator[SubtopicKeys::kDefines].fMembers.push_back(def);
        return;
    }
    if (MarkType::kMember == def->fMarkType) {
        populator[SubtopicKeys::kMembers].fMembers.push_back(def);
        return;
    }
    if (MarkType::kTypedef == def->fMarkType) {
        populator[SubtopicKeys::kTypedefs].fMembers.push_back(def);
        return;
    }
    if (MarkType::kMethod != def->fMarkType) {
        return;
    }
    if (def->fClone) {
        return;
    }
    if (Definition::MethodType::kConstructor == def->fMethodType
            || Definition::MethodType::kDestructor == def->fMethodType) {
        populator[SubtopicKeys::kConstructors].fMembers.push_back(def);
        return;
    }
    if (Definition::MethodType::kOperator == def->fMethodType) {
        populator[SubtopicKeys::kOperators].fMembers.push_back(def);
        return;
    }
    populator[SubtopicKeys::kMemberFunctions].fMembers.push_back(def);
    const Definition* csParent = this->csParent();
    if (csParent) {
        if (0 == def->fName.find(csParent->fName + "::Make")
                || 0 == def->fName.find(csParent->fName + "::make")) {
            populator[SubtopicKeys::kConstructors].fMembers.push_back(def);
            return;
        }
    }
    for (auto item : def->fChildren) {
        if (MarkType::kIn == item->fMarkType) {
            string name(item->fContentStart, item->fContentEnd - item->fContentStart);
            populator[name].fMembers.push_back(def);
            populator[name].fShowClones = true;
            break;
        }
    }
}

void MdOut::populateTables(const Definition* def, RootDefinition* root) {
    for (auto child : def->fChildren) {
        if (MarkType::kSubtopic == child->fMarkType) {
            string name = child->fName;
            bool builtInTopic = name == SubtopicKeys::kOverview;
            for (auto item : SubtopicKeys::kGeneratedSubtopics) {
                builtInTopic |= name == item;
            }
            if (!builtInTopic) {
                string subname;
                const Definition* subtopic = child->subtopicParent();
                if (subtopic) {
                    subname = subtopic->fName + '_';
                }
                builtInTopic = name == subname + SubtopicKeys::kOverview;
                for (auto item : SubtopicKeys::kGeneratedSubtopics) {
                    builtInTopic |= name == subname + item;
                }
                if (!builtInTopic) {
                    root->populator(SubtopicKeys::kRelatedFunctions).fMembers.push_back(child);
                }
            }
            this->populateTables(child, root);
            continue;
        }
        if (child->isStructOrClass()) {
            if (fClassStack.size() > 0) {
                root->populator(MarkType::kStruct != child->fMarkType ? SubtopicKeys::kClasses :
                        SubtopicKeys::kStructs).fMembers.push_back(child);
            }
            fClassStack.push_back(child);
            this->populateTables(child, child->asRoot());
            fClassStack.pop_back();
            continue;
        }
        if (MarkType::kEnum == child->fMarkType || MarkType::kEnumClass == child->fMarkType) {
            this->populateTables(child, root);
        }
        this->populateOne(child, root->fPopulators);
    }
}

void MdOut::resolveOut(const char* start, const char* end, Resolvable resolvable) {
    if ((Resolvable::kLiteral == resolvable || fLiteralAndIndent ||
            fResolveAndIndent) && end > start) {
        int linefeeds = 0;
        while ('\n' == *start) {
            ++linefeeds;
            ++start;
        }
        if (fResolveAndIndent && linefeeds) {
            this->lf(linefeeds);
        }
        const char* spaceStart = start;
        while (' ' == *start) {
            ++start;
        }
        if (start > spaceStart) {
            fIndent = start - spaceStart;
        }
    }
    if (Resolvable::kLiteral == resolvable || fLiteralAndIndent) {
        this->writeBlockTrim(end - start, start);
        if ('\n' == end[-1]) {
            this->lf(1);
        }
        fIndent = 0;
        return;
    }
    // FIXME: this needs the markdown character present when the def was defined,
    // not the last markdown character the parser would have seen...
    while (fBmhParser.fMC == end[-1]) {
        --end;
    }
    if (start >= end) {
        return;
    }
    string resolved = this->addReferences(start, end, resolvable);
    trim_end_spaces(resolved);
    if (resolved.length()) {
        TextParser paragraph(fFileName, &*resolved.begin(), &*resolved.end(), fLineCount);
        while (!paragraph.eof()) {
            while ('\n' == paragraph.peek()) {
                paragraph.next();
                if (paragraph.eof()) {
                    return;
                }
            }
            const char* lineStart = paragraph.fChar;
            paragraph.skipWhiteSpace();
            const char* contentStart = paragraph.fChar;
            if (fResolveAndIndent && contentStart > lineStart) {
                this->writePending();
                this->indentToColumn(contentStart - lineStart);
            }
            paragraph.skipToEndBracket('\n');
            ptrdiff_t lineLength = paragraph.fChar - contentStart;
            if (lineLength) {
                while (lineLength && contentStart[lineLength - 1] <= ' ') {
                    --lineLength;
                }
                string str(contentStart, lineLength);
                this->writeString(str.c_str());
                fWroteSomething = !!lineLength;
            }
            if (paragraph.eof()) {
                break;
            }
            if ('\n' == paragraph.next()) {
                int linefeeds = 1;
                if (!paragraph.eof() && '\n' == paragraph.peek()) {
                    linefeeds = 2;
                }
                this->lf(linefeeds);
            }
        }
    }
}

void MdOut::returnHeaderOut(const Definition** prior, Definition* def) {
    this->mdHeaderOut(3);
    FPRINTF("Return Value");
    if (MarkType::kPopulate != def->fMarkType && !this->checkParamReturnBody(def)) {
        *prior = def;
        return;
    }
    this->lf(2);
}

void MdOut::rowOut(string col1, const Definition* col2) {
    FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : "  <tr>");
    this->lfAlways(1);
    FPRINTF("%s", kTD_Left.c_str());
    if ("" != col1) {
        this->writeString(col1);
    }
    FPRINTF("</td>");
    this->lfAlways(1);
    FPRINTF("%s", kTD_Left.c_str());
    TextParser parser(col2->fFileName, col2->fStart, col2->fContentStart, col2->fLineCount);
    parser.skipExact("#Method");
    parser.skipSpace();
    parser.trimEnd();
    string methodName(parser.fChar, parser.fEnd - parser.fChar);
    this->htmlOut(this->anchorRef("#" + col2->fFiddle, methodName));
    this->htmlOut("</td>");
    this->lfAlways(1);
    FPRINTF("  </tr>");
    this->lfAlways(1);
    fOddRow = !fOddRow;
}

void MdOut::rowOut(const char* name, string description, bool literalName) {
    FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : "  <tr>");
    this->lfAlways(1);
    FPRINTF("%s", kTD_Left.c_str());
    if (literalName) {
        if (strlen(name)) {
            this->writeString(name);
        }
    } else {
        this->resolveOut(name, name + strlen(name), Resolvable::kYes);
    }
    FPRINTF("</td>");
    this->lfAlways(1);
    FPRINTF("%s", kTD_Left.c_str());
    this->resolveOut(&description.front(), &description.back() + 1, Resolvable::kYes);
    FPRINTF("</td>");
    this->lfAlways(1);
    FPRINTF("  </tr>");
    this->lfAlways(1);
    fOddRow = !fOddRow;
}

void MdOut::subtopicsOut(Definition* def) {
    Definition* csParent = def->csParent();
    const Definition* subtopicParent = def->subtopicParent();
    const Definition* topicParent = def->topicParent();
    SkASSERT(subtopicParent);
    this->lfAlways(1);
    FPRINTF("%s", kTableDeclaration);
    this->lfAlways(1);
    FPRINTF("%s", kTopicsTableHeader);
    this->lfAlways(1);
    fOddRow = true;
    for (auto item : SubtopicKeys::kGeneratedSubtopics) {
        if (SubtopicKeys::kMemberFunctions == item) {
            continue;
        }
        for (auto entry : fRoot->populator(item).fMembers) {
            if ((csParent && entry->csParent() == csParent)
                    || entry->subtopicParent() == subtopicParent) {
                if (SubtopicKeys::kRelatedFunctions == item) {
                    (void) subtopicRowOut(entry->fName, entry); // report all errors
                    continue;
                }
                auto popItem = fPopulators.find(item);
                string description = popItem->second.fOneLiner;
                if (SubtopicKeys::kConstructors == item) {
                    description += " " + fRoot->fName;
                }
                string subtopic;
                if (subtopicParent != topicParent) {
                    subtopic = subtopicParent->fName + '_';
                }
                string link = this->anchorLocalRef(subtopic + item, popItem->second.fPlural);
                this->rowOut(link.c_str(), description, true);
                break;
            }
        }
    }
    FPRINTF("</table>");
    this->lfAlways(1);
}

void MdOut::subtopicOut(string name) {
    const Definition* topicParent = fSubtopic ? fSubtopic->topicParent() : nullptr;
    Definition* csParent = fRoot && fRoot->isStructOrClass() ? fRoot : this->csParent();
    if (!csParent) {
        auto csIter = std::find_if(topicParent->fChildren.begin(), topicParent->fChildren.end(),
                [](const Definition* def){ return MarkType::kEnum == def->fMarkType
                || MarkType::kEnumClass == def->fMarkType; } );
        SkASSERT(topicParent->fChildren.end() != csIter);
        csParent = *csIter;
    }
    SkASSERT(csParent);
    this->lfAlways(1);
    if (fPopulators.end() != fPopulators.find(name)) {
        const SubtopicDescriptions& tableDescriptions = this->populator(name);
        this->anchorDef(name, tableDescriptions.fPlural);
        this->lfAlways(1);
        if (tableDescriptions.fDetails.length()) {
            string details = csParent->fName;
            details += " " + tableDescriptions.fDetails;
            this->writeString(details);
            this->lfAlways(1);
        }
    } else {
        this->anchorDef(name, name);
        this->lfAlways(1);
    }
    if (SubtopicKeys::kMembers == name) {
        return; // members output their own table
    }
    const RootDefinition::SubtopicContents& tableContents = fRoot->populator(name.c_str());
    if (SubtopicKeys::kTypedefs == name && fSubtopic && MarkType::kTopic == fSubtopic->fMarkType) {
        topicParent = fSubtopic;
    }
    this->subtopicOut(name, tableContents.fMembers, csParent, topicParent,
            tableContents.fShowClones);
}

void MdOut::subtopicOut(string key, const vector<Definition*>& data, const Definition* csParent,
        const Definition* topicParent, bool showClones) {
    this->writeString(kTableDeclaration);
    this->lfAlways(1);
    this->writeSubtopicTableHeader(key);
    this->lfAlways(1);
    fOddRow = true;
    std::map<string, const Definition*> items;
    for (auto entry : data) {
        if (!BmhParser::IsExemplary(entry)) {
            continue;
        }
        if (entry->csParent() != csParent && entry->topicParent() != topicParent) {
            continue;
        }
        size_t start = entry->fName.find_last_of("::");
        if (MarkType::kConst == entry->fMarkType && entry->fParent
                && MarkType::kEnumClass == entry->fParent->fMarkType
                && string::npos != start && start > 1) {
            start = entry->fName.substr(0, start - 1).rfind("::");
        }
        string entryName = entry->fName.substr(string::npos == start ? 0 : start + 1);
        items[entryName] = entry;
    }
    for (auto entry : items) {
        if (!this->subtopicRowOut(entry.first, entry.second)) {
            return;
        }
        if (showClones && entry.second->fCloned) {
            int cloneNo = 2;
            string builder = entry.second->fName;
            if ("()" == builder.substr(builder.length() - 2)) {
                builder = builder.substr(0, builder.length() - 2);
            }
            builder += '_';
            this->rowOut("overloads", entry.second);
            do {
                string match = builder + to_string(cloneNo);
                auto child = csParent->findClone(match);
                if (!child) {
                    break;
                }
                this->rowOut("", child);
            } while (++cloneNo);
        }
    }
    FPRINTF("</table>");
    this->lf(2);
}

bool MdOut::subtopicRowOut(string keyName, const Definition* entry) {
    const Definition* oneLiner = nullptr;
    for (auto child : entry->fChildren) {
        if (MarkType::kLine == child->fMarkType) {
            oneLiner = child;
            break;
        }
    }
    if (!oneLiner) {
        TextParser parser(entry->fFileName, entry->fStart,
                entry->fContentStart, entry->fLineCount);
        return parser.reportError<bool>("missing #Line");
    }
    TextParser dummy(entry); // for reporting errors, which we won't do
    if (!this->isDefined(dummy, Resolvable::kOut)) {
        keyName = entry->fName;
        size_t doubleColon = keyName.find("::");
        SkASSERT(string::npos != doubleColon);
        keyName = keyName.substr(doubleColon + 2);
    }
    this->rowOut(keyName.c_str(), string(oneLiner->fContentStart,
            oneLiner->fContentEnd - oneLiner->fContentStart), false);
    return true;
}

void MdOut::writeSubtopicTableHeader(string key) {
    this->htmlOut("<tr>");
    this->htmlOut(kTH_Left);
    if (fPopulators.end() != fPopulators.find(key)) {
        this->writeString(fPopulators[key].fSingular);
    } else {
        this->writeString("Function");
    }
    this->htmlOut("</th>");
    this->lf(1);
    this->htmlOut(kTH_Left);
    this->writeString("Description");
    this->htmlOut("</th>");
    this->htmlOut("</tr>");
}

#undef kTH_Left