#include "galaxy.h"

#include <algorithm>  // sort
#include <boost/format.hpp>
#include <chrono>
#include <exception>
#include <fstream>
#include <ostream>
#include <set>
#include <string>

#include "config.h"
#include "logging.h"
#include "yaml-cpp/yaml.h"

// c++ default exceptions list
// https://en.cppreference.com/w/cpp/error/exception

std::ostream &operator<<(std::ostream &os, const port &p) {
  if (p.type == 0) {
    os << p.sector << ": " << (int)p.type;
  } else {
    os << p.sector << ": " << (int)p.type << " " << text_from_type(p.type)
       << " " << p.amount[0] << "," << p.amount[1] << "," << p.amount[2];
  }
  return os;
}

/**
 * Is port unknown?
 *
 * As in we haven't visited it, we don't know what it has?
 * We were checking percent to 0, but we've seen valid ports with 0 percent.
 * We now check amount.  If this becomes an issue, we'll change to an unknown
 * flag.
 *
 * @return true
 * @return false
 */
bool port::unknown(void) {
  for (int x = 0; x < 3; ++x) {
    if (amount[x] != 0) return false;
  }
  return true;
}

density_scan::density_scan() { reset(0); }

void density_scan::reset(sector_type s) {
  sector = s;
  pos = 0;
  for (int x = 0; x < (int)d.size(); ++x) {
    d[x].sector = 0;
  }
}

void density_scan::add_scan(density den) {
  if (pos > (int)d.size()) {
    std::string message("density_scan::add_scan() exceeded max size ");
    message.append(std::to_string(d.size()));
    throw std::out_of_range(message);
  }

  d[pos] = den;
  ++pos;
}

density density_scan::find(sector_type sector) {
  for (int x = 0; x < pos; ++x) {
    if (d[x].sector == sector) return d[x];
  }
  BUGZ_LOG(fatal) << "density_scan::find failed: " << sector << " we have "
                  << pos << " scans.";
  density den;
  den.sector = 0;
  return den;
}

bool operator==(const density lhs, const density rhs) {
  return ((lhs.sector == rhs.sector) && (lhs.density == rhs.density) &&
          (lhs.navhaz == rhs.navhaz) && (lhs.anomaly == rhs.anomaly) &&
          (lhs.warps == rhs.warps) && (lhs.known == rhs.known));
}

sector_warps::sector_warps() { sector = 0; }
sector_warps::sector_warps(sector_type s, std::set<sector_type> w) {
  sector = s;
  warps = w;
}

void sector_warps::add(sector_type new_sector) {
  warps.insert(new_sector);
  /*
  for (int x = 0; x < MAX_WARPS; ++x) {
    if (warps[x] == new_sector) return;
    if (warps[x] == 0) {
      warps[x] = new_sector;
      return;
    }
  }
  std::string message = str(boost::format("More then MAX %1% sectors for %2%") %
                            MAX_WARPS % (int)sector);
  throw std::out_of_range(message);
  */
}

std::ostream &operator<<(std::ostream &os, const sector_warps &warps) {
  os << "Sector: " << warps.sector << " ";
  bool comma = false;
  for (auto const &warp : warps.warps) {
    if (comma)
      os << ",";
    else
      comma = true;
    os << warp;
  }
  return os;
}

#define GTEST_COUT std::cerr << "[          ] [ INFO ]"
// #define GTEST_DEBUG

// TODO:  fix this.  I want some trace output, but I don't want
// my logs flooded ...

struct port parse_portcim(const std::string line) {
  struct port p;
  p.sector = std::stoi(line);
  //  20 - 1708  97% -  710  56%    287  15%
  static std::regex portrx(
      "[ ]*([0-9]+) (.)[ ]+([0-9]+)[ ]+([0-9]+%) (.)[ "
      "]+([0-9]+)[ ]+([0-9]+%) (.)[ ]+([0-9]+)[ ]+([0-9]+%)[ ]*",
      std::regex_constants::ECMAScript);

