/*
    cbp2make : Makefile generation tool for the Code::Blocks IDE
    Copyright (C) 2010-2013 Mirai Computing (mirai.computing@gmail.com)
    Copyright (C) 2014      Sergey "dmpas" Batanov (sergey.batanov (at) dmpas (dot) ru)

    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 "cbptarget.h"
#include "cbhelper.h"
#include "stlconvert.h"
#include "stlfutils.h"
#include "tinyxml.h"
//------------------------------------------------------------------------------

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

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

CString CBuildTarget::TargetTypeName(const TargetType Type)
{
    switch (Type) {
    case CBuildTarget::ttExecutable:
        return "executable";
    case CBuildTarget::ttConsoleExecutable:
        return "console executable";
    case CBuildTarget::ttStaticLibrary:
        return "static library";
    case CBuildTarget::ttDynamicLibrary:
        return "dynamic library";
    case CBuildTarget::ttNative:
        return "native";
    case CBuildTarget::ttCommands:
        return "commands";
    default:
        return "";
    }
    return "";
}

CString CBuildTarget::TargetTypeName(void)
{
    return TargetTypeName(m_Type);
}

CString CBuildTarget::OptionsRelationName(const OptionsRelation Relation)
{
    switch (Relation) {
    case CBuildTarget::orProject:
        return "project";
    case CBuildTarget::orTarget:
        return "target";
    case CBuildTarget::orTargetProject:
        return "target project";
    case CBuildTarget::orProjectTarget:
        return "project target";
    default:
        return "";
    }
    return "";
}

CString CBuildTarget::AutoFilePrefix(const CPlatform::OS_Type OS)
{
    switch (OS) {
    default:
    case CPlatform::OS_Count: // fall-through
    case CPlatform::OS_Other: // fall-through
    case CPlatform::OS_MSys:  // fall-through
    case CPlatform::OS_Unix: {
        switch (m_Type) {
        case CBuildTarget::ttConsoleExecutable: {
            return "";
        }
        case CBuildTarget::ttExecutable:        {
            return "";
        }
        case CBuildTarget::ttStaticLibrary:     {
            return "lib";
        }
        case CBuildTarget::ttDynamicLibrary:    {
            return "";
        }
        case CBuildTarget::ttNative:            {
            return "";
        }
        case CBuildTarget::ttCommands:          {
            return "";
        }
        default:
            return "";
        }
        break;
    }
    case CPlatform::OS_Windows: {
        switch (m_Type) {
        case CBuildTarget::ttConsoleExecutable: {
            return "";
        }
        case CBuildTarget::ttExecutable:        {
            return "";
        }
        case CBuildTarget::ttStaticLibrary:     {
            return "lib";
        }
        case CBuildTarget::ttDynamicLibrary:    {
            return "";
        }
        case CBuildTarget::ttNative:            {
            return "";
        }
        case CBuildTarget::ttCommands:          {
            return "";
        }
        default:
            return "";
        }
        break;
    }
    case CPlatform::OS_Mac: {
        switch (m_Type) {
        case CBuildTarget::ttConsoleExecutable: {
            return "";
        }
        case CBuildTarget::ttExecutable:        {
            return "";
        }
        case CBuildTarget::ttStaticLibrary:     {
            return "lib";
        }
        case CBuildTarget::ttDynamicLibrary:    {
            return "";
        }
        case CBuildTarget::ttNative:            {
            return "";
        }
        case CBuildTarget::ttCommands:          {
            return "";
        }
        default:
            return "";
        }
        break;
    }
    }
    return "";
}

CString CBuildTarget::AutoFileExtension(const CPlatform::OS_Type OS, const CBuildTarget::TargetType Type)
{
    switch (OS) {
    default:
    case CPlatform::OS_Count: // fall-though
    case CPlatform::OS_Other: // fall-though
    case CPlatform::OS_Unix: {
        switch (Type) {
        case CBuildTarget::ttConsoleExecutable: {
            return "";
        }
        case CBuildTarget::ttExecutable:        {
            return "";
        }
        case CBuildTarget::ttStaticLibrary:     {
            return "a";
        }
        case CBuildTarget::ttDynamicLibrary:    {
            return "so";
        }
        case CBuildTarget::ttNative:            {
            return "";
        }
        case CBuildTarget::ttCommands:          {
            return "";
        }
        default:
            return "";
        }
        break;
    }
    case CPlatform::OS_MSys:
    case CPlatform::OS_Windows: {
        switch (Type) {
        case CBuildTarget::ttConsoleExecutable: {
            return "exe";
        }
        case CBuildTarget::ttExecutable:        {
            return "exe";
        }
        case CBuildTarget::ttStaticLibrary:     {
            return "a";
        }
        case CBuildTarget::ttDynamicLibrary:    {
            return "dll";
        }
        case CBuildTarget::ttNative:            {
            return "sys";
        }
        case CBuildTarget::ttCommands:          {
            return "";
        }
        default:
            return "";
        }
        break;
    }
    case CPlatform::OS_Mac: {
        switch (Type) {
        case CBuildTarget::ttConsoleExecutable: {
            return "";
        }
        case CBuildTarget::ttExecutable:        {
            return "";
        }
        case CBuildTarget::ttStaticLibrary:     {
            return "a";
        }
        case CBuildTarget::ttDynamicLibrary:    {
            return "dylib";
        }
        case CBuildTarget::ttNative:            {
            return "";
        }
        case CBuildTarget::ttCommands:          {
            return "";
        }
        default:
            return "";
        }
        break;
    }
    }
    return "";
}

CString CBuildTarget::AutoFileExtension(CString& Platform)
{
    return AutoFileExtension(CPlatform::OS(Platform),m_Type);
}

CString CBuildTarget::AutoFileExtension(const int Platform)
{
    return AutoFileExtension(m_Platforms.GetString(Platform));
}

CString CBuildTarget::MakeOptions(const OptionsRelation Relation,
                                  const CString& ProjectOptions, const CString& TargetOptions)
{
    switch (Relation) {
    case CBuildTarget::orProject:
        return ProjectOptions;
    case CBuildTarget::orTarget:
        return TargetOptions;
    case CBuildTarget::orTargetProject:
        return JoinStr(TargetOptions,ProjectOptions,' ');
    default:
    case CBuildTarget::orProjectTarget:
        return JoinStr(ProjectOptions,TargetOptions,' ');
    }
}

CString CBuildTarget::CFlags(void)
{
    CString result;
    for (int i = 0; i < m_CompilerOptions.GetCount(); i++) {
        result += " "+m_CompilerOptions[i];
    }
    return result;
}

CString CBuildTarget::CFlags(const CString& ProjectCFlags)
{
    return MakeOptions(m_CompilerOptionsRelation,ProjectCFlags,CFlags());
}

CString CBuildTarget::RCFlags(void)
{
    CString result;
    for (int i = 0; i < m_ResourceCompilerOptions.GetCount(); i++) {
        result += " "+m_ResourceCompilerOptions[i];
    }
    return result;
}

CString CBuildTarget::RCFlags(const CString& ProjectRCFlags)
{
    return MakeOptions(m_ResourceCompilerOptionsRelation,ProjectRCFlags,RCFlags());
}

CString CBuildTarget::LdFlags(void)
{
    CString result;
    for (int i = 0; i < m_LinkerOptions.GetCount(); i++) {
        result += " "+m_LinkerOptions[i];
    }
    return result;
}

CString CBuildTarget::LdFlags(const CString& ProjectLdFlags)
{
    return MakeOptions(m_LinkerOptionsRelation,ProjectLdFlags,LdFlags());
}

CString CBuildTarget::IncDirs(const CString& IncDirSwitch)
{
    CString result;
    for (int i = 0; i < m_CompilerDirectories.GetCount(); i++) {
        result = JoinStr(result,IncDirSwitch+m_CompilerDirectories[i],' ');
    }
    return result;
}

CString CBuildTarget::IncDirs(const CString& ProjectIncDirs, const CString& IncDirSwitch)
{
    return MakeOptions(m_IncludeDirectoriesRelation,ProjectIncDirs,IncDirs(IncDirSwitch));
}

CString CBuildTarget::ResDirs(const CString& IncDirSwitch)
{
    CString result;
    for (int i = 0; i < m_ResourceCompilerDirectories.GetCount(); i++) {
        result = JoinStr(result,IncDirSwitch+m_ResourceCompilerDirectories[i],' ');
    }
    return result;
}

CString CBuildTarget::ResDirs(const CString& ProjectResDirs, const CString& IncDirSwitch)
{
    return MakeOptions(m_ResourceIncludeDirectoriesRelation,ProjectResDirs,ResDirs(IncDirSwitch));
}

CString CBuildTarget::LibDirs(const CString& LibDirSwitch)
{
    CString result;
    for (int i = 0; i < m_LinkerDirectories.GetCount(); i++) {
        result = JoinStr(result,LibDirSwitch+m_LinkerDirectories[i],' ');
    }
    return result;
}

CString CBuildTarget::LibDirs(const CString& ProjectLibDirs, const CString& LibDirSwitch)
{
    return MakeOptions(m_LibraryDirectoriesRelation,ProjectLibDirs,LibDirs(LibDirSwitch));
}

CString CBuildTarget::Libs(const CPlatform& Platform, const CString& LinkLibSwitch)
{
    CString result;
    for (int i = 0; i < m_LinkerLibraries.GetCount(); i++) {
        CString lib_name = m_LinkerLibraries[i];
        CString lib_ext = ExtractFileExt(lib_name);
        //if (lib_ext.IsEmpty()) // wrong for files with dots but without .a extension
        //if (lib_ext==AutoFileExtension(OS,CBuildTarget::ttStaticLibrary)) // a plaform may have several valid extensions
        if (Platform.IsStaticLibraryExtension(lib_ext)) {
            result = JoinStr(result,lib_name,' ');
        } else {
            result = JoinStr(result,LinkLibSwitch+lib_name,' ');
        }
    }
    return result;
}

CString CBuildTarget::ExtDeps(void)
{
    CString result;
    for (int i = 0; i < m_ExternalDependencies.GetCount(); i++) {
        result += " "+m_ExternalDependencies[i];
    }
    return result;
}

void CBuildTarget::Clear(void)
{
    m_Title = "default"; //m_Title.Clear();
    m_Platforms.Clear();
    m_Output = "a.out"; //m_Output.Clear();
    m_ObjectOutput.Clear();
    m_ExternalDependencies.Clear();
    m_Type = ttExecutable;
    m_AutoPrefix = false;
    m_AutoExtension = false;
    m_Compiler.Clear();
    m_CompilerOptions.Clear();
    m_CompilerDirectories.Clear();
    m_ResourceCompilerDirectories.Clear();
    m_LinkerOptions.Clear();
    m_LinkerLibraries.Clear();
    m_LinkerDirectories.Clear();
    m_BeforeBuildCommands.Clear();
    m_AfterBuildCommands.Clear();
    m_ForceBeforeBuildCommands = false;
    m_ForceAfterBuildCommands = false;
    m_CompilerOptionsRelation = orProjectTarget;
    m_LinkerOptionsRelation = orProjectTarget;
    m_IncludeDirectoriesRelation = orProjectTarget;
    m_ResourceIncludeDirectoriesRelation = orProjectTarget;
    m_LibraryDirectoriesRelation = orProjectTarget;
    m_UCName.Clear();
    m_LCName.Clear();
}

void CBuildTarget::Read(const TiXmlElement *TargetRoot)
{
    char *value = 0;
    if ((value = (char *)TargetRoot->Attribute("title"))) m_Title = value;
    TiXmlNode *_option = (TiXmlNode *)TargetRoot->FirstChild("Option");
    while (0!=_option) {
        TiXmlElement* option = _option->ToElement();
        if (0!=option) {
            char *value_ex = 0;
            if ((value_ex = (char *)option->Attribute("platforms"))) {
                ParseStr(value_ex,';',m_Platforms);
                m_Platforms.RemoveEmpty();

                if (m_Platforms.FindString("Windows") != INVALID_INDEX
                    && m_Platforms.FindString("MSys") == INVALID_INDEX) {
                    m_Platforms.Insert("MSys");
                }

            }
            if ((value_ex = (char *)option->Attribute("output"))) {
                m_Output = value_ex;
            }
            if ((value_ex = (char *)option->Attribute("prefix_auto"))) {
                m_AutoPrefix = StringToBoolean(value_ex);
            }
            if ((value_ex = (char *)option->Attribute("extension_auto"))) {
                m_AutoExtension = StringToBoolean(value_ex);
            }
            if ((value_ex = (char *)option->Attribute("working_dir"))) {
                m_WorkingDirectory = value_ex;
            }
            if ((value_ex = (char *)option->Attribute("object_output"))) {
                m_ObjectOutput = value_ex;
            }
            if ((value_ex = (char *)option->Attribute("external_deps"))) {
                ParseStr(value_ex,';',m_ExternalDependencies);
                m_ExternalDependencies.RemoveEmpty();
            }
            if ((value_ex = (char *)option->Attribute("type"))) {
                m_Type = (CBuildTarget::TargetType)StringToInteger(value_ex);
            }
            if ((value_ex = (char *)option->Attribute("compiler"))) {
                m_Compiler = value_ex;
            }
            if ((value_ex = (char *)option->Attribute("projectCompilerOptionsRelation"))) {
                m_CompilerOptionsRelation = (CBuildTarget::OptionsRelation)StringToInteger(value_ex);
            }
            if ((value_ex = (char *)option->Attribute("projectLinkerOptionsRelation"))) {
                m_LinkerOptionsRelation = (CBuildTarget::OptionsRelation)StringToInteger(value_ex);
            }
            if ((value_ex = (char *)option->Attribute("projectIncludeDirsRelation"))) {
                m_IncludeDirectoriesRelation = (CBuildTarget::OptionsRelation)StringToInteger(value_ex);
            }
            if ((value_ex = (char *)option->Attribute("projectResourceIncludeDirsRelation"))) {
                m_ResourceIncludeDirectoriesRelation = (CBuildTarget::OptionsRelation)StringToInteger(value_ex);
            }
            if ((value_ex = (char *)option->Attribute("projectLibDirsRelation"))) {
                m_LibraryDirectoriesRelation = (CBuildTarget::OptionsRelation)StringToInteger(value_ex);
            }
        }
        _option = (TiXmlNode *)TargetRoot->IterateChildren(_option);
    } // option
    TiXmlNode *_compiler = (TiXmlNode *)TargetRoot->FirstChild("Compiler");
    if (0!=_compiler) {
        TiXmlNode *_option_ex = (TiXmlNode *)_compiler->FirstChild("Add");
        while (0!=_option_ex) {
            TiXmlElement* option = _option_ex->ToElement();
            if (0!=option) {
                char *value_ex = 0;
                if ((value_ex = (char *)option->Attribute("option"))) {
                    m_CompilerOptions.Insert(value_ex);
                }
                if ((value_ex = (char *)option->Attribute("directory"))) {
                    m_CompilerDirectories.Insert(value_ex);
                }
            }
            _option_ex = (TiXmlNode *)_compiler->IterateChildren(_option_ex);
        } // option
    } // compiler
    TiXmlNode *_res_compiler = (TiXmlNode *)TargetRoot->FirstChild("ResourceCompiler");
    if (0!=_res_compiler) {
        TiXmlNode *_option_ex = (TiXmlNode *)_res_compiler->FirstChild("Add");
        while (0!=_option) {
            TiXmlElement* option = _option_ex->ToElement();
            if (0!=option) {
                char *value_ex = 0;
                if ((value_ex = (char *)option->Attribute("directory"))) {
                    m_ResourceCompilerDirectories.Insert(value_ex);
                }
            }
            _option_ex = (TiXmlNode *)_res_compiler->IterateChildren(_option_ex);
        } // option
    } // resource compiler
    TiXmlNode *_linker = (TiXmlNode *)TargetRoot->FirstChild("Linker");
    if (0!=_linker) {
        TiXmlNode *_option_ex = (TiXmlNode *)_linker->FirstChild("Add");
        while (0!=_option_ex) {
            TiXmlElement* option = _option_ex->ToElement();
            if (0!=option) {
                if (strcmp(option->Value(),"Add")) break;
                char *value_ex = 0;
                if ((value_ex = (char *)option->Attribute("option"))) {
                    m_LinkerOptions.Insert(value_ex);
                }
                if ((value_ex = (char *)option->Attribute("directory"))) {
                    m_LinkerDirectories.Insert(value_ex);
                }
                if ((value_ex = (char *)option->Attribute("library"))) {
                    m_LinkerLibraries.Insert(value_ex);
                }
            }
            _option_ex = (TiXmlNode *)_linker->IterateChildren(_option_ex);
        } // option
    } // linker
    TiXmlNode *_extra_cmd = (TiXmlNode *)TargetRoot->FirstChild("ExtraCommands");
    if (0!=_extra_cmd) {
        TiXmlNode *_option_ex = (TiXmlNode *)_extra_cmd->FirstChild("Add");
        while (0!=_option_ex) {
            TiXmlElement* option = _option_ex->ToElement();
            if (0!=option) {
                if (strcmp(option->Value(),"Add")) break;
                char *value_ex = 0;
                if ((value_ex = (char *)option->Attribute("before"))) {
                    m_BeforeBuildCommands.Insert(value_ex);
                }
                if ((value_ex = (char *)option->Attribute("after"))) {
                    m_AfterBuildCommands.Insert(value_ex);
                }
            }
            _option_ex = (TiXmlNode *)_extra_cmd->IterateChildren(_option_ex);
        } // option
        _option_ex = (TiXmlNode *)_extra_cmd->FirstChild("Mode");
        while (0!=_option_ex) {
            TiXmlElement* option = _option_ex->ToElement();
            if (0!=option) {
                if (strcmp(option->Value(),"Mode")) break;
                char *value_ex = 0;
                if ((value_ex = (char *)option->Attribute("before"))) {
                    m_ForceBeforeBuildCommands = (strcmp(value_ex,"always")==0);
                }
                if ((value_ex = (char *)option->Attribute("after"))) {
                    m_ForceAfterBuildCommands = (strcmp(value_ex,"always")==0);
                }
            }
            _option_ex = (TiXmlNode *)_extra_cmd->IterateChildren(_option_ex);
        } // option
    } // extra commands
// decorate target name
    {
        m_MFName = MakefileFriendly(m_Title);
        m_UCName = UpperCase(m_MFName);
        m_LCName = LowerCase(m_MFName);
        m_ObjectOutput = RemoveTrailingPathDelimiter(m_ObjectOutput);
    }
}

CString CBuildTarget::Name(const CString& Prefix, const int Case) const
{
    switch (Case) {
    default:
    case 0:
        return Prefix+m_MFName;
    case 1:
        return LowerCase(Prefix)+m_LCName;
    case 2:
        return UpperCase(Prefix)+m_UCName;
    }
}

CString CBuildTarget::Name(const CString& Prefix) const
{
    switch (m_NameCase) {
    default:
    case 0:
        return Prefix+m_MFName;
    case 1:
        return LowerCase(Prefix)+m_LCName;
    case 2:
        return UpperCase(Prefix)+m_UCName;
    }
}

void CBuildTarget::Show(void)
{
    std::cout<<"Target title: "<<m_Title.GetString()<<std::endl;
    if (m_Platforms.GetCount()) {
        ShowStringList("Platforms","Platform",m_Platforms);
    } else {
        std::cout<<"Platforms: all"<<std::endl;
    }
    std::cout<<"Binary output: "<<m_Output.GetCString()<<std::endl;
    std::cout<<"Automatic prefix: "<<BooleanToYesNoString(m_AutoPrefix).GetCString()<<std::endl;
    std::cout<<"Automatic extension: "<<BooleanToYesNoString(m_AutoExtension).GetCString()<<std::endl;
    std::cout<<"Working directory: "<<m_WorkingDirectory.GetCString()<<std::endl;
    std::cout<<"Object output: "<<m_ObjectOutput.GetCString()<<std::endl;
    ShowStringList("External dependencies","Dependency",m_ExternalDependencies);
    std::cout<<"Type: "<<TargetTypeName().GetCString()<<std::endl;
    std::cout<<"Compiler: "<<m_Compiler.GetCString()<<std::endl;
    std::cout<<"Compiler options relation: "
             <<OptionsRelationName(m_CompilerOptionsRelation).GetCString()<<std::endl;
    std::cout<<"Linker options relation: "
             <<OptionsRelationName(m_LinkerOptionsRelation).GetCString()<<std::endl;
    std::cout<<"Include directories relation: "
             <<OptionsRelationName(m_IncludeDirectoriesRelation).GetCString()<<std::endl;
    std::cout<<"Resource include directories relation: "
             <<OptionsRelationName(m_ResourceIncludeDirectoriesRelation).GetCString()<<std::endl;
    std::cout<<"Library directories relation: "
             <<OptionsRelationName(m_LibraryDirectoriesRelation).GetCString()<<std::endl;
    ShowStringList("Compiler options","Option",m_CompilerOptions);
    ShowStringList("Compiler directories","Directory",m_CompilerDirectories);
    ShowStringList("Resource compiler directories","Directory",m_ResourceCompilerDirectories);
    ShowStringList("Linker options","Option",m_LinkerOptions);
    ShowStringList("Linker directories","Directory",m_LinkerDirectories);
    ShowStringList("Linked libraries","Library",m_LinkerLibraries);
    ShowStringList("Before-build commands","Command",m_BeforeBuildCommands);
    std::cout<<"Force before-build commands: "
             <<BooleanToYesNoString(m_ForceBeforeBuildCommands).GetCString()<<std::endl;
    ShowStringList("After-build commands","Command",m_AfterBuildCommands);
    std::cout<<"Force after-build commands: "
             <<BooleanToYesNoString(m_ForceAfterBuildCommands).GetCString()<<std::endl;
}

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

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

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

void CVirtualTarget::Clear(void)
{
    m_Alias.Clear();
    m_Targets.Clear();
    m_UCName.Clear();
    m_LCName.Clear();
}

void CVirtualTarget::Read(const TiXmlElement *TargetRoot)
{
    char *value = 0;
    if ((value = (char *)TargetRoot->Attribute("alias"))) {
        m_Alias = value;
    }
    if ((value = (char *)TargetRoot->Attribute("targets"))) {
        ParseStr(value,';',m_Targets);
        m_Targets.RemoveEmpty();
    }
// decorate target name
    {
        m_MFName = MakefileFriendly(m_Alias);
        m_UCName = UpperCase(m_MFName);
        m_LCName = LowerCase(m_MFName);
    }
}

CString CVirtualTarget::Name(const CString& Prefix) const
{
    switch (m_NameCase) {
    default:
    case 0:
        return Prefix+m_MFName;
    case 1:
        return LowerCase(Prefix)+m_LCName;
    case 2:
        return UpperCase(Prefix)+m_UCName;
    }
}

CString CVirtualTarget::Name(const CString& Prefix, const int TargetIndex) const
{
   (void)Prefix;
    CString name = MakefileFriendly(m_Targets.GetString(TargetIndex));
    switch (m_NameCase) {
    default:
    case 0:
        return name;
    case 1:
        return LowerCase(name);
    case 2:
        return UpperCase(name);
    }
}

void CVirtualTarget::Show(void)
{
    std::cout<<"Target alias: "<<m_Alias.GetString()<<std::endl;
    if (m_Targets.GetCount()) {
        ShowStringList("Targets","Target",m_Targets);
    }
}

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