// fgpickbest.cc

#include "fgpickbest.h"

#ifndef _FGALIST_H
#include "fgalist.h"
#endif

#ifndef _FGBDFNAME_H
#include "fgbdfname.h"
#endif

#ifndef _FGDLIST_H
#include "fgdlist.h"
#endif

#ifndef _FGMRANK_H
#include "fgmrank.h"
#endif

#ifndef _FGDLACTION_H
#include "fgdlaction.h"
#endif

#ifndef _FGDELACTION_H
#include "fgdelaction.h"
#endif

#ifndef __SGI_STL_MAP_H
#include <map>
#endif

#include <assert.h>

#undef DEBUG_PICKER

struct FGPickBest::Internal_FGPickBest {
  Internal_FGPickBest(const FGString& fname);

  int mRevisionsCount;
  FGBrokenDownFileName mFileBits;
};

FGPickBest::Internal_FGPickBest::Internal_FGPickBest(const FGString& fname)
: mFileBits(fname)
{
}

FGPickBest::FGPickBest(FGConnectionInterface* pConnIf, const FGString& match,
                       int count)
: FGFilePickerInterface(pConnIf),
  mpInternals(0)
{
  assert(count > 0);

  mpInternals = new Internal_FGPickBest(match);
  mpInternals->mRevisionsCount = count;
}

FGPickBest::~FGPickBest()
{
  if (mpInternals)
  {
    delete mpInternals;
    mpInternals = 0;
  }
}

FGActionList
FGPickBest::DecideActions(const FGDirListing& localDir,
                          const FGDirListing& remoteDir)
{
  FGActionList ret;

  // Special structures used in this algorithm
  typedef enum _floc {
    kLocal = 0,
    kRemote,
    kBoth
  } EFGFileLocation;

  // n.b. 2nd arg, int, isn't allowed (by ANSI) to be of type
  // EFGFileLocation
  typedef std::pair<FGFileInfo, int> FGPickMapInfo;
  typedef std::map<FGMatchRanking, FGPickMapInfo> FGPickMap;
  typedef FGPickMap::iterator FGPickMapIterator;
  typedef FGPickMap::const_iterator FGPickMapCIterator;

  // The sacred map itself
  FGPickMap mainMap;

  // Inject all local matches into map
  std::vector<FGFileInfo>::const_iterator iFiles;

  for (iFiles = localDir.begin(); iFiles != localDir.end(); iFiles++) {

    // Rule based matching only considers files
    if (!iFiles->IsRegularFile()) {
      continue;
    }
    FGString fileName = iFiles->GetFileName();

    FGMatchRanking thisRank = mpInternals->mFileBits.GetRanking(fileName);

    if (thisRank.IsMatch()) {

#ifdef DEBUG_PICKER
      printf("Local file found\n");
#endif

      FGPickMapInfo newPair;
      newPair.first = *iFiles;
      newPair.second = kLocal;

      FGPickMap::value_type theValue(thisRank, newPair);

      mainMap.insert(theValue);
    }
  }

  // Now inject all remote files into the map
  // Any duplicates get their location flag set to "both"
  for (iFiles = remoteDir.begin(); iFiles != remoteDir.end(); iFiles++) {
    FGString fileName = iFiles->GetFileName();

    // Rule based matching only considers files
    if (!iFiles->IsRegularFile()) {
      continue;
    }

    FGMatchRanking thisRank = mpInternals->mFileBits.GetRanking(fileName);

    // If we have less matches than we want, or the current match is
    // better than our worst match yet, take it
    if (thisRank.IsMatch()) {

#ifdef DEBUG_PICKER
      printf("Remote file found\n");
#endif

      // Two cases - rank value already in map, or not
      FGPickMapIterator iMap = mainMap.find(thisRank);
      if (iMap == mainMap.end()) {
        // Not there yet
        FGPickMapInfo newPair;
        newPair.first = *iFiles;
        newPair.second = kRemote;

        FGPickMap::value_type theValue(thisRank, newPair);

        mainMap.insert(theValue);
      } else {
        // Yes, already there
        iMap->second.second = kBoth;
      }
    }
  }

  // Use our map to decide actions
  // Empty map? Just get out..
  if (mainMap.size() == 0) {
    return ret;
  }

  FGPickMapCIterator iAllMap = mainMap.end();
  // end() points off end => back iterator off one
  iAllMap--;
  int wanted = mpInternals->mRevisionsCount;
  do {

    // Case 1: we are in the wanted region
    if (wanted > 0) {
      // Within the wanted region, make download actions
      // for all "remote only" files
      if (iAllMap->second.second == kRemote) {
        ret.push_back(new FGDownloadAction(iAllMap->second.first.GetFileName(),
                                           mpConnIf,
                                           localDir.GetDirName(),
                                           iAllMap->second.first.GetSize()));
      }
    } else {
      // Case 2: we are in the ditch region
      // Within the ditch region, make delete actions for
      // all "local only" or "local and remote" files
      EFGFileLocation fl = (EFGFileLocation)iAllMap->second.second;
      if (fl == kLocal || fl == kBoth) {
        ret.push_back(new FGDeleteAction(iAllMap->second.first.GetFileName(),
                                         localDir.GetDirName()));
      }
    }

    // Make sure we only try and keep the top N matches on local disk
    wanted--;

    // Decrement the iterator (move towards worse matches)

  } while (iAllMap-- != mainMap.begin());

  return ret;
}
