/***************************************************************************
                     htmlgenerator.cpp  -  description
                             -------------------

    copyright            : (C) 2007-2024 by Andre Simon
    email                : a.simon@mailbox.org
 ***************************************************************************/

/*
This file is part of ANSIFilter.

ANSIFilter is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

ANSIFilter is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with ANSIFilter.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <fstream>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <string_view>
#include <unordered_map>

#include "htmlgenerator.h"
#include "version.h"

namespace ansifilter
{

HtmlGenerator::HtmlGenerator ():
    CodeGenerator(HTML),
    fileSuffix(".html")
{
    newLineTag="\n";
    styleCommentOpen="/*";
    styleCommentClose="*/";
    spacer=" ";
}

string HtmlGenerator::getOpenTag()
{
    ostringstream fmtStream;
    string attrName("style");

    if (applyDynStyles){
        attrName = "class";

        ostringstream fgColStream;
         if (elementStyle.isFgColorSet()) {
            fgColStream << elementStyle.getFgColour().getRed(HTML)
                        << elementStyle.getFgColour().getGreen(HTML)
                        << elementStyle.getFgColour().getBlue(HTML);
         }

        ostringstream bgColStream;
        if (elementStyle.isBgColorSet()) {

            bgColStream << elementStyle.getBgColour().getRed(HTML)
                        << elementStyle.getBgColour().getGreen(HTML)
                        << elementStyle.getBgColour().getBlue(HTML);
        }

        StyleInfo sInfo( fgColStream.str(), bgColStream.str(),
                         elementStyle.isBold(), elementStyle.isItalic(), elementStyle.isConceal(),
                         elementStyle.isBlink(), elementStyle.isUnderline() );

        auto fit = std::find(documentStyles.begin(), documentStyles.end(), sInfo );
        if (fit == documentStyles.end()){
            documentStyles.push_back(sInfo);
            fmtStream << "af_"<< documentStyles.size();
        } else {
            fmtStream << "af_"<< 1+(int)std::distance(documentStyles.begin(), fit);
        }

    } else {

        if (elementStyle.isBold()) {
            fmtStream<< "font-weight:bold;";
        }
        if (elementStyle.isItalic()) {
            fmtStream<< "font-style:italic;";
        }
        if (elementStyle.isBlink()) {
            fmtStream<< "text-decoration:blink;";
        }
        if (elementStyle.isUnderline()) {
            fmtStream<< "text-decoration:underline;";
        }
        if (elementStyle.isConceal()) {
            fmtStream<< "display:none;";
        }

        if (elementStyle.isFgColorSet()) {
            fmtStream << "color:#"
                    << elementStyle.getFgColour().getRed(HTML)
                    << elementStyle.getFgColour().getGreen(HTML)
                    << elementStyle.getFgColour().getBlue(HTML)
                    << ";";
        }

        if (elementStyle.isBgColorSet()) {
            fmtStream <<"background-color:#"
                    << elementStyle.getBgColour().getRed(HTML)
                    << elementStyle.getBgColour().getGreen(HTML)
                    << elementStyle.getBgColour().getBlue(HTML)
                    <<";";
        }
    }


    string fmt  = fmtStream.str();
    tagIsOpen = fmt.size()>0;
    if (tagIsOpen) {
        ostringstream spanTag;
        spanTag<< "<span "<<attrName<<"=\""<<fmt<<"\">";
        return spanTag.str();
    }
    return "";
}

string HtmlGenerator::getCloseTag()
{
    string retVal = tagIsOpen ? "</span>"  : "";
    tagIsOpen = false;
    return retVal;
}

string HtmlGenerator::getGeneratorComment()
{
    ostringstream s;
    s << "<!--HTML generated by ansifilter "
      << ANSIFILTER_VERSION << ", " <<  ANSIFILTER_URL <<"-->\n";

    return s.str();
}

string HtmlGenerator::getHeader()
{
    ostringstream os;
    os << "<!DOCTYPE html>"
       << "\n<html>\n<head>\n";
    if (encodingDefined()) {
        os << "<meta charset=\""
           << encoding
           << "\">\n";
    }

    os << "<style type=\"text/css\">\n";
    os << "pre {\n";
    os << "  font-family:"<< font << ";\n";
    os << "  font-size:"<< fontSize << ";\n";

    if (parseCP437) {
        os << "  color: #e5e5e5;\n";
    }

    os << "}\n\n";
    os << ".af_line {\n";
    os << "  color: gray;\n";
    os << "  text-decoration: none;\n";
    os << "}\n\n";

    if (parseCP437 || parseAsciiBin || parseAsciiTundra) {
     os << "body {  background-color: black; } \n";
    }
    os << "</style>\n";

    if (!styleSheetPath.empty()) {
        os << R"(<link rel="stylesheet" type="text/css" href=")"
           << styleSheetPath << "\">\n";
    }
    os << "<title>" << docTitle << "</title>\n";
    os << "</head>\n<body>\n<pre>";

    return os.str();
}

