/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#include "cmDependsJavaParserHelper.h"

#include "cmDependsJavaLexer.h"
#include "cmSystemTools.h"

#include "cmsys/FStream.hxx"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utility>

int
cmDependsJava_yyparse(yyscan_t yyscanner);

cmDependsJavaParserHelper::cmDependsJavaParserHelper()
{
    this->CurrentDepth = 0;

    this->UnionsAvailable = 0;
    this->LastClassId     = 0;

    CurrentClass tl;
    tl.Name = "*";
    this->ClassStack.push_back(std::move(tl));
}

cmDependsJavaParserHelper::~cmDependsJavaParserHelper()
{
    this->CleanupParser();
}

void
cmDependsJavaParserHelper::CurrentClass::AddFileNamesForPrinting(
    std::vector<std::string>* files, const char* prefix, const char* sep) const
{
    std::string rname;
    if(prefix)
    {
        rname += prefix;
        rname += sep;
    }
    rname += this->Name;
    files->push_back(rname);
    for(CurrentClass const& nc : this->NestedClasses)
    {
        nc.AddFileNamesForPrinting(files, rname.c_str(), sep);
    }
}

void
cmDependsJavaParserHelper::DeallocateParserType(char** pt)
{
    if(!pt)
    {
        return;
    }
    if(!*pt)
    {
        return;
    }
    *pt = nullptr;
    this->UnionsAvailable--;
}

void
cmDependsJavaParserHelper::AddClassFound(const char* sclass)
{
    if(!sclass)
    {
        return;
    }
    for(std::string const& cf : this->ClassesFound)
    {
        if(cf == sclass)
        {
            return;
        }
    }
    this->ClassesFound.push_back(sclass);
}

void
cmDependsJavaParserHelper::AddPackagesImport(const char* sclass)
{
    for(std::string const& pi : this->PackagesImport)
    {
        if(pi == sclass)
        {
            return;
        }
    }
    this->PackagesImport.push_back(sclass);
}

void
cmDependsJavaParserHelper::SafePrintMissing(const char* str, int line, int cnt)
{
    if(str)
    {
        std::cout << line << " String " << cnt << " exists: ";
        unsigned int cc;
        for(cc = 0; cc < strlen(str); cc++)
        {
            unsigned char ch = str[cc];
            if(ch >= 32 && ch <= 126)
            {
                std::cout << static_cast<char>(ch);
            } else
            {
                std::cout << "<" << static_cast<int>(ch) << ">";
                break;
            }
        }
        std::cout << "- " << strlen(str) << std::endl;
    }
}
void
cmDependsJavaParserHelper::Print(const char* place, const char* str)
{
    if(this->Verbose)
    {
        std::cout << "[" << place << "=" << str << "]" << std::endl;
    }
}

void
cmDependsJavaParserHelper::CombineUnions(char** out, const char* in1,
                                         char** in2, const char* sep)
{
    size_t len = 1;
    if(in1)
    {
        len += strlen(in1);
    }
    if(*in2)
    {
        len += strlen(*in2);
    }
    if(sep)
    {
        len += strlen(sep);
    }
    *out    = new char[len];
    *out[0] = 0;
    if(in1)
    {
        strcat(*out, in1);
    }
    if(sep)
    {
        strcat(*out, sep);
    }
    if(*in2)
    {
        strcat(*out, *in2);
    }
    if(*in2)
    {
        this->DeallocateParserType(in2);
    }
    this->UnionsAvailable++;
}

void
cmDependsJavaParserHelper::CheckEmpty(int line, int cnt,
                                      cmDependsJavaParserHelper::ParserType* pt)
{
    int cc;
    int kk = -cnt + 1;
    for(cc = 1; cc <= cnt; cc++)
    {
        cmDependsJavaParserHelper::ParserType* cpt = pt + kk;
        this->SafePrintMissing(cpt->str, line, cc);
        kk++;
    }
}

void
cmDependsJavaParserHelper::PrepareElement(
    cmDependsJavaParserHelper::ParserType* me)
{
    // Inititalize self
    me->str = nullptr;
}

void
cmDependsJavaParserHelper::AllocateParserType(
    cmDependsJavaParserHelper::ParserType* pt, const char* str, int len)
{
    pt->str = nullptr;
    if(len == 0)
    {
        len = static_cast<int>(strlen(str));
    }
    if(len == 0)
    {
        return;
    }
    this->UnionsAvailable++;
    pt->str = new char[len + 1];
    strncpy(pt->str, str, len);
    pt->str[len] = 0;
    this->Allocates.push_back(pt->str);
}

void
cmDependsJavaParserHelper::StartClass(const char* cls)
{
    CurrentClass cl;
    cl.Name = cls;
    this->ClassStack.push_back(std::move(cl));

    this->CurrentDepth++;
}

