
#include "makefilegen.h"

#include <sdk.h>
#ifndef CB_PRECOMP
    #include <wx/file.h>

    #include <cbproject.h>
    #include <compiler.h>
    #include <compilerfactory.h>
#endif
#include <map>

#include "makefiledlg.h"
#include "nativeparserf.h"

void MakefileGen::GenerateMakefile(cbProject* project, ProjectDependencies* projDep, NativeParserF* pNativeParser)
{
    if (!project || !projDep)
        return;

    ProjectBuildTarget* buildTarget = project->GetBuildTarget(project->GetActiveBuildTarget());
    if (!buildTarget)
        return;

    wxString projDir = project->GetBasePath();
    wxFileName mffn = wxFileName(projDir, "Makefile");
    if (!mffn.IsOk())
        return;
    if (!SelectMikefileName(mffn))
        return;

    wxFile mfile;
    if(!mfile.Create(mffn.GetFullPath(), true))
    {
        cbMessageBox("Makefile can't be created!", _("Error"), wxICON_ERROR);
        return;
    }

    mfile.Write("#\n# This Makefile was generated by Code::Blocks IDE.\n#\n");

    // Add project files to a map. Purpose: get sorted list of files.
    std::map<wxString,ProjectFile*> projFilesMap;
    FilesList& filesList = buildTarget->GetFilesList();
    FilesList::iterator it;
    for( it = filesList.begin(); it != filesList.end(); ++it )
    {
        ProjectFile* projFile = *it;
        const pfDetails& pfd = projFile->GetFileDetails(buildTarget);
        projFilesMap[pfd.source_file_absolute_native] = projFile;
    }

    wxArrayString src_dirs;
    wxArrayString src_ext;
    wxArrayString src_files;
    wxArrayString obj_files;
    wxString sfiles;
    wxString ofiles;

    for (auto const& p : projFilesMap)
    {
        ProjectFile* projFile = p.second;
        const pfDetails& pfd = projFile->GetFileDetails(buildTarget);

        wxFileName sfn(pfd.source_file_absolute_native);
        sfn.MakeRelativeTo(mffn.GetPath(wxPATH_GET_SEPARATOR));
        wxString dir = sfn.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR, wxPATH_UNIX);
        int didx = src_dirs.Index(dir);
        if (didx == wxNOT_FOUND)
            didx = src_dirs.Add(dir);
        wxString ext = sfn.GetExt();
        ext << "d" << didx+1;
        int eidx = src_ext.Index(ext);
        if (eidx == wxNOT_FOUND)
        {
            eidx = src_ext.Add(ext);
            sfiles = wxEmptyString;
            src_files.Add(wxEmptyString);
            ofiles = wxEmptyString;
            obj_files.Add(wxEmptyString);
        }
        else
        {
            sfiles = src_files.Item(eidx);
            ofiles = obj_files.Item(eidx);
        }

        sfiles << sfn.GetFullName() << " \\\n";
        src_files[eidx] = sfiles;

        if (projFile->compile)
        {
            ofiles << sfn.GetName() << ".o" << " \\\n";
            obj_files[eidx] = ofiles;
        }
    }
    for (size_t i=0; i<src_ext.Count(); i++)
    {
        wxString str;
        str << "\nSRCS_" << src_ext.Item(i) << " = \\\n";
        mfile.Write(str);
        mfile.Write(src_files[i].Mid(0,src_files[i].Find('\\', true)));
        mfile.Write("\n");
    }
    for (size_t i=0; i<src_ext.Count(); i++)
    {
        wxString str;
        str << "\nOBJS_" << src_ext.Item(i) << " = \\\n";
        mfile.Write(str);
        mfile.Write(obj_files[i].Mid(0,obj_files[i].Find('\\', true)));
        mfile.Write("\n");
    }

    wxString objdir = "OBJS_DIR = ";

    for (auto const& p : projFilesMap)
    {
        ProjectFile* projFile = p.second;
        const pfDetails& pfd = projFile->GetFileDetails(buildTarget);

        wxFileName sfn(pfd.source_file_absolute_native);
        wxString ffpath = sfn.GetFullName();
        if (pNativeParser->IsFileFortran(ffpath) && projFile->compile)
        {
            wxFileName ofn(pfd.object_file_absolute_native);
            ofn.MakeRelativeTo(mffn.GetPath(wxPATH_GET_SEPARATOR));
            ofn.SetExt("o");
            objdir << ofn.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR, wxPATH_UNIX) + "\n";
            break;
        }
    }

    bool containsNonFortranFiles = false;

    wxString depsFiles;
    for (auto const& p : projFilesMap)
    {
        ProjectFile* projFile = p.second;
        const pfDetails& pfd = projFile->GetFileDetails(buildTarget);

        if (pNativeParser->IsFileFortran(pfd.source_file) && projFile->compile)
        {
            wxFileName sfn(pfd.source_file);
            sfn.SetExt("o");
            depsFiles << sfn.GetFullName();
            wxFileName sfn2(pfd.source_file);
            depsFiles << ": \\\n    " << sfn2.GetFullName();
            wxArrayString use;
            projDep->GetUseFilesFile(pfd.source_file_absolute_native, use);
            for (size_t i=0; i<use.size(); i++)
            {
                wxFileName ufn(use.Item(i));
                ufn.SetExt("o");
                depsFiles << " \\\n    " << ufn.GetFullName();
            }

            wxArrayString extends;
            projDep->GetExtendsFilesFile(pfd.source_file_absolute_native, extends);
            for (size_t i=0; i<extends.size(); i++)
            {
                wxFileName ufn(extends.Item(i));
                ufn.SetExt("o");
                depsFiles << " \\\n    " << ufn.GetFullName();
            }

            wxArrayString incl;
            projDep->GetIncludeFilesFile(pfd.source_file_absolute_native, incl);
            for (size_t i=0; i<incl.size(); i++)
            {
                wxFileName ifn(incl.Item(i));
                depsFiles << " \\\n    " << ifn.GetFullName();
            }
            depsFiles << "\n";
        }
        else if (!pNativeParser->IsFileFortran(pfd.source_file) && projFile->compile)
        {
            containsNonFortranFiles = true;
        }
    }

    for (size_t i=0; i<src_ext.Count(); i++)
    {
        wxString sdir;
        wxString ext = src_ext.Item(i);
        sdir << "\nSRC_DIR_" << ext << " = ";
        int dpos = ext.Find('d', true);
        if (dpos != wxNOT_FOUND)
        {
            wxString idxstr;
            idxstr = ext.Mid(dpos+1);
            long longint;
            if (idxstr.ToLong(&longint))
            {
                sdir << src_dirs.Item(longint-1);
            }
        }
        sdir << "\n";
        mfile.Write(sdir);
    }
    mfile.Write(objdir);

    TargetType tagTyp = buildTarget->GetTargetType();
    wxFileName exefile(buildTarget->GetOutputFilename());
    wxFileName basepath;
    basepath.Assign(buildTarget->GetBasePath(), wxEmptyString, wxEmptyString);
    wxArrayString bpdirs = basepath.GetDirs();
    if (bpdirs.GetCount() > 0)
    {
        for (size_t i=bpdirs.GetCount(); i>0; i--)
            exefile.PrependDir(bpdirs.Item(i-1));
        exefile.SetVolume(basepath.GetVolume()+wxFileName::GetVolumeSeparator());
    }

    wxString exeStr;
    if (basepath.HasVolume())
        exeStr << basepath.GetVolume() << wxFileName::GetVolumeSeparator() << wxFileName::GetPathSeparator();
    else
    {
        wxString sep = wxFileName::GetPathSeparator();
        if (!exefile.GetPath(wxPATH_GET_SEPARATOR).StartsWith(sep))
            exeStr << wxFileName::GetPathSeparator();
    }
    exeStr << exefile.GetPath(wxPATH_GET_SEPARATOR);
    exefile = wxFileName(exeStr, exefile.GetName(), exefile.GetExt());
    exefile.MakeRelativeTo(mffn.GetPath(wxPATH_GET_SEPARATOR));

    mfile.Write("EXE_DIR = " + exefile.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR, wxPATH_UNIX) + "\n");
    mfile.Write("\nEXE = " + exefile.GetFullName() + "\n");
    Compiler * compiler = CompilerFactory::GetCompiler(buildTarget->GetCompilerID());
    wxString compStr = "FC = ";
    wxString linkerStr = "LD = ";
    if (compiler)
    {
        compStr << compiler->GetPrograms().C;
        compStr << "\n";
        if (tagTyp == ttDynamicLib)
            linkerStr << compiler->GetPrograms().LD;
        else if (tagTyp == ttStaticLib)
            linkerStr << compiler->GetPrograms().LIB;
        else
            linkerStr << compiler->GetPrograms().LD;
        linkerStr << "\n";
    }
    else
        compStr << "\n";
    mfile.Write(compStr);
    mfile.Write(linkerStr);

    wxString idir = "IDIR = ";
    const wxArrayString& idirs = project->GetIncludeDirs();
    for(size_t i=0; i<idirs.size(); i++)
    {
        idir << "-I" << idirs.Item(i) << " ";
    }
    const wxArrayString& idirst = buildTarget->GetIncludeDirs();
    for(size_t i=0; i<idirst.size(); i++)
    {
        idir << "-I" << idirst.Item(i) << " ";
    }
    mfile.Write(idir + "\n");

    wxString cflags = "CFLAGS = ";
    const wxArrayString& copt = project->GetCompilerOptions();
    for(size_t i=0; i<copt.size(); i++)
    {
        cflags << copt.Item(i) << " ";
    }
    const wxArrayString& copt_t = buildTarget->GetCompilerOptions();
    for(size_t i=0; i<copt_t.size(); i++)
    {
        cflags << copt_t.Item(i) << " ";
    }

    if(compiler)
    {
        const wxArrayString& copt2 = compiler->GetCompilerOptions();
        for(size_t i=0; i<copt2.size(); i++)
        {
            cflags << copt2.Item(i) << " ";
        }
    }

    if (CompilerFactory::CompilerInheritsFrom(buildTarget->GetCompilerID(), "g95"))
        cflags << " -fmod=$(OBJS_DIR) $(IDIR)";
    else if (CompilerFactory::CompilerInheritsFrom(buildTarget->GetCompilerID(), "ifclin"))
        cflags << " -module $(OBJS_DIR) $(IDIR)";
    else if (CompilerFactory::CompilerInheritsFrom(buildTarget->GetCompilerID(), "ifcwin"))
        cflags << " /nologo /module:$(OBJS_DIR) $(IDIR)";
    else if (CompilerFactory::CompilerInheritsFrom(buildTarget->GetCompilerID(), "pgfortran"))
        cflags << " -module $(OBJS_DIR) $(IDIR)";
    else //gfortran
        cflags << " -J$(OBJS_DIR) $(IDIR)";

    mfile.Write(cflags + "\n");

    wxString lflags = "LFLAGS = ";
    const wxArrayString& lopt = project->GetLinkerOptions();
    for(size_t i=0; i<lopt.size(); i++)
    {
        wxString optstr = lopt.Item(i);
        optstr.Trim();
        int ipos = optstr.Find("--rpath=\\\\$$$ORIGIN");
        if (ipos != wxNOT_FOUND)
        {
            wxString optstr1 = optstr.Mid(0, ipos+8);
            wxString optstr2 = "'$$ORIGIN" + optstr.Mid(ipos+19) + "'";
            optstr = optstr1 + optstr2;
        }
        lflags << optstr << " ";
    }
    const wxArrayString& lopt_t = buildTarget->GetLinkerOptions();
    for(size_t i=0; i<lopt_t.size(); i++)
    {
        wxString optstr = lopt_t.Item(i);
        optstr.Trim();
        int ipos = optstr.Find("--rpath=\\\\$$$ORIGIN");
        if (ipos != wxNOT_FOUND)
        {
            wxString optstr1 = optstr.Mid(0, ipos+8);
            wxString optstr2 = "'$$ORIGIN" + optstr.Mid(ipos+19) + "'";
            optstr = optstr1 + optstr2;
        }
        lflags << optstr << " ";
    }
    mfile.Write(lflags + "\n");

    wxString libs = "LIBS = ";
    const wxArrayString& ldirs = project->GetLibDirs();
    for(size_t i=0; i<ldirs.size(); i++)
    {
        libs << "-L" << ldirs.Item(i) << " ";
    }
    const wxArrayString& ldirst = buildTarget->GetLibDirs();
    for(size_t i=0; i<ldirst.size(); i++)
    {
        libs << "-L" << ldirst.Item(i) << " ";
    }
    const wxArrayString& lbsarr = project->GetLinkLibs();
    for(size_t i=0; i<lbsarr.size(); i++)
    {
        wxString lnam;
        if (lbsarr.Item(i).StartsWith("lib"))
            lnam = lbsarr.Item(i).Mid(3);
        else
            lnam = lbsarr.Item(i);
        libs << "-l" << lnam << " ";
    }
    const wxArrayString& lbsarrt = buildTarget->GetLinkLibs();
    for(size_t i=0; i<lbsarrt.size(); i++)
    {
        wxString lnam;
        if (lbsarrt.Item(i).StartsWith("lib"))
            lnam = lbsarrt.Item(i).Mid(3);
        else
            lnam = lbsarrt.Item(i);
        libs << "-l" << lnam << " ";
    }
    mfile.Write(libs + "\n");


    wxString vpath;
    vpath << "\nVPATH = ";
    for (size_t i=0; i<src_ext.Count(); i++)
    {
        vpath << "$(SRC_DIR_" << src_ext.Item(i) << ")";
        vpath << ":$(OBJS_DIR)";
        if (i < src_ext.Count()-1)
        {
            vpath << ":";
        }
    }
