#ifndef VSTS_VERSION_FIXER_IMPLEMENTATION_FILE
#define VSTS_VERSION_FIXER_IMPLEMENTATION_FILE

/*****************************************************************************\
*                                                                             *
*  Name   : vsts_version_fixer                                                *
*  Author : Chris Koeritz                                                     *
*                                                                             *
*******************************************************************************
* Copyright (c) 2008-$now By Author.  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 2 of   *
* the License or (at your option) any later version.  This is online at:      *
*     http://www.fsf.org/copyleft/gpl.html                                    *
* Please send any updates to: fred@gruntose.com                               *
\*****************************************************************************/

#include <basis/function.h>
#include <basis/istring.h>
#include <basis/portable.h>
#include <basis/string_array.h>
#include <data_struct/static_memory_gremlin.h>
#include <opsystem/application_shell.h>
#include <opsystem/byte_filer.h>
#include <opsystem/directory.h>
#include <opsystem/filename.h>
#include <opsystem/version_ini.h>

#undef LOG
#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger(), s)
#undef BASE_LOG
#define BASE_LOG(s) program_wide_logger().log(s)

//#define DEBUG_VSTS_VERSION_FIXER
  // uncomment for noisy version.

////////////////////////////////////////////////////////////////////////////

class vsts_version_fixer : public application_shell
{
public:
  vsts_version_fixer() : application_shell(static_class_name()) {}
  virtual ~vsts_version_fixer() {}

  virtual int execute();

  IMPLEMENT_CLASS_NAME("vsts_version_fixer");

  void remove_confusing_files();
    //!< tosses out the generated files that confuse ms devstudio.

//move these
  typedef bool spider_method(const directory &current);
    //!< prototype for functions that are called during directory spidering.
    /*!< this function should do whatever work is needed on the items in
    that "current" directory.  true should be returned by this method when
    the traversal of the directory is still desired.  if there is a reason
    to stop traversing the directory hierarchy, then it should return false. */

//hmmm: support postfix and in order also.
//hmmm: support reporting where the spidering stopped.
  bool spider_directory(directory start, spider_method to_invoke);
    //!< traverses hierarchy "start" in prefix order while calling "to_invoke".
    /*!< true is returned if all invoked spider methods returned true.
    otherwise, false is returned. */
//move those

  bool perform_version_stamping(const directory &start);
    //!< finds all version ini files and applies stamps using them.

  void whack_in_subdirs(const directory &start,
      const string_array &file_whacks, const string_array &dir_whacks);
    //!< recursively cleans all items found in "file_whacks" and "dir_whacks".
    /*!< "file_whacks" is a list of file suffixes to whack.  for example, to
    remove all files matching a pattern *.exe, pass in just ".exe" in the
    "file_whacks".  the "dir_whacks" list is a list of directories to
    completely obliterate where found. */
};

HOOPLE_MAIN(vsts_version_fixer, )

////////////////////////////////////////////////////////////////////////////

//hmmm: move to a useful place; maybe even in directory class?
bool vsts_version_fixer::spider_directory(directory start,
    spider_method to_invoke)
{
  FUNCDEF("spider_directory");

//LOG(istring("spider_directory: ") + start.path());
  // call our method on this directory first.  this ensures that we have
  // dealt with it before we spider off elsewhere.
  bool ret = to_invoke(start);
  if (!ret) return false;  // bail.

  // now let's look at the subdirectories.  we'll recurse on all of them in
  // the order listed.
  const string_array &dirs = start.directories();
  for (int dir_indy = 0; dir_indy < dirs.length(); dir_indy++) {
    const istring &current_dir = dirs[dir_indy];
//LOG(istring("currdir into ") + current_dir);
    if (current_dir == ".svn") continue;  // skip this.
    if (current_dir == "CVS") continue;  // skip this also.
    directory new_dir(start.path() + "/" + current_dir,
        start.pattern().observe());
    bool ret = spider_directory(new_dir, to_invoke);
    if (!ret) return false;  // bail from subdir issue.
  }
  // if we made it to here, everything was groovy.
  return true;
}

////////////////////////////////////////////////////////////////////////////

