#include "sdk_precomp.h"
#include <wx/confbase.h>
#include <wx/fileconf.h>
#include <wx/msgdlg.h>
#include <wx/log.h>
#include <wx/intl.h>
#include <wx/filename.h>
#include <wx/msgdlg.h>

#include <wx/stream.h>
#include <wx/wfstream.h>
#include <wx/txtstrm.h>
#include <wx/dynarray.h>

#include "manager.h"
#include "projectmanager.h"
#include "messagemanager.h"
#include "cbproject.h"
#include "globals.h"
#include "importers_globals.h"
#include "msvcloader.h"
#include "compilerfactory.h"
#include "compiler.h"
#include "multiselectdlg.h"

MSVCLoader::MSVCLoader(cbProject* project)
    : m_pProject(project),
    m_ConvertSwitches(true)
{
	//ctor
}

MSVCLoader::~MSVCLoader()
{
	//dtor
}

bool MSVCLoader::Open(const wxString& filename)
{
 /* NOTE (mandrav#1#): not necessary to ask for switches conversion... */
    m_ConvertSwitches = m_pProject->GetCompilerIndex() == 0; // GCC

   m_Filename = filename;
    if (!ReadConfigurations())
        return false;

    // the file is read, now process it
    Manager::Get()->GetMessageManager()->DebugLog(_("Importing MSVC project: %s"), filename.c_str());

    // delete all targets of the project (we 'll create new ones from the imported configurations)
    while (m_pProject->GetBuildTargetsCount())
        m_pProject->RemoveBuildTarget(0);

    wxArrayInt selected_indices;
    if (ImportersGlobals::ImportAllTargets)
    {
        // don't ask; just fill selected_indices with all indices
        for (size_t i = 0; i < m_Configurations.GetCount(); ++i)
            selected_indices.Add(i);
    }
    else
    {
        // ask the user to select a configuration - multiple choice ;)
        MultiSelectDlg dlg(0, m_Configurations, true, _("Select configurations to import:"), m_Filename.GetName());
        if (dlg.ShowModal() == wxID_CANCEL)
        {
            Manager::Get()->GetMessageManager()->DebugLog(_T("Canceled..."));
            return false;
        }
        selected_indices = dlg.GetSelectedIndices();
    }

    // create all selected targets
    for (size_t i = 0; i < selected_indices.GetCount(); ++i)
    {
        if (!ParseConfiguration(selected_indices[i]))
            return false;
    }

    m_pProject->SetTitle(m_Filename.GetName());
    return ParseSourceFiles();
}

bool MSVCLoader::Save(const wxString& filename)
{
    // no support to save MSVC projects
    return false;
}

bool MSVCLoader::ReadConfigurations()
{
    m_Configurations.Clear();
    m_ConfigurationsLineIndex.Clear();
    m_BeginTargetLine = -1;

    wxFileInputStream file(m_Filename.GetFullPath());
    if (!file.Ok())
        return false; // error opening file???

    wxArrayString comps;
    wxTextInputStream input(file);

    int currentLine = 0;
    while (!file.Eof())
    {
        wxString line = input.ReadLine();
        ++currentLine;
        line.Trim(true);
        line.Trim(false);
        int size = -1;
        if (line.StartsWith(_T("# TARGTYPE")))
        {
          	// # TARGTYPE "Win32 (x86) Application" 0x0103
            int idx = line.Find(' ', true);
            if (idx != -1)
            {
            	TargetType type;
                wxString targtype = line.Mid(12, idx-1-12);
                wxString projcode = line.Mid(idx+3, 4);
                if      (projcode.Matches(_T("0101"))) type = ttExecutable;
                else if (projcode.Matches(_T("0102"))) type = ttDynamicLib;
                else if (projcode.Matches(_T("0103"))) type = ttConsoleOnly;
                else if (projcode.Matches(_T("0104"))) type = ttStaticLib;
                else if (projcode.Matches(_T("010a"))) type = ttCommandsOnly;
                else {
                  type = ttCommandsOnly;
                  Manager::Get()->GetMessageManager()->DebugLog(_("unrecognized target type"));
                }

                //Manager::Get()->GetMessageManager()->DebugLog(_("TargType '%s' is %d"), targtype.c_str(), type);
                m_TargType[targtype] = type;
            }
            continue;
        }
        else if (line.StartsWith(_T("!MESSAGE \""))) {
        //  !MESSAGE "anothertest - Win32 Release" (based on "Win32 (x86) Application")
            int pos;
            pos = line.Find('\"');
            line.Remove(0, pos+1);
            pos = line.Find('\"');
            wxArrayString projectTarget = GetArrayFromString(line.Left(pos), _T("-"));
            wxString target = projectTarget[1];
            if (projectTarget.GetCount() != 2) {
                Manager::Get()->GetMessageManager()->DebugLog(_("ERROR: bad target format"));
                return false;
            }
            line.Remove(0, pos+1);
            pos = line.Find('\"');
            line.Remove(0, pos+1);
            pos = line.Find('\"');
            wxString basedon = line.Left(pos);
            TargetType type = ttCommandsOnly;
            HashTargetType::iterator it = m_TargType.find(basedon);
            if (it != m_TargType.end()) type = it->second;
            else {
                Manager::Get()->GetMessageManager()->DebugLog(_("ERROR: target type not found"));
                return false;
            }
            m_TargetBasedOn[target] = type;
            //Manager::Get()->GetMessageManager()->DebugLog(_("Target '%s' type %d"), target.c_str(), type);
        }
        else if (line.StartsWith(_T("!IF  \"$(CFG)\" ==")))
            size = 16;
        else if (line.StartsWith(_T("!ELSEIF  \"$(CFG)\" ==")))
            size = 20;
        else if (line == _T("# Begin Target"))
        {
            // done
            m_BeginTargetLine = currentLine;
            break;
        }
        if (size != -1)
        {
            // read configuration name
            line.Remove(0, size);
            line.Trim(true);
            line.Trim(false);
            wxString tmp = RemoveQuotes(line);
            // remove the project name part, i.e "anothertest - "
            int idx = tmp.Find('-');
            if (idx != -1) {
              tmp.Remove(0, idx+1);
              tmp.Trim(false);
            }
            if (m_Configurations.Index(tmp) == wxNOT_FOUND)
            {
                m_Configurations.Add(tmp);
                m_ConfigurationsLineIndex.Add(currentLine);
                Manager::Get()->GetMessageManager()->DebugLog(_T("Detected configuration '%s' at line %d"), tmp.c_str(), currentLine);
            }
        }
    }
    return true;
}