  // does it not understand {3} ??
  // NO, it does not, from regex101.com:
  // A repeated capturing group will only capture the last iteration. Put a
  // capturing group around the repeated group to capture all iterations or use
  // a non-capturing group instead if you're not interested in the data
  //
  //  static std::regex portrx("[ ]*([0-9]+)( (.)[ ]+([0-9]+)[ ]+([0-9]+%)){3}[
  //  ]*",
  //                           std::regex_constants::ECMAScript);

  // sector + amount pct + amount pct + amount pct
  // 1      2 3      4   5 6      7   8 9      10

#ifdef GTEST_DEBUG
  GTEST_COUT << "Sector: " << p.sector << std::endl;
  GTEST_COUT << "Line: [" << line << "]" << std::endl;
#endif

  buysell port_buysell;

  std::smatch matches;
  if (std::regex_match(line, matches, portrx)) {
#ifdef GTEST_DEBUG
    for (size_t x = 1; x < matches.size(); ++x) {
      GTEST_COUT << x << " : " << matches[x] << std::endl;
    }
#endif

    if (matches.size() != 11) {
#ifdef GTEST_DEBUG
      GTEST_COUT << "Now you have 101 problems." << std::endl;
#endif
      p.sector = 0;
      p.type = 0;
      return p;
    }
    // GTEST_COUT << "matches: " << matches.size() << std::endl;
    p.sector = stoi(matches[1]);
    // GTEST_COUT << "sector: " << matches[1] << std::endl;
    // for (int x = 1; x < 11; ++x) {
    //   GTEST_COUT << x << " : " << matches[x] << std::endl;
    // }
    for (int x = 0; x < 3; ++x) {
      int pos = x * 3;
      port_buysell.foe[x] = matches[pos + 2] == "-";
      p.amount[x] = stoi(matches[pos + 3]);
      p.percent[x] = stoi(matches[pos + 4]);
    }
    p.type = type_from_buysell(port_buysell);
#ifdef GTEST_DEBUG
    GTEST_COUT << "port is type " << (int)p.type << std::endl;
#endif
    return p;
  } else {
#ifdef GTEST_DEBUG
    GTEST_COUT << "regex_match failed." << std::endl;
#endif
    p.type = 0;
    p.sector = 0;
    return p;
  }
}

Galaxy::Galaxy() { burnt_percent = 40; }

Galaxy::~Galaxy() { BUGZ_LOG(fatal) << "Galaxy::~Galaxy()"; }

void Galaxy::reset(void) {
  meta = YAML::Node();
  config = YAML::Node();
  ports.clear();
  warps.clear();
}

void Galaxy::add_warp(sector_warps sw) {
  auto pos = warps.find(sw.sector);

  if (pos == warps.end()) {
    // not found
    // sw.sort();
    warps[sw.sector] = sw;
    // BUGZ_LOG(info) << "add_warp NEW " << sw.sector;
  } else {
    // found!
    if (pos->second.warps == sw.warps) {
      // BUGZ_LOG(trace) << "add_warp: Yup, I already know about " << sw.sector;
    } else {
      BUGZ_LOG(info) << "add_warp:  Warps don't match! Updating...";
      BUGZ_LOG(warning) << "Have: " << pos->second;
      BUGZ_LOG(warning) << "Got : " << sw;
      warps[sw.sector] = sw;
    }
  }
}

void Galaxy::add_port(sector_type sector, int port_type) {
  auto pos = ports.find(sector);
  if (pos == ports.end()) {
    // no such port.
    port p;
    p.sector = sector;
    p.type = port_type;
    for (int x = 0; x < 3; x++) {
      p.amount[x] = 0;
      p.percent[x] = 0;
    }
    // BUGZ_LOG(trace) << "add_port: " << sector << ", " << port_type << " : "
    // << p;
    ports[sector] = p;
  } else {
    // port was found, so:
    if (pos->second.type == port_type) {
      // BUGZ_LOG(trace) << "add_port: Yup, port " << sector << " is class " <<
      // port_type;
    } else {
      BUGZ_LOG(fatal) << "add_port: " << sector << " shows " << pos->second.type
                      << " >> set to " << port_type;
      pos->second.type = port_type;
    }
  }
}