//    vpath << "\nendif\n";
    vpath << "\n";
    mfile.Write(vpath);

    wxString objsstr = "OBJS = $(addprefix $(OBJS_DIR),";
    for (size_t i=0; i<src_ext.Count(); i++)
    {
        objsstr << " $(OBJS_" << src_ext.Item(i) << ")";
    }
    objsstr << ")\n";
    mfile.Write(objsstr);

    mfile.Write("\nall : $(EXE)\n");

    wxString lstr = "\n$(EXE) :";

    for (size_t i=0; i<src_ext.Count(); i++)
    {
        lstr << " $(OBJS_" << src_ext.Item(i) << ")";
    }
    lstr << "\n\t@mkdir -p $(EXE_DIR)";
    if (tagTyp == ttDynamicLib)
    {
        lstr << "\n\t$(LD)";
        lstr << " -shared $(OBJS) -o $(EXE_DIR)$(EXE) $(LFLAGS) $(LIBS)\n";
    }
    else if (tagTyp == ttStaticLib)
    {
        lstr << "\n\trm -f $(EXE_DIR)$(EXE)";
        lstr << "\n\t$(LD)";
        lstr << " -r -s $(EXE_DIR)$(EXE) $(OBJS)\n";
    }
    else
    {
        lstr << "\n\t$(LD)";
        lstr << " -o $(EXE_DIR)$(EXE) $(OBJS) $(LFLAGS) $(LIBS)\n";
    }
    mfile.Write(lstr);

    for (size_t i=0; i<src_ext.Count(); i++)
    {
        wxString sdir;
        sdir << "$(SRC_DIR_" << src_ext.Item(i) << ")";

        wxString ext = src_ext.Item(i);
        int dpos = ext.Find('d',true);
        if (dpos != wxNOT_FOUND)
            ext = ext.Mid(0,dpos);

        wxString cstr;
        cstr << "\n$(OBJS_" << src_ext.Item(i) << "):\n";
        cstr << "\t@mkdir -p $(OBJS_DIR)\n";
        if (CompilerFactory::CompilerInheritsFrom(buildTarget->GetCompilerID(), "ifcwin"))
            cstr << "\t$(FC) $(CFLAGS) /c " << sdir << "$(@:.o=." << ext << ")" << " /object: $(OBJS_DIR)$@\n";
        else
            cstr << "\t$(FC) $(CFLAGS) -c " << sdir << "$(@:.o=." << ext << ")" << " -o $(OBJS_DIR)$@\n";

        mfile.Write(cstr);
    }

    wxString clean;
    clean << "\nclean :\n";
	clean << "\trm -f $(OBJS_DIR)*.*\n";
	clean << "\trm -f $(EXE_DIR)$(EXE)\n";
    mfile.Write(clean);

    mfile.Write("\n# File dependencies\n");
    mfile.Write(depsFiles);
    mfile.Write("\n");

    if (containsNonFortranFiles)
        cbMessageBox(_("The build target includes non Fortran files. They were added to the list of files, however the plugin doesn't know how to handle these files correctly."), _("Warning"), wxICON_WARNING);

    const wxString msg(wxString::Format(_("The make file \"%s\" was generated seccessfully."), mffn.GetFullPath()));
    cbMessageBox(msg);
}

bool MakefileGen::SelectMikefileName(wxFileName& mffn)
{
    MakefileDlg mfdlg(Manager::Get()->GetAppWindow());
    mfdlg.SetFilename(mffn.GetFullPath());
    int imax = 5;
    int i;
    for (i=0; i<imax; i++)
    {
        PlaceWindow(&mfdlg);
        if (mfdlg.ShowModal() != wxID_OK)
            return false;
        mffn = mfdlg.GetFilename();
        if (!mffn.IsOk())
        {
            cbMessageBox(_("Error in the file name!"), _("Error"), wxICON_ERROR);
            continue;
        }

        if (mffn.FileExists())
        {
            int answ = cbMessageBox(wxString::Format(_("File \"%s\" already exist.\nWould you like to overwrite it?"), mffn.GetFullPath()), _("Question"), wxYES_NO | wxICON_QUESTION);
            if (answ == wxID_YES)
                break;
        }
        else
            break;
    }

    if (i == imax)
    {
        cbMessageBox(_("I am tired. Maybe next time..."), _("Info"), wxICON_INFORMATION);
        return false;
    }
    return true;
}