bool MSVCLoader::ParseConfiguration(int index)
{
    wxFileInputStream file(m_Filename.GetFullPath());
    if (!file.Ok())
        return false; // error opening file???

    // create new target
    ProjectBuildTarget* bt = m_pProject->AddBuildTarget(m_Configurations[index]);
    if (!bt)
        return false;
    bt->SetCompilerIndex(m_pProject->GetCompilerIndex());
    m_Type = ttCommandsOnly;
    HashTargetType::iterator it = m_TargetBasedOn.find(m_Configurations[index]);
    if (it != m_TargetBasedOn.end()) m_Type = it->second;
    else Manager::Get()->GetMessageManager()->DebugLog(_T("ERROR: could not find the target type of %s"), m_Configurations[index].c_str());
    bt->SetTargetType(m_Type);
    bt->SetOutputFilename(bt->SuggestOutputFilename());

    wxTextInputStream input(file);

    // go to the configuration's line
    int currentLine = 0;
    while (!file.Eof() && currentLine <= m_ConfigurationsLineIndex[index])
    {
        input.ReadLine();
        ++currentLine;
    }

    // start parsing the configuration
    while (!file.Eof())
    {
        wxString line = input.ReadLine();
        line.Trim(true);
        line.Trim(false);

        // we want empty lines (skipped) or lines starting with #
        // if we encounter a line starting with !, we break out of here
        if (line.GetChar(0) == '!')
            break;
        if (line.IsEmpty() || line.GetChar(0) != '#')
            continue;

//        if (line.StartsWith("# PROP BASE Output_Dir "))
        if (line.StartsWith(_T("# PROP Output_Dir ")))
        {
            line.Remove(0, 18);
            line.Trim(true);
            line.Trim(false);
            wxString tmp = RemoveQuotes(line);
            if (!line.IsEmpty())
            {
                wxFileName out = bt->GetOutputFilename();
                out.SetPath(tmp); // out could be a full path name and not only a relative one !
                if (out.IsRelative())
                    out.MakeAbsolute(m_Filename.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR));
                bt->SetOutputFilename(out.GetFullPath());
            }
        }
//        else if (line.StartsWith("# PROP BASE Intermediate_Dir "))
        else if (line.StartsWith(_T("# PROP Intermediate_Dir ")))
        {
            line.Remove(0, 24);
            line.Trim(true);
            line.Trim(false);
            wxString tmp = RemoveQuotes(line);
            if (!line.IsEmpty())
            {
                bt->SetObjectOutput(tmp);
            }
        }
        else if (line.StartsWith(_T("# ADD BASE CPP ")))
        {
            line.Remove(0, 15);
            line.Trim(true);
            line.Trim(false);
            ProcessCompilerOptions(bt, line);
        }
        else if (line.StartsWith(_T("# ADD CPP ")))
        {
            line.Remove(0, 10);
            line.Trim(true);
            line.Trim(false);
            ProcessCompilerOptions(bt, line);
        }
        else if (line.StartsWith(_T("# ADD BASE LIB32 ")))
        {
            line.Remove(0, 17);
            line.Trim(true);
            line.Trim(false);
            ProcessLinkerOptions(bt, line);
        }
        else if (line.StartsWith(_T("# ADD LIB32 ")))
        {
            line.Remove(0, 12);
            line.Trim(true);
            line.Trim(false);
            ProcessLinkerOptions(bt, line);
        }
    }
    return true;
}