#define static_class_name() "vsts_version_fixer"

// global variables used to communicate with whacking_spider.
string_array global_file_whacks;
string_array global_dir_whacks;

bool whacking_spider(const directory &current)
{
  FUNCDEF("whacking_spider");
//LOG(istring("whacking_spider: ") + current.path());
  // iterate across the files in the directory and check for evil ones.
  const string_array &files = current.files();
  for (int file_indy = 0; file_indy < files.length(); file_indy++) {
    const istring &current_file = files[file_indy];
//LOG(istring("currfile ") + current_file);
    // now iterate across our pattern list to see if this thing is
    // one of the offending files.
    for (int pat_indy = 0; pat_indy < global_file_whacks.length(); pat_indy++) {
//LOG(istring("currpat ") + global_file_whacks[pat_indy]);
      if (current_file.iends(global_file_whacks[pat_indy])) {
        filename goner(current.path() + "/" + current_file);
        BASE_LOG(istring("whack file: ") + goner.raw());
        goner.unlink();
        break;  // stop looking at the pattern list for matches.
      }
    }
  }

  // okay, now that we've cleaned out those files, let's look at the
  // subdirectories.
  const string_array &dirs = current.directories();
  for (int dir_indy = 0; dir_indy < dirs.length(); dir_indy++) {
    const istring &current_dir = dirs[dir_indy];
//LOG(istring("currdir ") + current_dir);
    for (int pat_indy = 0; pat_indy < global_dir_whacks.length(); pat_indy++) {
      if (current_dir.iequals(global_dir_whacks[pat_indy])) {
        filename goner(current.path() + "/" + current_dir);
        BASE_LOG(istring("whack dir: ") + goner.raw());
//hmmm: plug in recursive delete here instead.
u_int kid;
portable::launch_process("rm", istring("-rf ") + goner.raw(), portable::AWAIT_APP_EXIT, kid);
        break;  // skip remainder of patterns for this dir.
      }
    }
  }
  return true;
}

#undef static_class_name

////////////////////////////////////////////////////////////////////////////

void vsts_version_fixer::whack_in_subdirs(const directory &start,
    const string_array &file_whacks, const string_array &dir_whacks)
{
  FUNCDEF("whack_in_subdirs");

  // save the lists so the spider method can see them.
  // note that this approach with a global variable would be bad if there
  // were concurrent invocations of the spidering, but we're not doing
  // that here.
  global_file_whacks = file_whacks;
  global_dir_whacks = dir_whacks;

  bool worked = spider_directory(start, whacking_spider);
  if (!worked) {
    LOG(istring("spidering of ") + start.path() + " failed for some reason.");
  }
}

////////////////////////////////////////////////////////////////////////////

#define static_class_name() "vsts_version_fixer"

istring global_build_ini;

bool stamping_spider(const directory &current)
{
  FUNCDEF("stamping_spider");
//LOG(istring("stamping_spider: ") + current.path());

  const string_array &files = current.files();
  for (int file_indy = 0; file_indy < files.length(); file_indy++) {
    const istring &current_file = files[file_indy];
//LOG(istring("currfile ") + current_file);
    if (current_file.ends("version.ini")) {
//LOG(istring("found ver file: ") + current.path() + "/" + current_file);
      version_ini::one_stop_version_stamp(current.path() + "/" + current_file,
          global_build_ini, true);
    }
  }
  return true;
}

#undef static_class_name

////////////////////////////////////////////////////////////////////////////

bool vsts_version_fixer::perform_version_stamping(const directory &start)
{
  FUNCDEF("perform_version_stamping");
  return spider_directory(start, stamping_spider);
}

////////////////////////////////////////////////////////////////////////////

