#include "cmExtraQbsGenerator.h"

#include "cmGlobalGenerator.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmGeneratedFileStream.h"
#include "cmSourceFile.h"

cmExtraQbsGenerator::cmExtraQbsGenerator()
{
#if defined(_WIN32)
  this->SupportedGlobalGenerators.push_back("MinGW Makefiles");
  this->SupportedGlobalGenerators.push_back("NMake Makefiles");
#endif
  this->SupportedGlobalGenerators.push_back("Ninja");
  this->SupportedGlobalGenerators.push_back("Unix Makefiles");
}

cmExtraQbsGenerator::~cmExtraQbsGenerator() {}

void cmExtraQbsGenerator::GetDocumentation(cmDocumentationEntry &entry,
                                           const std::string &) const
{
  entry.Name = this->GetName();
  entry.Brief = "Generates Qbs project files.";
}

void cmExtraQbsGenerator::Generate()
{
  for (std::map<std::string, std::vector<cmLocalGenerator *> >::const_iterator
       it = this->GlobalGenerator->GetProjectMap().begin();
       it != this->GlobalGenerator->GetProjectMap().end(); ++it)
    {
    // create a project file
    this->CreateProjectFile(it->first, it->second);
    }
}

void cmExtraQbsGenerator::CreateProjectFile(
        const std::string &name,
        const std::vector<cmLocalGenerator *> &lgs)
{
  const cmMakefile *mf = lgs[0]->GetMakefile();
  std::string outputDir = mf->GetStartOutputDirectory();

  const std::string filename = outputDir + "/" + name + ".qbs";

  this->CreateNewProjectFile(name, lgs, filename);
}

void cmExtraQbsGenerator::CreateNewProjectFile(
    const std::string &projectName, const std::vector<cmLocalGenerator *> &lgs,
    const std::string &filename)
{
  cmGeneratedFileStream fout(filename.c_str());
  if (!fout)
    {
    return;
    }

  fout << "import qbs\n"
       << "import qbs.File\n\n"
       << "Project {\n"
       << "\tname:\"" << projectName << "\"\n";
  std::vector<cmLocalGenerator *>::const_iterator itr = lgs.begin();
  for (; itr != lgs.end(); ++itr)
    {
    cmLocalGenerator *lg = (*itr);
    this->AppendSubProject(fout, lg);
    }
  fout << "}\n";
}

void cmExtraQbsGenerator::AppendSubProject(cmGeneratedFileStream &fout,
                                           cmLocalGenerator *lg)
{
  const cmMakefile *mk = lg->GetMakefile();
  if (!mk || mk->GetTargets().size() == 0)
    {
    return;
    }

  const std::string &relativePath = cmSystemTools::RelativePath(
      mk->GetHomeDirectory(), mk->GetCurrentDirectory());
  fout << "\tProject {\n"
       << "\t\tname:\"" << relativePath << "\"\n";
  this->AppendProduct(fout, lg);
  fout << "\t}\n";
}

void cmExtraQbsGenerator::AppendProduct(cmGeneratedFileStream &fout,
                                        cmLocalGenerator *lg)
{
  const cmMakefile *mk = lg->GetMakefile();
  const cmTargets &ts = mk->GetTargets();
  std::string cfg = mk->GetSafeDefinition("CMAKE_BUILD_TYPE");
  cmTargets::const_iterator itr = ts.begin();
  for (; itr != ts.end(); ++itr)
    {
    const cmTarget &t = itr->second;
    this->AppendTarget(fout, lg, t, cfg);
    }
}