void
cmDependsJavaParserHelper::EndClass()
{
    if(this->ClassStack.empty())
    {
        std::cerr << "Error when parsing. Current class is null" << std::endl;
        abort();
    }
    if(this->ClassStack.size() <= 1)
    {
        std::cerr << "Error when parsing. Parent class is null" << std::endl;
        abort();
    }
    CurrentClass& current = this->ClassStack.back();
    CurrentClass& parent  = this->ClassStack[this->ClassStack.size() - 2];
    this->CurrentDepth--;
    parent.NestedClasses.push_back(current);
    this->ClassStack.pop_back();
}

void
cmDependsJavaParserHelper::PrintClasses()
{
    if(this->ClassStack.empty())
    {
        std::cerr << "Error when parsing. No classes on class stack"
                  << std::endl;
        abort();
    }
    for(std::string const& f : this->GetFilesProduced())
    {
        std::cout << "  " << f << ".class" << std::endl;
    }
}

std::vector<std::string>
cmDependsJavaParserHelper::GetFilesProduced()
{
    std::vector<std::string> files;
    CurrentClass const&      toplevel = this->ClassStack.front();
    for(CurrentClass const& nc : toplevel.NestedClasses)
    {
        nc.AddFileNamesForPrinting(&files, nullptr, "$");
    }
    return files;
}

int
cmDependsJavaParserHelper::ParseString(const char* str, int verb)
{
    if(!str)
    {
        return 0;
    }
    this->Verbose        = verb;
    this->InputBuffer    = str;
    this->InputBufferPos = 0;
    this->CurrentLine    = 0;

    yyscan_t yyscanner;
    cmDependsJava_yylex_init(&yyscanner);
    cmDependsJava_yyset_extra(this, yyscanner);
    int res = cmDependsJava_yyparse(yyscanner);
    cmDependsJava_yylex_destroy(yyscanner);
    if(res != 0)
    {
        std::cout << "JP_Parse returned: " << res << std::endl;
        return 0;
    }

    if(verb)
    {
        if(!this->CurrentPackage.empty())
        {
            std::cout << "Current package is: " << this->CurrentPackage
                      << std::endl;
        }
        std::cout << "Imports packages:";
        if(!this->PackagesImport.empty())
        {
            std::vector<std::string>::iterator it;
            for(it = this->PackagesImport.begin();
                it != this->PackagesImport.end(); ++it)
            {
                std::cout << " " << *it;
            }
        }
        std::cout << std::endl;
        std::cout << "Depends on:";
        if(!this->ClassesFound.empty())
        {
            for(std::string const& cf : this->ClassesFound)
            {
                std::cout << " " << cf;
            }
        }
        std::cout << std::endl;
        std::cout << "Generated files:" << std::endl;
        this->PrintClasses();
        if(this->UnionsAvailable != 0)
        {
            std::cout << "There are still " << this->UnionsAvailable
                      << " unions available" << std::endl;
        }
    }
    this->CleanupParser();
    return 1;
}

void
cmDependsJavaParserHelper::CleanupParser()
{
    for(char* allocate : this->Allocates)
    {
        delete[] allocate;
    }
    this->Allocates.erase(this->Allocates.begin(), this->Allocates.end());
}

int
cmDependsJavaParserHelper::LexInput(char* buf, int maxlen)
{
    if(maxlen < 1)
    {
        return 0;
    }
    if(this->InputBufferPos < this->InputBuffer.size())
    {
        buf[0] = this->InputBuffer[this->InputBufferPos++];
        if(buf[0] == '\n')
        {
            this->CurrentLine++;
        }
        return 1;
    }
    buf[0] = '\n';
    return 0;
}
void
cmDependsJavaParserHelper::Error(const char* str)
{
    unsigned long pos = static_cast<unsigned long>(this->InputBufferPos);
    fprintf(stderr, "JPError: %s (%lu / Line: %d)\n", str, pos,
            this->CurrentLine);
    int cc;
    std::cerr << "String: [";
    for(cc = 0;
        cc < 30 && *(this->InputBuffer.c_str() + this->InputBufferPos + cc);
        cc++)
    {
        std::cerr << *(this->InputBuffer.c_str() + this->InputBufferPos + cc);
    }
    std::cerr << "]" << std::endl;
}

void
cmDependsJavaParserHelper::UpdateCombine(const char* str1, const char* str2)
{
    if(this->CurrentCombine.empty() && str1 != nullptr)
    {
        this->CurrentCombine = str1;
    }
    this->CurrentCombine += ".";
    this->CurrentCombine += str2;
}

int
cmDependsJavaParserHelper::ParseFile(const char* file)
{
    if(!cmSystemTools::FileExists(file))
    {
        return 0;
    }
    cmsys::ifstream ifs(file);
    if(!ifs)
    {
        return 0;
    }

    std::string fullfile;
    std::string line;
    while(cmSystemTools::GetLineFromStream(ifs, line))
    {
        fullfile += line + "\n";
    }
    return this->ParseString(fullfile.c_str(), 0);
}