void vsts_version_fixer::remove_confusing_files()
{
  // clean out a few directories that show up in the source tree from c#
  // projects compilation.  c# projects always rebuild every time anyways,
  // so this doesn't lose us any compilation time.  the only thing c#
  // projects don't ever seem to rebuild is their version resource, unless
  // they're forced to totally recompile like we cause below.
  directory repo_source(portable::env_string("REPOSITORY_DIR") + "/source");
  string_array source_file_whacks;  // none right now.
  string_array source_dir_whacks;
  source_dir_whacks += "obj";
  source_dir_whacks += "Debug";
  source_dir_whacks += "Release";
  source_dir_whacks += "bin";
  source_dir_whacks += "temp_build";
  whack_in_subdirs(repo_source, source_file_whacks, source_dir_whacks);

  // now reset but whack the same things in the dsm sdk support.
  repo_source = portable::env_string("REPOSITORY_DIR") + "/dsm_sdk";
  whack_in_subdirs(repo_source, source_file_whacks, source_dir_whacks);

  // clean out a variety of bad files in the objects hierarchy.
  // currently this is just the generated RES files which we have seen cause
  // vsts to think apps and dlls are up to date when they are actually not.
  directory repo_objects(portable::env_string("REPOSITORY_DIR"));
  string_array objects_file_whacks;
  objects_file_whacks += ".res";
  string_array objects_dir_whacks;  // none right now.
  whack_in_subdirs(repo_objects, objects_file_whacks, objects_dir_whacks);
}

int vsts_version_fixer::execute()
{
  log(timestamp(true, true) + "vsts_version_fixer started.");

  remove_confusing_files();

  directory repo_source = portable::env_string("REPOSITORY_DIR") + "/source";
  global_build_ini = portable::env_string("REPOSITORY_DIR") + "/build.ini";
  perform_version_stamping(repo_source);

  log(timestamp(true, true) + "vsts_version_fixer finished.");
  return 0;
}

#endif

#ifdef __BUILD_STATIC_APPLICATION__
  // static dependencies found by buildor_gen_deps.sh:
  #include <basis/array.cpp>
  #include <basis/byte_array.cpp>
  #include <basis/callstack_tracker.cpp>
  #include <basis/chaos.cpp>
  #include <basis/convert_utf.cpp>
  #include <basis/definitions.cpp>
  #include <basis/earth_time.cpp>
  #include <basis/guards.cpp>
  #include <basis/istring.cpp>
  #include <basis/log_base.cpp>
  #include <basis/memory_checker.cpp>
  #include <basis/mutex.cpp>
  #include <basis/object_base.cpp>
  #include <basis/outcome.cpp>
  #include <basis/packable.cpp>
  #include <basis/portable.cpp>
  #include <basis/sequence.cpp>
  #include <basis/set.cpp>
  #include <basis/utility.cpp>
  #include <basis/version_record.cpp>
  #include <data_struct/amorph.cpp>
  #include <data_struct/bit_vector.cpp>
  #include <data_struct/byte_hasher.cpp>
  #include <data_struct/configurator.cpp>
  #include <data_struct/hash_table.cpp>
  #include <data_struct/pointer_hash.cpp>
  #include <data_struct/stack.cpp>
  #include <data_struct/static_memory_gremlin.cpp>
  #include <data_struct/string_hash.cpp>
  #include <data_struct/string_hasher.cpp>
  #include <data_struct/string_table.cpp>
  #include <data_struct/symbol_table.cpp>
  #include <data_struct/table_configurator.cpp>
  #include <loggers/console_logger.cpp>
  #include <loggers/file_logger.cpp>
  #include <loggers/locked_logger.cpp>
  #include <loggers/null_logger.cpp>
  #include <loggers/program_wide_logger.cpp>
  #include <opsystem/application_base.cpp>
  #include <opsystem/application_shell.cpp>
  #include <opsystem/byte_filer.cpp>
  #include <opsystem/command_line.cpp>
  #include <opsystem/critical_events.cpp>
  #include <opsystem/directory.cpp>
  #include <opsystem/filename.cpp>
  #include <opsystem/ini_config.cpp>
  #include <opsystem/ini_parser.cpp>
  #include <opsystem/path_configuration.cpp>
  #include <opsystem/rendezvous.cpp>
  #include <opsystem/version_ini.cpp>
  #include <textual/byte_format.cpp>
  #include <textual/parser_bits.cpp>
  #include <textual/string_manipulation.cpp>
  #include <textual/tokenizer.cpp>
#endif // __BUILD_STATIC_APPLICATION__