string HtmlGenerator::getFooter()
{
    string footer;
    footer += getCloseTag();
    footer += "</pre>\n</body>\n</html>\n";

     if (!omitVersionInfo)
        footer += getGeneratorComment();

    return footer;
}

void HtmlGenerator::printBody()
{
    processInput();
}

void HtmlGenerator::insertLineNumber ()
{
  if ( showLineNumbers && !parseCP437) {

        ostringstream lnum;
        lnum << setw ( 5 ) << right;
        if ( numberCurrentLine ) {

            lnum << lineNumber;
            if (addFunnyAnchors)
                *out << "<a href=\"#l_" << lineNumber<< "\"";
            else
                *out << "<span";

            if (addAnchors) {
                *out << " id=\"l_" << lineNumber<< "\" ";
            }
            *out << " class=\"af_line\">";

            *out <<lnum.str() << ( addFunnyAnchors  ? "</a> " : "</span> ");
        } else {
            *out << lnum.str(); //for indentation
        }
    }
}

string HtmlGenerator::getHyperlink(std::string_view uri, std::string_view txt){
    ostringstream os;
    os << "<a href='" << uri << "'>" << txt << "</a>";
    return os.str();
}

bool HtmlGenerator::printDynamicStyleFile ( const string &outPath ) {

    //do not overwrite
    ifstream infile(outPath.c_str());
    if (infile.good()) return true;

    ofstream indexfile ( outPath.c_str() );

    if ( !indexfile.fail() ) {
        indexfile << "/* CSS generated by ansifilter - styles derived from document formatting\n   Ansifilter will not overwrite this file\n*/\n";

        for (unsigned int i=0; i<documentStyles.size();i++){
            StyleInfo sInfo = documentStyles[i];
            indexfile << "span.af_" << (i+1) <<" {";

            if (sInfo.isBold) {
                indexfile<< "font-weight:bold;";
            }
            if (sInfo.isItalic) {
                indexfile<< "font-style:italic;";
            }
            if (sInfo.isBlink) {
                indexfile<< "text-decoration:blink;";
            }
            if (sInfo.isUnderLine) {
                indexfile<< "text-decoration:underline;";
            }
            if (sInfo.isConcealed) {
                indexfile<< "display:none;";
            }

            if (sInfo.fgColor.length()) {
                indexfile << "color:#"
                          << sInfo.fgColor
                          << ";";
            }

            if (sInfo.bgColor.length()) {
                indexfile << "background-color:#"
                          << sInfo.bgColor
                          << ";";
            }

            indexfile << "}\n";
        }


    } else {
        return false;
    }
    return true;
}

string HtmlGenerator::maskCharacter(unsigned char c)
{
    switch (c) {
    case '<' :
        return "&lt;";
        break;
    case '>' :
        return "&gt;";
        break;
    case '&' :
        return "&amp;";
        break;
    case '\"' :
        return "&quot;";
        break;
    case '\'' :
      return "&apos;";
      break;
    case '\t' : // see deletion of nonprintable chars below
        return "\t";
        break;

    case '@' :
        return "&#64;";
        break;

    default :
        if (c>0x1f ) { // printable?
            return  string( 1, c );
        } else {
            return "";
        }
    }
}