void Galaxy::add_port(port p) {
  auto pos = ports.find(p.sector);
  if (pos == ports.end()) {
    // BUGZ_LOG(trace) << "add_port: NEW " << p;
    ports[p.sector] = p;
  } else {
    if (pos->second.type != p.type) {
      if ((pos->second.type == 9) && (p.type == 8)) {
        BUGZ_LOG(trace) << "add_port: StarDock " << p.sector;
        p.type = 9;
        ports[p.sector] = p;
      } else {
        BUGZ_LOG(fatal) << "add_port: " << pos->second << " NEW : " << p;
        ports[p.sector] = p;
      }
    } else {
      if (pos->second.amount != p.amount) {
        // BUGZ_LOG(info) << "add_port: UPDATE " << p.sector;
        pos->second = p;
      } else {
        // BUGZ_LOG(info) << "add_port: Yup " << p.sector;
      }
    }
  }
}

void Galaxy::load(void) {
  std::string basename = "galaxy";

  if (CONFIG["basename"]) {
    basename = CONFIG["basename"].as<std::string>();
  }
  std::string filename =
      str(boost::format("%1%-%2%-%3%.yaml") % basename % game % username);

  // reset ?
  meta = YAML::Node();
  config = YAML::Node();
  ports.clear();
  warps.clear();
  if (file_exists(filename)) {
    YAML::Node data = YAML::LoadFile(filename);
    if (config["meta"]) meta = config["meta"];
    meta["load_from"] = filename;
    std::chrono::_V2::system_clock::time_point now =
        std::chrono::system_clock::now();

    meta["load_time"] = std::chrono::system_clock::to_time_t(now);  // time_t
    if (data["config"]) {
      config = data["config"];
    } else {
      BUGZ_LOG(fatal) << "YAML Missing config section.";
    }
    if (data["ports"]) {
      const YAML::Node ports = data["ports"];
      for (auto const &port_iter : ports) {
        port p;
        p.sector = port_iter.first.as<int>();
        p.type = port_iter.second["class"].as<int>();
        int x;
        if (p.type == 0) {
          // nothing to load for type = 0, set defaults.
          for (x = 0; x < 3; ++x) {
            p.amount[x] = 0;
            p.percent[x] = 0;
          }
        } else {
          x = 0;
          for (auto const &amount : port_iter.second["amount"]) {
            p.amount[x] = amount.as<int>();
            ++x;
          }
          x = 0;
          for (auto const &pct : port_iter.second["pct"]) {
            p.percent[x] = pct.as<int>();
            ++x;
          }
        }
        add_port(p);
      }
    } else {
      BUGZ_LOG(fatal) << "YAML Missing ports section.";
    }
    if (data["warps"]) {
      const YAML::Node &warps = data["warps"];
      // if (warps.IsMap()) {
      for (auto const warp_iter : warps) {
        sector_warps sw;
        sw.sector = warp_iter.first.as<int>();
        for (auto const sector_iter : warp_iter.second) {
          sw.add(sector_iter.as<int>());
        }
        // BUGZ_LOG(trace) << "YAML warp: " << sw;
        add_warp(sw);
      }
      // }
    } else {
      BUGZ_LOG(fatal) << "YAML Missing warps section.";
    }

    BUGZ_LOG(fatal) << "YAML: config keys: " << config.size();
    BUGZ_LOG(fatal) << "YAML:   warp keys: " << warps.size();
    BUGZ_LOG(fatal) << "YAML:   port keys: " << ports.size();

  } else {
    BUGZ_LOG(fatal) << "Missing YAML: " << filename;
  }
}