void cmExtraQbsGenerator::AppendTarget(cmGeneratedFileStream &fout,
                                       cmLocalGenerator *lg, const cmTarget &t,
                                       const std::string &cfg)
{
  std::string type;
  bool isBuildable = true;
  switch (t.GetType())
    {
    case cmTarget::EXECUTABLE:
      type = "application";
      break;
    case cmTarget::SHARED_LIBRARY:
      type = "dynamiclibrary";
      break;
    case cmTarget::STATIC_LIBRARY:
      type = "staticlibrary";
      break;
    default:
      isBuildable = false;
      break;
    }

  if (type.empty())
    {
    fout << "\t\tProject {\n";
    }
  else
    {
    fout << "\t\tProduct {\n";
    fout << "\t\t\tdestinationDirectory: \"" << t.GetDirectory(cfg) << "\"\n";
    }
  fout << "\t\t\tname:\"" << t.GetName() << "\"\n";

  if (!type.empty())
    {
    fout << "\t\t\ttype: \"" << type << "\"\n";
    fout << "\t\t\ttargetName: \"" << t.GetName() << "\"\n";
    }

  if (isBuildable)
    {
    fout << "\t\t\tDepends { name: \"cpp\" }\n";
    cmGeneratorTarget *gt = this->GlobalGenerator->GetGeneratorTarget(&t);
    this->AppendSources(fout, gt, t, cfg);

    std::set<std::string> langs, incPaths, defs;
    t.GetLanguages(langs, cfg);
    for (std::set<std::string>::const_iterator lang = langs.begin();
         lang != langs.end();
         ++ lang)
      {
      const std::vector<std::string> &paths =
          gt->GetIncludeDirectories(cfg, *lang);
      std::copy(paths.begin(), paths.end(),
                std::inserter(incPaths, incPaths.end()));

      lg->AddCompileDefinitions(defs, &t, cfg, *lang);
      }
    this->AppendIncludePaths(fout, incPaths);
    this->AppendCompileDefinitions(fout, defs);
    }

  fout << "\t\t}\n";
}

void cmExtraQbsGenerator::AppendSources(cmGeneratedFileStream &fout,
                                        cmGeneratorTarget *gt,
                                        const cmTarget &t,
                                        const std::string &cfg)
{
  std::vector<cmSourceFile *> sources;
  gt->GetSourceFiles(sources, cfg);
  if (sources.empty())
    {
    return;
    }

  std::vector<cmSourceFile *> genSources;
  std::vector<cmSourceFile *>::const_iterator itr = sources.begin();
  fout << "\t\t\tfiles: [\n"
       << "\t\t\t\t\"" << t.GetMakefile()->GetCurrentListFile() << "\",\n";
  for (; itr != sources.end(); ++itr)
    {
    if (!(*itr)->GetPropertyAsBool("GENERATED"))
      {
      fout << "\t\t\t\t\"" << (*itr)->GetFullPath() << "\",\n";
      }
    else
      {
      genSources.push_back(*itr);
      }
    }
  fout << "\t\t\t]\n";

  if (!genSources.empty())
    {
    fout << "\t\t\tGroup {\n"
         << "\t\t\t\tname:\"Generated\"\n"
         << "\t\t\t\tfiles: [\n";
    itr = genSources.begin();
    std::string groupCondition;
    bool initialCondition = true;
    for (; itr != genSources.end(); ++itr)
      {
      const std::string &path = (*itr)->GetFullPath();
      fout << "\t\t\t\t\t\"" << path << "\",\n";
      if (initialCondition)
        {
        initialCondition = false;
        }
      else
        {
        groupCondition += "\t\t\t\t\t && ";
        }
      groupCondition += "File.exists(\"" + path + "\")\n";
      }
    fout << "\t\t\t\t]\n"
         << "\t\t\t\tcondition: " << groupCondition << "\t\t\t}\n";
  }
}

void cmExtraQbsGenerator::AppendIncludePaths(
    cmGeneratedFileStream &fout,
    const std::set<std::string> &paths)
{
  if (paths.empty())
    {
    return;
    }

  std::set<std::string>::const_iterator itr = paths.begin();
  fout << "\t\t\tcpp.includePaths: [\n";
  for (; itr != paths.end(); ++ itr)
    {
    fout << "\t\t\t\t\"" << (*itr) << "\",\n";
    }
  fout << "\t\t\t]\n";
}

void cmExtraQbsGenerator::AppendCompileDefinitions(
    cmGeneratedFileStream &fout,
    const std::set<std::string> &defs)
{
  if (defs.empty())
    {
    return;
    }

  std::set<std::string>::const_iterator itr = defs.begin();
  fout << "\t\t\tcpp.defines: [\n";
  for (; itr != defs.end(); ++ itr)
    {
    fout << "\t\t\t\t'" << (*itr) << "',\n";
    }
  fout << "\t\t\t]\n";
}