bool MSVCLoader::ParseSourceFiles()
{
    wxFileInputStream file(m_Filename.GetFullPath());
    if (!file.Ok())
        return false; // error opening file???

    wxTextInputStream input(file);

    // go to the begining of source files
    int currentLine = 0;
    while (!file.Eof() && currentLine < m_BeginTargetLine)
    {
        input.ReadLine();
        ++currentLine;
    }

    while (!file.Eof())
    {
        wxString line = input.ReadLine();
        line.Trim(true);
        line.Trim(false);

        // we 're only interested in lines starting with SOURCE=
        if (!line.StartsWith(_T("SOURCE=")))
            continue;

        line.Remove(0, 7);
        line.Trim(true);
        line.Trim(false);

        ProjectFile* pf = m_pProject->AddFile(0, RemoveQuotes(line));
        if (pf)
        {
            // add it to all configurations, not just the first
            for (int i = 1; i < m_pProject->GetBuildTargetsCount(); ++i)
                pf->AddBuildTarget(m_pProject->GetBuildTarget(i)->GetTitle());
        }
    }
    return true;
}

void MSVCLoader::ProcessCompilerOptions(ProjectBuildTarget* target, const wxString& opts)
{
    wxArrayString array;
    array = GetArrayFromString(opts, _T(" "));

    for (unsigned int i = 0; i < array.GetCount(); ++i)
    {
        wxString opt = array[i];
        opt.Trim();

        if (m_ConvertSwitches)
        {
            if (opt.Matches(_T("/D")))
                target->AddCompilerOption(_T("-D") + RemoveQuotes(array[++i]));
            else if (opt.Matches(_T("/U")))
                target->AddCompilerOption(_T("-U") + RemoveQuotes(array[++i]));
            else if (opt.Matches(_T("/Zi")) || opt.Matches(_T("/ZI")))
                target->AddCompilerOption(_T("-g"));
            else if (opt.Matches(_T("/I")))
                target->AddIncludeDir(RemoveQuotes(array[++i]));
            else if (opt.Matches(_T("/W0")))
                target->AddCompilerOption(_T("-w"));
            else if (opt.Matches(_T("/O1")) ||
                    opt.Matches(_T("/O2")) ||
                    opt.Matches(_T("/O3")))
                target->AddCompilerOption(_T("-O2"));
            else if (opt.Matches(_T("/W1")) ||
                    opt.Matches(_T("/W2")) ||
                    opt.Matches(_T("/W3")))
                target->AddCompilerOption(_T("-W"));
            else if (opt.Matches(_T("/W4")))
                target->AddCompilerOption(_T("-Wall"));
            else if (opt.Matches(_T("/WX")))
                target->AddCompilerOption(_T("-Werror"));
            else if (opt.Matches(_T("/GX")))
                target->AddCompilerOption(_T("-fexceptions"));
            else if (opt.Matches(_T("/Ob0")))
                target->AddCompilerOption(_T("-fno-inline"));
            else if (opt.Matches(_T("/Ob2")))
                target->AddCompilerOption(_T("-finline-functions"));
            else if (opt.Matches(_T("/Oy")))
                target->AddCompilerOption(_T("-fomit-frame-pointer"));
            else if (opt.Matches(_T("/GB")))
                target->AddCompilerOption(_T("-mcpu=pentiumpro -D_M_IX86=500"));
            else if (opt.Matches(_T("/G6")))
                target->AddCompilerOption(_T("-mcpu=pentiumpro -D_M_IX86=600"));
            else if (opt.Matches(_T("/G5")))
                target->AddCompilerOption(_T("-mcpu=pentium -D_M_IX86=500"));
            else if (opt.Matches(_T("/G4")))
                target->AddCompilerOption(_T("-mcpu=i486 -D_M_IX86=400"));
            else if (opt.Matches(_T("/G3")))
                target->AddCompilerOption(_T("-mcpu=i386 -D_M_IX86=300"));
            else if (opt.Matches(_T("/Za")))
                target->AddCompilerOption(_T("-ansi"));
            else if (opt.Matches(_T("/Zp1")))
                target->AddCompilerOption(_T("-fpack-struct"));
            else if (opt.Matches(_T("/nologo")))
            {
                // do nothing (ignore silently)
            }
            else if (opt.Matches(_T("/c"))) {} // do nothing
            //else Manager::Get()->GetMessageManager()->DebugLog("Unhandled compiler option: " + opt);
        }
        else // !m_ConvertSwitches
        {
            // only differentiate includes and definitions
            if (opt.Matches(_T("/I")))
                target->AddIncludeDir(RemoveQuotes(array[++i]));
            else if (opt.Matches(_T("/D")))
                target->AddCompilerOption(_T("/D") + RemoveQuotes(array[++i]));
            else if (opt.Matches(_T("/U")))
                target->AddCompilerOption(_T("/U") + RemoveQuotes(array[++i]));
            else if (opt.StartsWith(_T("/Yu")))
                Manager::Get()->GetMessageManager()->DebugLog(_("Ignoring precompiled headers option (/Yu)"));
            else if (opt.Matches(_T("/c")) || opt.Matches(_T("/nologo"))) {} // do nothing
            else
                target->AddCompilerOption(opt);
        }
    }
}