void Galaxy::save(void) {
  std::string basename = "galaxy";

  if (CONFIG["basename"]) {
    basename = CONFIG["basename"].as<std::string>();
  }
  std::string filename =
      str(boost::format("%1%-%2%-%3%.yaml") % basename % game % username);

  std::ofstream fout(filename);
  fout << "%YAML 1.2" << std::endl << "---" << std::endl;

  BUGZ_LOG(fatal) << "YAML: " << filename;
  int depth = 0;
  std::string depth_spacer;

  meta["save_to"] = filename;
  std::chrono::_V2::system_clock::time_point now =
      std::chrono::system_clock::now();
  meta["save_time"] = std::chrono::system_clock::to_time_t(now);  // time_t

  /*   // testing sequence code
  meta["sequence"]["part"].push_back(1);
  meta["sequence"]["part"].push_back(2);
  meta["sequence"]["part"].push_back(3);
  meta["sequence"]["smeg"].push_back(0);
  */

  // meta:
  fout << "meta:" << std::endl;
  ++depth;
  depth_spacer.assign(depth * 2, ' ');

  std::function<void(std::ofstream & of, int depth, const YAML::Node &n)>
      yaml_out;

  yaml_out = [&yaml_out](std::ofstream &of, int yaml_depth,
                         const YAML::Node &n) {
    std::string yaml_spacer;
    yaml_spacer.assign(yaml_depth * 2, ' ');
    for (auto const &data : n) {
      if (data.second.Type() == YAML::NodeType::Scalar) {
        of << yaml_spacer << data.first << ": " << data.second << std::endl;
      } else if (data.second.Type() == YAML::NodeType::Map) {
        // nested
        of << yaml_spacer << data.first << ":" << std::endl;
        yaml_out(of, yaml_depth + 1, data.second);
      } else if (data.second.Type() == YAML::NodeType::Sequence) {
        // sequence
        BUGZ_LOG(fatal) << "Outputting Sequence... " << data.first;
        of << yaml_spacer << data.first << ": [";
        bool first = true;
        for (auto const &seq : data.second) {
          if (!first)
            of << ", ";
          else
            first = false;
          of << seq;
        }
        of << "]" << std::endl;
      } else {
        BUGZ_LOG(fatal) << "Unsupported NodeType: " << data.second.Type();
      }
    }
  };

  yaml_out(fout, depth, meta);

  BUGZ_LOG(fatal) << "YAML config: " << config.size();
  fout << "config:" << std::endl;

  // in config, I usually switch to doing flow instead.  I'll keep this like
  // this for now.
  yaml_out(fout, depth, config);

  BUGZ_LOG(fatal) << "YAML warps: " << warps.size();
  fout << "warps:" << std::endl;

  for (auto const &warp : warps) {
    fout << depth_spacer << warp.first << ": [";
    bool first = true;
    for (auto const &sector : warp.second.warps) {
      if (!first) {
        fout << ", ";
      } else
        first = false;
      fout << sector;
    }
    fout << "]" << std::endl;
  }

  BUGZ_LOG(fatal) << "YAML ports: " << ports.size();
  fout << "ports:" << std::endl;

  /*
  When saving to yaml, my sector_type is like char.  So, it wants
  to save the values as a character.  Cast to int.
   */
  for (auto const &port : ports) {
    fout << depth_spacer << port.second.sector
         << ": {class: " << (int)port.second.type;
    if (port.second.type == 0) {
      fout << "}" << std::endl;
    } else {
      // write out the rest of the information
      fout << ", amount: [";
      for (int x = 0; x < 3; x++) {
        fout << port.second.amount[x];
        if (x != 2) fout << ", ";
      }
      fout << "], pct: [";
      for (int x = 0; x < 3; x++) {
        fout << (int)port.second.percent[x];
        if (x != 2) fout << ", ";
      }
      fout << "]}" << std::endl;
    }
  }

  // the big data structures later on are the ones I really need to optimize
  // here. Writing out 20 lines isn't what is killing us!

  /*
  for (auto const & meta_data : meta ) {
    std::cerr << "key: " << meta_data.first << std::endl;

    if (meta_data.second.Type() == YAML::NodeType::Scalar) {
      fout << depth_spacer << meta_data.first << ": " << meta_data.second <<
  std::endl; } else {
      // nested structure
      std::cerr << "Nested: " << meta_data.first << std::endl;
      fout << depth_spacer << meta_data.first << ":" << std::endl;
      ++depth;
      depth_spacer.assign(depth *2, ' ');
      for (auto const &nested : meta_data.second) {
         if (nested.second.Type() == YAML::NodeType::Scalar) {
           fout << depth_spacer << nested.first << ": " << nested.second <<
  std::endl; } else { std::cerr << "double-Nested meta structure under key: " <<
  nested.first << std::endl;
        }
      }
      --depth;
      depth_spacer.assign(depth * 2, ' ');
    }
  }
  */
}