std::string HtmlGenerator::maskCP437Character(unsigned char c) {
    static const std::unordered_map<unsigned char, std::string> charMap = {
        {0x00, " "},
        {'<', "&lt;"},
        {'>', "&gt;"},
        {'&', "&amp;"},
        {'\"', "&quot;"},
        {'\'', "&apos;"},
        {'\t', "\t"},
        {0x01, "&#x263a;"},
        {0x02, "&#x263b;"},
        {0x03, "&#x2665;"},
        {0x04, "&#x2666;"},
        {0x05, "&#x2663;"},
        {0x06, "&#x2660;"},
        {0x08, "&#x25d8;"},
        {0x0a, "&#x25d9;"},
        {0x0b, "&#x2642;"},
        {0x0c, "&#x2640;"},
        {0x10, "&#x25BA;"},
        {0x11, "&#x25C4;"},
        {0x12, "&#x2195;"},
        {0x13, "&#x203C;"},
        {0x14, "&#x00b6;"},
        {0x15, "&#x00a7;"},
        {0x16, "&#x25ac;"},
        {0x17, "&#x21A8;"},
        {0x18, "&#x2191;"},
        {0x19, "&#x2193;"},
        {0x1a, "&#x2192;"},
        {0x1b, "&#x2190;"},
        {0x1c, "&#x221F;"},
        {0x1d, "&#x2194;"},
        {0x1e, "&#x25B2;"},
        {0x1f, "&#x25BC;"},
        {0x80, "&#x00c7;"},
        {0x81, "&#x00fc;"},
        {0x82, "&#x00e9;"},
        {0x83, "&#x00e2;"},
        {0x84, "&#x00e4;"},
        {0x85, "&#x00e0;"},
        {0x86, "&#x00e5;"},
        {0x87, "&#x00e7;"},
        {0x88, "&#x00ea;"},
        {0x89, "&#x00eb;"},
        {0x8a, "&#x00e8;"},
        {0x8b, "&#x00ef;"},
        {0x8c, "&#x00ee;"},
        {0x8d, "&#x00ec;"},
        {0x8e, "&#x00c4;"},
        {0x8f, "&#x00c5;"},
        {0x90, "&#x00c9;"},
        {0x91, "&#x00e6;"},
        {0x92, "&#x00c6;"},
        {0x93, "&#x00f4;"},
        {0x94, "&#x00f6;"},
        {0x95, "&#x00f2;"},
        {0x96, "&#x00fb;"},
        {0x97, "&#x00f9;"},
        {0x98, "&#x00ff;"},
        {0x99, "&#x00d6;"},
        {0x9a, "&#x00dc;"},
        {0x9b, "&#x00a2;"},
        {0x9c, "&#x00a3;"},
        {0x9d, "&#x00a5;"},
        {0x9e, "&#x20a7;"},
        {0x9f, "&#x0192;"},
        {0xa0, "&#x00e1;"},
        {0xa1, "&#x00ed;"},
        {0xa2, "&#x00f3;"},
        {0xa3, "&#x00fa;"},
        {0xa4, "&#x00f1;"},
        {0xa5, "&#x00d1;"},
        {0xa6, "&#x00aa;"},
        {0xa7, "&#x00ba;"},
        {0xa8, "&#x00bf;"},
        {0xa9, "&#x2310;"},
        {0xaa, "&#x00ac;"},
        {0xab, "&#x00bd;"},
        {0xac, "&#x00bc;"},
        {0xad, "&#x00a1;"},
        {0xae, "&#x00ab;"},
        {0xaf, "&#x00bb;"},
        {0xb0, "&#9617;"},
        {0xb1, "&#9618;"},
        {0xb2, "&#9619;"},
        {0xb3, "&#9474;"},
        {0xb4, "&#9508;"},
        {0xb5, "&#9569;"},
        {0xb6, "&#9570;"},
        {0xb7, "&#9558;"},
        {0xb8, "&#9557;"},
        {0xb9, "&#9571;"},
        {0xba, "&#9553;"},
        {0xbb, "&#9559;"},
        {0xbc, "&#9565;"},
        {0xbd, "&#9564;"},
        {0xbe, "&#9563;"},
        {0xbf, "&#9488;"},
        {0xc0, "&#9492;"},
        {0xc1, "&#9524;"},
        {0xc2, "&#9516;"},
        {0xc3, "&#9500;"},
        {0xc4, "&#9472;"},
        {0xc5, "&#9532;"},
        {0xc6, "&#9566;"},
        {0xc7, "&#9567;"},
        {0xc8, "&#9562;"},
        {0xc9, "&#9556;"},
        {0xca, "&#9577;"},
        {0xcb, "&#9574;"},
        {0xcc, "&#9568;"},
        {0xcd, "&#9552;"},
        {0xce, "&#9580;"},
        {0xcf, "&#9575;"},
        {0xd0, "&#9576;"},
        {0xd1, "&#9572;"},
        {0xd2, "&#9573;"},
        {0xd3, "&#9561;"},
        {0xd4, "&#9560;"},
        {0xd5, "&#9554;"},
        {0xd6, "&#9555;"},
        {0xd7, "&#9579;"},
        {0xd8, "&#9578;"},
        {0xd9, "&#9496;"},
        {0xda, "&#9484;"},
        {0xdb, "&#9608;"},
        {0xdc, "&#9604;"},
        {0xdd, "&#9612;"},
        {0xde, "&#9616;"},
        {0xdf, "&#9600;"},
        {0xe0, "&#x03b1;"},
        {0xe1, "&#x00df;"},
        {0xe2, "&#x0393;"},
        {0xe3, "&#x03c0;"},
        {0xe4, "&#x03a3;"},
        {0xe5, "&#x03c3;"},
        {0xe6, "&#x00b5;"},
        {0xe7, "&#x03c4;"},
        {0xe8, "&#x03a6;"},
        {0xe9, "&#x0398;"},
        {0xea, "&#x03a9;"},
        {0xeb, "&#x03b4;"},
        {0xec, "&#x221e;"},
        {0xed, "&#x03c6;"},
        {0xee, "&#x03b5;"},
        {0xef, "&#x2229;"},
        {0xf0, "&#x2261;"},
        {0xf1, "&#x00b1;"},
        {0xf2, "&#x2265;"},
        {0xf3, "&#x2264;"},
        {0xf4, "&#x2320;"},
        {0xf5, "&#x2321;"},
        {0xf6, "&#x00f7;"},
        {0xf7, "&#x2248;"},
        {0xf8, "&#x00b0;"},
        {0xf9, "&#x2219;"},
        {0xfa, "&#x00b7;"},
        {0xfb, "&#x221a;"},
        {0xfc, "&#x207f;"},
        {0xfd, "&#x00b2;"},
        {0xfe, "&#x25a0;"},
        {0xff, "&#x00a0;"}
    };

    auto it = charMap.find(c);
    return it != charMap.end() ? it->second : std::string(1, c);
}

}