void MSVCLoader::ProcessLinkerOptions(ProjectBuildTarget* target, const wxString& opts)
{
    wxArrayString array;
    array = GetArrayFromString(opts, _T(" "));

    for (unsigned int i = 0; i < array.GetCount(); ++i)
    {
        wxString opt = array[i];
        opt.Trim();

        if (m_ConvertSwitches)
        {
            if (opt.StartsWith(_T("/libpath:")))
            {
                opt.Remove(0, 9);
                target->AddLibDir(RemoveQuotes(opt));
            }
            else if (opt.StartsWith(_T("/base:")))
            {
                opt.Remove(0, 6);
                target->AddLinkerOption(_T("--image-base ") + RemoveQuotes(opt));
            }
            else if (opt.StartsWith(_T("/implib:")))
            {
                opt.Remove(0, 8);
                target->AddLinkerOption(_T("--implib ") + RemoveQuotes(opt));
            }
            else if (opt.StartsWith(_T("/map:")))
            {
                opt.Remove(0, 5);
                target->AddLinkerOption(_T("-Map ") + RemoveQuotes(opt) + _T(".map"));
            }
            else if (opt.Matches(_T("/nologo")))
            {
                // do nothing (ignore silently)
            }
            else if (opt.StartsWith(_T("/out:")))
            {
                // do nothing; it is handled below, in common options
            }
            else
                Manager::Get()->GetMessageManager()->DebugLog(_("Unknown linker option: " + opt));
        }
        else // !m_ConvertSwitches
        {
            if (opt.StartsWith(_T("/libpath:")))
            {
                opt.Remove(0, 9);
                target->AddLibDir(RemoveQuotes(opt));
            }
            else if (opt.Matches(_T("/nologo"))) {} // ignore silently
            else
            {
                // don't add linking lib (added below, in common options)
                int idx = opt.Find(_T(".lib"));
                if (idx == -1)
                    target->AddLinkerOption(opt);
            }
        }

        // common options
        if (!opt.StartsWith(_T("/")))
        {
            // probably linking lib
            int idx = opt.Find(_T(".lib"));
            if (idx != -1)
            {
                opt.Remove(idx);
                target->AddLinkLib(opt);
            }
        }
        else if (opt.StartsWith(_T("/out:")))
        {
            opt.Remove(0, 5);
            opt = RemoveQuotes(opt);
            if (m_Type == ttStaticLib)
            {
              // convert lib filename based on compiler
/* NOTE (mandrav#1#): I think I should move this code somewhere more accessible...
I need it here and there... */
                wxFileName orig = target->GetOutputFilename();
                wxFileName newf = opt;
                if (newf.IsRelative())
                    newf.MakeAbsolute(m_pProject->GetBasePath());
                Compiler* compiler = CompilerFactory::Compilers[m_pProject->GetCompilerIndex()];
                newf.SetExt(compiler->GetSwitches().libExtension);
                wxString name = newf.GetName();
                wxString prefix = compiler->GetSwitches().libPrefix;
                if (!prefix.IsEmpty() && !name.StartsWith(prefix))
                    newf.SetName(prefix + name);
                target->SetOutputFilename(newf.GetFullPath());
            }
            else
                target->SetOutputFilename(opt);
        }
    }
}

wxString MSVCLoader::RemoveQuotes(const wxString& src)
{
    wxString res = src;
    if (res.StartsWith(_T("\"")))
    {
        res.Remove(0, 1);
        res.Remove(res.Length() - 1);
    }
//    Manager::Get()->GetMessageManager()->DebugLog("Removing quotes: %s --> %s", src.c_str(), res.c_str());
    return res;
}


syntax highlighted by Code2HTML, v. 0.9.1