std::vector<port_pair_type> Galaxy::find_trades(sector_type sector,
                                                bool highest) {
  std::vector<port_pair_type> pptv;

  // Does this sector have a port?
  auto port = ports.find(sector);
  if (port == ports.end()) return pptv;

  auto port_warps = warps.find(sector);
  // warps not found for port sector?
  if (port_warps == warps.end()) return pptv;

  for (auto const &s : port_warps->second.warps) {
    // only count the ports > our sector number -- so we don't have dups
    if (highest && (s < sector)) continue;

    // verify we have a way back
    {
      auto warpback = warps.find(s);
      // can we find that warp?
      if (warpback == warps.end()) continue;
      // does it link back to the port's sector?
      if (warpback->second.warps.find(sector) == warpback->second.warps.end())
        continue;
    }

    // Does this sector have a port?
    auto possible_port = ports.find(s);
    if (possible_port == ports.end()) continue;

    if (possible_port->second.type == 0) continue;

    // calculate trade type
    // int t = trade_type(port->second.type, possible_port->second.type);
    BUGZ_LOG(trace) << "find_trades: Port " << sector << ","
                    << (int)port->second.type << " " << s
                    << (int)possible_port->second.type;
    trade_type_result ttr = trade_type_info(
        sector, s);  // port->second.type, possible_port->second.type);
    if ((ttr.type == NONE) || (ttr.type == FAIR_F)) continue;

    pptv.push_back(port_pair_type{ttr.type, ttr.trades, sector, s});
    BUGZ_LOG(trace) << "sector: " << sector << " and " << s
                    << " tt:" << ttr.type;
  }
  return pptv;
}

bool compare_port_pair(const port_pair_type &ppt1, const port_pair_type &ppt2) {
  if (ppt1.type == ppt2.type) {
    return ppt1.s1 < ppt2.s1;
  } else {
    return (ppt1.type < ppt2.type);
  }
}

void Galaxy::sort_port_pair_type(std::vector<port_pair_type> &pptv) {
  sort(pptv.begin(), pptv.end(), compare_port_pair);
}

std::vector<port_pair_type> Galaxy::find_best_trades(void) {
  std::vector<port_pair_type> pptv;

  burnt_percent = config["burnt_percent"].as<int>();
  if (burnt_percent > 90) {
    burnt_percent = 90;
    config["burnt_percent"] = 90;
  }
  if (burnt_percent < 10) {
    burnt_percent = 10;
    config["burnt_percent"] = 10;
  }

  for (auto const &pi : ports) {
    if (pi.second.type == 0) continue;
    sector_type sector = pi.second.sector;
    auto port_warps = warps.find(sector);
    if (port_warps == warps.end()) continue;

    std::vector<port_pair_type> ppt_sector = find_trades(sector, true);
    if (ppt_sector.empty()) continue;
    pptv.insert(pptv.end(), ppt_sector.begin(), ppt_sector.end());
  }
  return pptv;
}

/**
 * Find_closest trade
 *
 * This works by getting all of the ports we know of.
 * Then, we find the nearest.
 *
 * @param sector
 * @return port_pair_type
 */
