/*
    cbp2make : Makefile generation tool for the Code::Blocks IDE
    Copyright (C) 2010-2013 Mirai Computing (mirai.computing@gmail.com)

    This program 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.

    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

//------------------------------------------------------------------------------
#include <iostream>
//------------------------------------------------------------------------------
#include "revision.h"
#include "macros.h"
#include "cbworkspace.h"
#include "cbbuildcfg.h"
#include "cbhelper.h"
#include "stlfutils.h"
#include "tinyxml.h"
//------------------------------------------------------------------------------

CWorkspaceUnit::CWorkspaceUnit(void)
{
    Clear();
}

CWorkspaceUnit::~CWorkspaceUnit(void)
{
    Clear();
}

void CWorkspaceUnit::Clear(void)
{
    m_FileName.Clear();
    m_Depends.Clear();
    m_Project.Clear();
    m_Weight = 0;
}

void CWorkspaceUnit::Read(const TiXmlElement* UnitRoot)
{
    m_FileName = UnitRoot->Attribute("filename");
    const TiXmlNode *_depends = UnitRoot->FirstChild("Depends");
    while (0!=_depends) {
        const TiXmlElement *depends = _depends->ToElement();
        if (0!=depends) {
            char *value = (char *)depends->Attribute("filename");
            if (0!=value) {
                m_Depends.Insert(value);
            }
        }
        _depends = UnitRoot->IterateChildren(_depends);
    }
    m_Depends.RemoveDuplicates();
//m_Depends.RemoveEmpty();
}

void CWorkspaceUnit::Show(void)
{
#ifdef SHOW_MODE_ONELINE
    std::cout<<m_FileName.GetString();
    std::cout<<", Weight: "<<m_Weight;
    std::cout<<", Dependencies: "<<m_Depends.GetCount()<<std::endl;
    for (int i = 0, n = m_Depends.GetCount(); i < n; i++) {
        std::cout<<"Dependency #"<<(i+1)<<": "<<m_Depends[i].GetString()<<std::endl;
    }
#else
    std::cout<<"Filename: "<<m_FileName.GetString()<<std::endl;
    std::cout<<"Weight: "<<m_Weight<<std::endl;
    ShowStringList("Dependencies","Dependency",m_Depends);
#endif
}

bool CWorkspaceUnit::LoadProject(const CString& WorkspacePath)
{
    return m_Project.LoadProject(JoinPaths(WorkspacePath,m_FileName));
}

//------------------------------------------------------------------------------

CCodeBlocksWorkspace::CCodeBlocksWorkspace(void)
{
    Clear();
}

CCodeBlocksWorkspace::~CCodeBlocksWorkspace(void)
{
    Clear();
}

int CCodeBlocksWorkspace::CalculateProjectWeight(const size_t Index)
{
    CWorkspaceUnit *unit = m_Units[Index];
    if (unit->m_Depends.GetCount()) {
        if (unit->m_Weight) {
            return unit->m_Weight;
        }
        int weight = 0;
        for (int j = 0, nj = unit->m_Depends.GetCount(); j < nj; j++) {
            CString dependency = unit->m_Depends[j];
            for (size_t i = 0, ni = m_Units.size(); i < ni; i++) {
                if (i!=Index) {
                    if (unit->m_FileName == dependency) {
                        weight += CalculateProjectWeight(i);
                    }
                }
            }
        }
        weight++;
        return weight;
    }
    return 0;
}

void CCodeBlocksWorkspace::ResolveProjectDependencies(void)
{
    for (size_t i = 0; i < m_Units.size(); i++) {
        m_Units[i]->m_Weight = CalculateProjectWeight(i);
    }
}

class CProjectWeightComparison
{
public:
    bool operator ()(CWorkspaceUnit *const &AUnit, CWorkspaceUnit *const &BUnit)
    {
        return (AUnit->m_Weight < BUnit->m_Weight);
    }
};

void CCodeBlocksWorkspace::SortProjectsByWeight(void)
{
    CProjectWeightComparison cmp;
    std::sort(m_Units.begin(),m_Units.end(),cmp);
}

void CCodeBlocksWorkspace::Clear()
{
    m_Title.Clear();
    for (size_t i = 0; i < m_Units.size(); i++) {
        delete m_Units[i];
    }
    m_Units.clear();
    m_TargetNames.Clear();
    m_MakefileNames.Clear();
    m_MakefilePaths.Clear();
    m_TargetDeps.Clear();
    m_MakefileText.Clear();
    m_Makefile.Clear();
}

void CCodeBlocksWorkspace::Read(const TiXmlElement* WorkspaceRoot)
{
    const TiXmlNode *_workspace = WorkspaceRoot->FirstChild("Workspace");
    if (0!=_workspace) {
        const TiXmlElement *workspace = _workspace->ToElement();
        if (0!=workspace) {
            m_Title = workspace->Attribute("title");
            TiXmlNode *_project = (TiXmlNode *)_workspace->FirstChild("Project");
            while (0!=_project) {
                TiXmlElement* project = _project->ToElement();
                if (0!=project) {
                    CWorkspaceUnit *unit = new CWorkspaceUnit();
                    unit->Read(project);
                    m_Units.push_back(unit);
                }
                _project = (TiXmlNode *)_workspace->IterateChildren(_project);
            }
        }
    }
}

bool CCodeBlocksWorkspace::LoadWorkspace(const CString& FileName)
{
    bool result = false;
    TiXmlDocument cbw;
    result = cbw.LoadFile(FileName.GetCString());
    if (!result) return false;
    Clear();
    const TiXmlElement *root = cbw.RootElement();
    if (0==strcmp(root->Value(),"CodeBlocks_workspace_file")) {
        Read(root);
        result = true;
    }
//
    return result;
}

bool CCodeBlocksWorkspace::LoadWorkspaceProjects(const CString& WorkspacePath)
{
    bool result = true;
    for (size_t i = 0; i < m_Units.size(); i++) {
        result &= m_Units[i]->LoadProject(WorkspacePath);
    }
// resolve project dependencies and sort projects by weight
    ResolveProjectDependencies();
    SortProjectsByWeight();
    return result;
}

void CCodeBlocksWorkspace::Show(const bool ShowProjects)
{
    std::cout<<"Workspace title: "<<m_Title.GetString()<<std::endl;
    std::cout<<"Projects: "<<m_Units.size()<<std::endl;
    for (size_t i = 0; i < m_Units.size(); i++) {
        std::cout<<"Project #"<<(i+1)<<": ";
#ifndef SHOW_MODE_ONELINE
        std::cout<<std::endl;
#endif
        m_Units[i]->Show();
        if (ShowProjects) {
            m_Units[i]->m_Project.Show();
        }
    }
}

bool CCodeBlocksWorkspace::GenerateMakefile
(const CString& FileName, CCodeBlocksBuildConfig& Config)
{
// generate individual makefiles for projects
    CString cwd = GetCurrentDir();
    CString workspace_path = ExtractFilePath(FileName);
    if (!workspace_path.IsEmpty()) {
        ChangeDir(workspace_path);
    }
//
    m_TargetNames.Clear();
    m_MakefileNames.Clear();
    m_TargetDeps.Clear();
    for (size_t i = 0; i < m_Units.size(); i++) {
        CString project_name = m_Units[i]->m_FileName;
        //CString makefile_path = JoinPaths(workspace_path,ExtractFilePath(project_name));
        CString makefile_path = ExtractFilePath(project_name);
        //CString makefile_pathname = JoinPaths(workspace_path,project_name+".mak");
        CString makefile_pathname = project_name+".mak";
        CString target_name = ChangeFileExt(project_name,"");
        CString makefile_name = ExtractFileName(makefile_pathname);
        target_name = CCodeBlocksProject::DecorateTargetName(target_name,Config.TargetNameCase());
        CString target_deps;
        for (int j = 0; j < m_Units[i]->m_Depends.GetCount(); j++) {
            CString dep_name = ChangeFileExt(m_Units[i]->m_Depends[j],"");
            if (j>0) target_deps += " ";
            target_deps += CCodeBlocksProject::DecorateTargetName(dep_name,Config.TargetNameCase());
        }
        m_TargetDeps.Insert(target_deps);
        m_TargetNames.Insert(target_name);
        m_MakefilePaths.Insert(makefile_path);
        m_MakefileNames.Insert(makefile_name);
        makefile_pathname = MakeNativePath(makefile_pathname);
        //std::cout<<"cwd "<<cwd.GetCString()<<std::endl;
        //std::cout<<"gen_makefile "<<makefile_pathname.GetCString()<<std::endl;
        m_Units[i]->m_Project.GenerateMakefile(makefile_pathname,Config);
    }
// generate workspace makefile
    int active_platforms = 0;
    for (size_t pi = 0, pn = Config.Platforms().GetCount(); pi < pn; pi++) {
        if (Config.Platforms().Platform(pi)->Active()) active_platforms++;
    }
    bool single_platform = (1==active_platforms);
    for (size_t pi = 0, pn = Config.Platforms().GetCount(); pi < pn; pi++) {
        CPlatform *pl = Config.Platforms().Platform(pi);
        if (!pl->Active()) continue;
        CString makefile_path = ExtractFilePath(FileName);
        CString makefile_name = ExtractFileName(FileName);
        CString platform_suffix;
        if (!single_platform) platform_suffix = "."+LowerCase(pl->Name());
        makefile_name += platform_suffix;
        ChangeDir(makefile_path);
        // begin makefile
        m_Makefile.Clear();
        int section = 0;
        // head comment
        const int header_width = 80;
        m_Makefile.Header().Insert(FillStr("#",'-',"#",header_width));
#ifdef REVISION_NUMBER
        {
            CString rn = IntegerToString(REVISION_NUMBER);
            CString line = FillStr("# This makefile was generated by 'cbp2make' tool rev."+rn,' ',"#",header_width);
            m_Makefile.Header().Insert(line);
        }
#else
        CString line = FillStr("# This makefile was generated by 'cbp2make' tool rev.",' ',"#",header_width)
#endif
        m_Makefile.Header().Insert(FillStr("#",'-',"#",header_width));
        m_Makefile.Header().Insert("");
        section++;
        // macros
        CString line = pl->EvalWorkDir();
        m_Makefile.AddMacro("WRKDIR",line,section);
        m_Makefile.AddMacro("MAKE",pl->Tool_Make(),section);
        section++;
        // targets
        CMakefileRule& rule_all = m_Makefile.AddRule("all",section);
        for (int i = 0; i < m_TargetNames.GetCount(); i++) {
            CString target_name = m_TargetNames[i];
            bool target_selected = ((Config.Targets().GetCount()==0) ||
                                    ((Config.Targets().GetCount()>0) && (Config.Targets().FindString(target_name)>=0)));
            if (target_selected)
                rule_all.Dependencies().Insert(target_name);
        }
        section++;
        for (int i = 0; i < m_TargetNames.GetCount(); i++) {
            CString target_name = m_TargetNames[i];
            bool target_selected = ((Config.Targets().GetCount()==0) ||
                                    ((Config.Targets().GetCount()>0) && (Config.Targets().FindString(target_name)>=0)));
            if (target_selected)
            {
                CString makefile_path_ex = m_MakefilePaths[i];
                CMakefileRule& rule_target = m_Makefile.AddRule(target_name,section);
                rule_target.Dependencies().Insert(m_TargetDeps[i]);
                line = "$(MAKE)";
                if (!makefile_path_ex.IsEmpty()) {
                    line += " -C "+pl->ProtectPath(pl->Pd(makefile_path_ex),Config.QuotePathMode());
                }
                line += " all -f "+pl->ProtectPath(pl->Pd(m_MakefileNames[i])+platform_suffix,Config.QuotePathMode());
                rule_target.Commands().Insert(line);
            }
        }
        section++;
        //
        CMakefileRule& rule_clean = m_Makefile.AddRule("clean",section);
        for (int i = 0; i < m_TargetNames.GetCount(); i++) {
            CString target_name = m_TargetNames[i];
            bool target_selected = ((Config.Targets().GetCount()==0) ||
                                    ((Config.Targets().GetCount()>0) && (Config.Targets().FindString(target_name)>=0)));
            if (target_selected)
                rule_clean.Dependencies().Insert("clean_"+target_name);
        }
        section++;
        for (int i = 0; i < m_TargetNames.GetCount(); i++) {
            CString target_name = m_TargetNames[i];
            bool target_selected = ((Config.Targets().GetCount()==0) ||
                                    ((Config.Targets().GetCount()>0) && (Config.Targets().FindString(target_name)>=0)));
            if (target_selected)
            {
                CString makefile_path_ex = m_MakefilePaths[i];
                CMakefileRule& rule_clean_target = m_Makefile.AddRule("clean_"+target_selected,section);
                line = "$(MAKE)";
                if (!makefile_path_ex.IsEmpty()) {
                    line += " -C "+pl->ProtectPath(pl->Pd(makefile_path_ex),Config.QuotePathMode());
                }
                line += " clean -f "+pl->ProtectPath(pl->Pd(m_MakefileNames[i])+platform_suffix,Config.QuotePathMode());
                rule_clean_target.Commands().Insert(line);
            }
        }
        // save makefile
        CStringList& text = m_Makefile.Update();
        text.SaveToFile(makefile_name);
        m_Makefile.Clear();
        ChangeDir(cwd);
    }
    return true;
}

void CCodeBlocksWorkspace::GenerateMakefileText(const CString& FileName,
        CCodeBlocksBuildConfig& Config)
{
// generate individual makefiles for projects
//std::cout<<"Workspace: "<<FileName.GetCString()<<std::endl;
    CString cwd = GetCurrentDir();
    CString workspace_path = ExtractFilePath(FileName);
    if (!workspace_path.IsEmpty()) {
        ChangeDir(workspace_path);
    }
//std::cout<<"Workspace path: "<<workspace_path.GetCString()<<std::endl;
    m_TargetNames.Clear();
    m_MakefileNames.Clear();
    for (size_t i = 0; i < m_Units.size(); i++) {
        CString project_name = m_Units[i]->m_FileName;
        CString makefile_path = JoinPaths(workspace_path,ExtractFilePath(project_name));
        CString makefile_pathname = JoinPaths(workspace_path,project_name+".mak");
        CString target_name = LowerCase(ChangeFileExt(project_name,""));
        CString makefile_name = ExtractFileName(makefile_pathname);
        //std::cout<<"Project name: "<<project_name.GetCString()<<std::endl;
        //std::cout<<"Makefile path: "<<makefile_path.GetCString()<<std::endl;
        //std::cout<<"Makefile pathname: "<<makefile_pathname.GetCString()<<std::endl;
        target_name = CCodeBlocksProject::DecorateTargetName(target_name);
        //std::cout<<"Target name: "<<target_name.GetCString()<<std::endl;
        //std::cout<<"Makefile name: "<<makefile_name.GetCString()<<std::endl;
        m_TargetNames.Insert(target_name);
        m_MakefilePaths.Insert(makefile_path);
        m_MakefileNames.Insert(makefile_name);
        makefile_pathname = MakeNativePath(makefile_pathname);
        //std::cout<<"Native makefile pathname: "<<makefile_pathname.GetCString()<<std::endl;
        m_Units[i]->m_Project.GenerateMakefile(makefile_pathname,Config);
    }
//CString cwd = GetCurrentDir();
// generate workspace makefile
    int active_platforms = 0;
    for (size_t pi = 0, pn = Config.Platforms().GetCount(); pi < pn; pi++) {
        if (Config.Platforms().Platform(pi)->Active()) active_platforms++;
    }
    bool single_platform = (1==active_platforms);
    for (size_t pi = 0, pn = Config.Platforms().GetCount(); pi < pn; pi++) {
        CPlatform *pl = Config.Platforms().Platform(pi);
        if (!pl->Active()) continue;
        CString makefile_path = ExtractFilePath(FileName);
        CString makefile_name = ExtractFileName(FileName);
        CString platform_suffix;
        if (!single_platform) platform_suffix = "."+LowerCase(pl->Name());
        makefile_name += platform_suffix;
        //ChangeDir(makefile_path);
        //
        m_MakefileText.Clear();
        // head comment
        m_MakefileText.Insert("#------------------------------------------------------------------------------#");
#ifdef REVISION_NUMBER
        {
            CString s = "# This makefile was generated by 'cbp2make' tool rev.                          #";
            CString n = IntegerToString(REVISION_NUMBER);
            int o = FindStr(s,"rev.") + 4;
            for (int i = 0; i < n.GetLength(); i++) s.SetChar(i+o,n[i]);
            m_MakefileText.Insert(s);
        }
#else
        m_MakefileText.Insert("# This makefile was generated by 'cbp2make' tool                               #");
#endif
        m_MakefileText.Insert("#------------------------------------------------------------------------------#");
        m_MakefileText.Insert("");
        // macros
        CString line = "WRKDIR = "+pl->EvalWorkDir();
        m_MakefileText.Insert(line);
        line = "MAKE = "+pl->Tool_Make();
        m_MakefileText.Insert(line);
        m_MakefileText.Insert("");
        // targets
        line = "all:";
        for (int i = 0; i < m_TargetNames.GetCount(); i++) {
            CString target_name = m_TargetNames[i];
            bool target_selected = ((Config.Targets().GetCount()==0) ||
                                    ((Config.Targets().GetCount()>0) && (Config.Targets().FindString(target_name)>=0)));
            if (target_selected)
            {
                line.Append(" ").Append(target_name);
            }
        }
        m_MakefileText.Insert(line);
        m_MakefileText.Insert("");
        for (int i = 0; i < m_TargetNames.GetCount(); i++) {
            CString target_name = m_TargetNames[i];
            bool target_selected = ((Config.Targets().GetCount()==0) ||
                                    ((Config.Targets().GetCount()>0) && (Config.Targets().FindString(target_name)>=0)));
            if (target_selected)
            {
                CString makefile_path_ex = m_MakefilePaths[i];
                line = target_name+":";
                m_MakefileText.Insert(line);
                line = "\t$(MAKE)";
                if (!makefile_path_ex.IsEmpty()) {
                    line += " -C "+pl->ProtectPath(pl->Pd(makefile_path_ex),Config.QuotePathMode());
                }
                line += " all -f "+pl->ProtectPath(pl->Pd(m_MakefileNames[i])+platform_suffix,Config.QuotePathMode());
                m_MakefileText.Insert(line);
                m_MakefileText.Insert("");
            }
        }
        //
        line = "clean:";
        for (int i = 0; i < m_TargetNames.GetCount(); i++) {
            CString target_name = m_TargetNames[i];
            bool target_selected = ((Config.Targets().GetCount()==0) ||
                                    ((Config.Targets().GetCount()>0) && (Config.Targets().FindString(target_name)>=0)));
            if (target_selected)
            {
                line.Append(" clean_").Append(target_name);
            }
        }
        m_MakefileText.Insert(line);
        m_MakefileText.Insert("");
        for (int i = 0; i < m_TargetNames.GetCount(); i++) {
            CString target_name = m_TargetNames[i];
            bool target_selected = ((Config.Targets().GetCount()==0) ||
                                    ((Config.Targets().GetCount()>0) && (Config.Targets().FindString(target_name)>=0)));
            if (target_selected)
            {
                CString makefile_path_ex = m_MakefilePaths[i];
                line = "clean_"+target_name+":";
                m_MakefileText.Insert(line);
                line = "\t$(MAKE)";
                if (!makefile_path_ex.IsEmpty()) {
                    line += " -C "+pl->ProtectPath(pl->Pd(makefile_path_ex),Config.QuotePathMode());
                }
                line += " clean -f "+pl->ProtectPath(pl->Pd(m_MakefileNames[i])+platform_suffix,Config.QuotePathMode());
                m_MakefileText.Insert(line);
                m_MakefileText.Insert("");
            }
        }
        //
        line = ".PHONY:";
        for (int i = 0; i < m_TargetNames.GetCount(); i++) {
            CString target_name = m_TargetNames[i];
            bool target_selected = ((Config.Targets().GetCount()==0) ||
                                    ((Config.Targets().GetCount()>0) && (Config.Targets().FindString(target_name)>=0)));
            if (target_selected)
            {
                line.Append(" ").Append(target_name);
                line.Append(" clean_").Append(target_name);
            }
        }
        m_MakefileText.Insert(line);
        m_MakefileText.Insert("");
        m_MakefileText.SaveToFile(makefile_name);
        m_MakefileText.Clear();
        ChangeDir(cwd);
    }
}

//------------------------------------------------------------------------------