port_pair_type Galaxy::find_closest(int sector) {
  auto trades = find_best_trades();
  // int type, sector_type s1, s2;
  std::set<sector_type> seen;
  std::set<sector_type> current;
  current.insert(sector);
  bool found = false;
  bool added_new = false;
  int depth = 0;

  while (!found) {
    // is current list in any of the trades ?
    for (auto const &t : trades) {
      if (t.type > 2) continue;
      if (current.find(t.s1) != current.end()) {
        // found one!
        return t;
      }
      if (current.find(t.s2) != current.end()) {
        return t;
      }
    }

    ++depth;
    BUGZ_LOG(info) << "depth: " << depth << " current:" << current.size()
                   << " seen: " << seen.size();
    added_new = false;

    if (!found) {
      // update the seen
      seen.insert(current.begin(), current.end());
      auto look_in = current;
      current.clear();
      for (auto const &li : look_in) {
        auto wi = warps.find(li);
        if (wi == warps.end()) continue;
        for (auto const &w : wi->second.warps) {
          // have we already seen this sector?
          if (seen.find(w) != seen.end()) continue;

          // does it have a warp back to the original sector?
          auto warp_back = warps.find(w);
          if (warp_back != warps.end()) {
            if (warp_back->second.warps.find(li) !=
                warp_back->second.warps.end()) {
              // Ok, this links back to the original sector...
              current.insert(w);
              added_new = true;
            }
          }
        }
      }
      if (!added_new) {
        BUGZ_LOG(warning) << "No new sectors added.  We're done!";
        port_pair_type ppt;
        ppt.type = 0;
        return ppt;
      }
    }
  }
  port_pair_type ppt;
  ppt.type = 0;
  return ppt;
}

/**
 * Find closest and best trade
 *
 * @param sector
 * @param lowest_trade_type
 * @return port_pair_type
 */
port_pair_type Galaxy::find_closest_trade(int sector, int lowest_trade_type,
                                          int burnt_percent) {
  // int type, sector_type s1, s2;
  BUGZ_LOG(fatal) << "find_closest_trade(" << sector << ")";
  std::vector<port_pair_type> vppt;
  std::set<sector_type> seen;
  std::set<sector_type> current;
  current.insert(sector);
  bool found = false;
  bool added_new = false;
  int depth = 0;

  while (!found) {
    /*
    for (auto const &t : trades) {
      if (t.type > 2) continue;
      if (current.find(t.s1) != current.end()) {
        // found one!
        return t;
      }
      if (current.find(t.s2) != current.end()) {
        return t;
      }
    }
    */
    ++depth;
    BUGZ_LOG(info) << "depth: " << depth << " current:" << current.size()
                   << " seen: " << seen.size();

    // search current for trades
    for (auto const &c : current) {
      auto port = ports.find(c);
      if (port == ports.end()) continue;
      if (port->second.type == 0) continue;
      auto port_warps = warps.find(c);
      if (port_warps == warps.end()) continue;
      for (auto const &s : port_warps->second.warps) {
        // verify we have a way back
        {
          auto warpback = warps.find(s);
          if (warpback == warps.end()) continue;
          if (warpback->second.warps.find(c) == warpback->second.warps.end())
            continue;
        }

        auto possible_port = ports.find(s);
        if (possible_port == ports.end()) continue;
        if (possible_port->second.type == 0) continue;
        trade_type_result ttr = trade_type_info(c, s, burnt_percent);
        if ((ttr.type == NONE) || (ttr.type > lowest_trade_type)) continue;
        // Ok! we found a trade that fits the criteria!
        vppt.push_back(port_pair_type{ttr.type, ttr.trades, c, s});
        found = true;
      }
    }
    added_new = false;

    if (found) {
      // ok, we've found some trades, sort and return the best
      sort_port_pair_type(vppt);
      return port_pair_type{vppt[0]};
    } else {
      // update the seen
      seen.insert(current.begin(), current.end());
      auto look_in = current;
      current.clear();
      for (auto const &li : look_in) {
        auto wi = warps.find(li);
        if (wi == warps.end()) continue;
        for (auto const &w : wi->second.warps) {
          // have we already seen this sector?
          if (seen.find(w) != seen.end()) continue;

          // does it have a warp back to the original sector?
          auto warp_back = warps.find(w);
          if (warp_back != warps.end()) {
            if (warp_back->second.warps.find(li) !=
                warp_back->second.warps.end()) {
              // Ok, this links back to the original sector...
              current.insert(w);
              added_new = true;
            }
          }
        }
      }
      if (!added_new) {
        BUGZ_LOG(warning) << "No new sectors added.  We're done!";
        port_pair_type ppt;
        ppt.type = 0;
        return ppt;
      }
    }
  }
  port_pair_type ppt;
  ppt.type = 0;
  return ppt;
}

sector_type Galaxy::find_nearest_unexplored(sector_type sector) {
  // search the galaxy for unexplored
  std::set<sector_type> seen;
  std::set<sector_type> current;
  current.insert(sector);
  bool found = false;
  bool added_new = false;
  int depth = 0;

  while (!found) {
    ++depth;
    BUGZ_LOG(info) << "depth: " << depth << " current:" << current.size()
                   << " seen: " << seen.size();

    // search for warps
    for (auto const &c : current) {
      auto port_warps = warps.find(c);
      if (port_warps == warps.end()) return c;
    }

    // update the seen
    seen.insert(current.begin(), current.end());
    auto look_in = current;
    current.clear();
    for (auto const &li : look_in) {
      auto wi = warps.find(li);
      if (wi == warps.end()) return li;
      for (auto const &w : wi->second.warps) {
        // have we already seen this sector?
        if (seen.find(w) != seen.end()) continue;

        current.insert(w);
        added_new = true;
      }
    }

    if (!added_new) {
      BUGZ_LOG(warning) << "No new sectors added.  We're done!";
      found = false;
    }
  }
  return 0;
}

trade_type_result Galaxy::trade_type_info(sector_type port1, sector_type port2,
                                          int burnt_percent) {
  BUGZ_LOG(fatal) << "Trade_type_info(" << port1 << "," << port2 << ")";
  // This only gives us one trade_type per pair.  There actually
  // should be multiple values returned here!
  // Like in case of BBB/SSS:  return 3, 4 and 5.

  // NONE = 0
  // GOOD = 1 = OE PAIR
  // OK   = 2 = ?? Pair
  // FAIR = 3 = B/S E
  //        4 = B/S O
  //        5 = B/S F
  buysell inv2;

  auto p1 = ports.find(port1);
  if (p1 == ports.end()) {
    BUGZ_LOG(fatal) << "Can't find port 1: " << (int)port1;
    return {NONE, inv2};
  }
  BUGZ_LOG(fatal) << "port 1: " << p1->first << " " << p1->second.sector << ", "
                  << (int)p1->second.type;
  auto p2 = ports.find(port2);

  if (p2 == ports.end()) {
    BUGZ_LOG(fatal) << "Can't find port 2: " << (int)port2;
    return {NONE, inv2};
  }
  BUGZ_LOG(fatal) << "port 2: " << p2->first << " " << p2->second.sector << ", "
                  << (int)p2->second.type;

  buysell bsp1 = get_buysell(p1->second.type);
  buysell bsp2 = get_buysell(p2->second.type);

  inv2 = invert_buysell(bsp2);
  int matches = 0;  // or pos.size();
  std::vector<int> pos;

  // If we don't know how many holds the ship has, default to 300.
  int max_holds = 300;
  if (meta["ship"]["holds"]["total"]) {
    max_holds = meta["ship"]["holds"]["total"].as<int>();
  }

  // find which FOE are flipped.  Save index pos.
  for (int x = 0; x < 3; ++x) {
    inv2.foe[x] = (bsp1.foe[x] == inv2.foe[x]);
    // Ok, these are possible trades (B->S or S->B)

    // If known, check for burnt
    if (!p1->second.unknown()) {
      if (p1->second.percent[x] < burnt_percent) {
        BUGZ_LOG(fatal) << "Marking Port 1: " << x << " (burnt)";
        inv2.foe[x] = false;
      }
      if (p1->second.amount[x] < max_holds) {
        BUGZ_LOG(fatal) << "Marking Port 1: " << x << " (burnt/amount)";
        inv2.foe[x] = false;
      }
    } else {
      BUGZ_LOG(fatal) << "Port 1 : unknown / skip burnt checks";
    }
    if (!p2->second.unknown()) {
      if (p2->second.percent[x] < burnt_percent) {
        BUGZ_LOG(fatal) << "Marking Port 2: " << x << " (burnt)";
        inv2.foe[x] = false;
      }
      if (p2->second.amount[x] < max_holds) {
        BUGZ_LOG(fatal) << "Marking Port 2: " << x << " (burnt/amount)";
        inv2.foe[x] = false;
      }
    } else {
      BUGZ_LOG(fatal) << "Port 2 : unknown / skip burnt checks";
    }

    if (inv2.foe[x]) {
      matches++;
      pos.push_back(x);
    }
  }

  BUGZ_LOG(fatal) << "Matches: " << matches << " inv: " << inv2;

  if (matches > 1) {
    // Check for BEST
    // O != E for both ports, and O != O, and ORG/EQU in inv2 list
    if (inv2.foe[ORG] && inv2.foe[EQU] && (bsp1.foe[ORG] != bsp1.foe[EQU]) &&
        (bsp2.foe[ORG] != bsp2.foe[EQU]) && (bsp1.foe[ORG] != bsp2.foe[ORG])) {
      // verify that fuel isn't set.
      inv2.foe[FUEL] = false;
      BUGZ_LOG(fatal) << "result: " << BEST << " " << inv2;
      return trade_type_result{BEST, inv2};
    }

    if (matches == 3) {
      // This could be SBB / BSS (it's a pair, but not BEST)
      // Is it FO or FE ?

      if (bsp1.foe[FUEL] != bsp2.foe[EQU]) {
        // OK, FE
        inv2.foe[ORG] = false;
        BUGZ_LOG(fatal) << "result: " << OK << " " << inv2;
        return trade_type_result{OK, inv2};
      }

      if (bsp1.foe[FUEL] != bsp2.foe[ORG]) {
        // OK, FO
        inv2.foe[EQU] = false;
        BUGZ_LOG(fatal) << "result: " << OK << " " << inv2;
        return trade_type_result{OK, inv2};
      }

      // Ok, take the highest (EQU)
      inv2.foe[FUEL] = false;
      inv2.foe[ORG] = false;
      BUGZ_LOG(fatal) << "result: " << FAIR_E << " " << inv2;
      return trade_type_result{FAIR_E, inv2};
    }

    // 2 matches.  but are they trade pairs?
    if (bsp1.foe[pos[matches - 1]] != bsp1.foe[pos[matches - 2]]) {
      // yes!
      BUGZ_LOG(fatal) << "result: " << OK << " " << inv2;
      return trade_type_result{OK, inv2};
    } else {
      // they are NOT.  Use highest one.  clear the lower flag

      inv2.foe[pos[0]] = false;

      switch (pos[1]) {
        case 0:
          BUGZ_LOG(fatal) << "result: " << FAIR_F << " " << inv2;
          return trade_type_result{FAIR_F, inv2};
          break;
        case 1:
          BUGZ_LOG(fatal) << "result: " << FAIR_O << " " << inv2;
          return trade_type_result{FAIR_O, inv2};
          break;
        case 2:
          BUGZ_LOG(fatal) << "result: " << FAIR_E << " " << inv2;
          return trade_type_result{FAIR_E, inv2};
          break;
      }
    }
  }

  if (matches == 1) {
    switch (pos[0]) {
      case 0:
        BUGZ_LOG(fatal) << "result: " << FAIR_F << " " << inv2;
        return trade_type_result{FAIR_F, inv2};
        break;
      case 1:
        BUGZ_LOG(fatal) << "result: " << FAIR_O << " " << inv2;
        return trade_type_result{FAIR_O, inv2};
        break;
      case 2:
        BUGZ_LOG(fatal) << "result: " << FAIR_E << " " << inv2;
        return trade_type_result{FAIR_E, inv2};
        break;
    }
  }

  // no matches.
  BUGZ_LOG(fatal) << "result: " << NONE << " " << inv2;
  return trade_type_result{NONE, inv2};
}