#pragma once
#include "rendering_randomwalk_impl.hxx"
#include "renderingalgorithms_simplebase.hxx"

#include <unordered_map>
#include <boost/functional/hash.hpp>

#pragma GCC diagnostic push
#pragma GCC diagnostic warning "-Wunused-parameter"
#pragma GCC diagnostic warning "-Wunused-variable"

namespace RandomWalk
{
using namespace SimplePixelByPixelRenderingDetails;

// Ref: Veach's Thesis. Chpt. 10.
namespace BdptDetail
{

using NodeContainer = ToyVector<RW::PathNode>;
using PdfContainer  = ToyVector<Pdf>;
using SegmentContainer = ToyVector<RaySegment>;
using PathDensityContainer = ToyVector<double>;
using ConversionFactorContainer = ToyVector<double>;
using SpecularFlagContainer = ToyVector<bool>;
using WeightContainer = ToyVector<Spectral3>;

class SubpathHistory
{
public:
  SubpathHistory(RadianceEstimatorBase& _randomwalk_functions)
    : randomwalk_functions(_randomwalk_functions)
  {
  }

  void Reset()
  {
    // Note: Allocation only on first use. clear() keeps the capacity up. However, incurs dtor calls.
    fwd_conversion_factors.clear();
    bwd_conversion_factors.clear();
    pdfs.clear();
    pdfs_rev.clear();
    nodes.clear();
    betas.clear();
    segments.clear();
    is_parallel_beam = true;
  }

  void Start(const RW::PathNode &start, const Spectral3 &_weight, Pdf _start_pdf)
  {
    // Bah ... copying :-(
    nodes.push_back(start);
    betas.push_back(_weight);
    pdfs.push_back(_start_pdf);  // The probability density of the starting location.
    pdfs_rev.push_back(1.);
    segments.push_back(RaySegment{}); // For convenience
    fwd_conversion_factors.push_back(1.);
    bwd_conversion_factors.push_back(1.);
    assert(_start_pdf > 0 && std::isfinite(_start_pdf)); // Because it is the sample. So the probability to generate it must be positive!
  }

  void AddSegment(const RW::PathNode &end_node, const Spectral3 &_weight, Pdf pdf_prev_scatter, Pdf pdf_prev_rev_scatter, const VolumePdfCoefficients &volume_pdf_coeff, const RaySegment &segment)
  {
    nodes.push_back(end_node);
    const int idx = isize(nodes)-1;
    Spectral3  beta_at_node = betas.back()*_weight;
    betas.push_back(beta_at_node);
    pdfs.push_back(pdf_prev_scatter);
    pdfs_rev.push_back(pdf_prev_rev_scatter);
    segments.push_back(segment);
    is_parallel_beam &= pdf_prev_scatter.IsFromDelta();
    double geom_coeff = randomwalk_functions.PdfConversionFactor(nodes[idx-1], nodes[idx], segments[idx], FwdCoeffs(volume_pdf_coeff), is_parallel_beam);
    fwd_conversion_factors.push_back(geom_coeff);
    geom_coeff = randomwalk_functions.PdfConversionFactor(nodes[idx], nodes[idx-1], segments[idx].Reversed(), BwdCoeffs(volume_pdf_coeff), false);
    bwd_conversion_factors.push_back(geom_coeff);
    assert(pdf_prev_scatter > 0 && std::isfinite(pdf_prev_scatter)); // Because it is the sample. So the probability to generate it must be positive!
    assert(geom_coeff > 0);
  }

  void Finish()
  {
    fwd_conversion_factors.push_back(NaN); // Space for data of connection segment.
    pdfs.push_back(Pdf{});  // Dito.
    pdfs_rev.push_back(Pdf{});
    assert (fwd_conversion_factors.size() == nodes.size() + 1);
    assert (bwd_conversion_factors.size() == nodes.size());
    assert (pdfs.size() == nodes.size() + 1);
    assert (pdfs_rev.size() == pdfs.size());
  }

  void Pop()
  {
    assert (!nodes.empty());
    nodes.pop_back();
    pdfs.pop_back();
    pdfs_rev.pop_back();
    betas.pop_back();
    fwd_conversion_factors.pop_back();
    bwd_conversion_factors.pop_back();
    segments.pop_back();
  }

  int NumNodes() const
  {
    return isize(nodes);
  }

  const RW::PathNode& Node(int i) const
  {
    return nodes[i];
  }

  RW::PathNode& Node(int i)
  {
    return nodes[i];
  }

  const RW::PathNode& NodeFromBack(int i) const
  {
    return nodes[isize(nodes)-i-1];
  }

  const Spectral3& Beta(int i) const
  {
    return betas[i];
  }

  friend class BdptMis;
  friend class BackupAndReplace;
private:
  RadianceEstimatorBase& randomwalk_functions;
  ConversionFactorContainer fwd_conversion_factors;
  ConversionFactorContainer bwd_conversion_factors;
  ToyVector<RaySegment> segments; // TODO: Might be able to get rid of this.
  PdfContainer pdfs; // Initial node probability, then scatter pdf at *previous* node.
  PdfContainer pdfs_rev; // First 1, then scatter pdf with swapped arguments at *previous* node.
  NodeContainer nodes;
  WeightContainer betas;
  bool is_parallel_beam;
};

struct Connection
{
  Pdf eye_pdf;
  Pdf eye_pdf_rev;
  Pdf light_pdf;
  Pdf light_pdf_rev;
  VolumePdfCoefficients volume_pdf_coeff;
  int eye_index;
  int light_index;
  RaySegment segment;
};

using DebugBuffers = std::unordered_map<std::pair<int,int>, Spectral3ImageBuffer, util::pair_hash<int,int>>;

class BackupAndReplace
{
  SubpathHistory &h;
  int node_index;
  Pdf pdf;
  Pdf pdf_rev;
  double fwd_conversion_factor;

  void InitBothPathsNonEmpty(const RW::PathNode &other_end_node, Pdf pdf_node_scatter, Pdf pdf_node_scatter_rev, const RaySegment &segment, const VolumePdfCoefficients &vol_pdf_coeff)
  {
    assert (node_index >= 0 && other_end_node.node_type != RW::NodeType::ZERO_CONTRIBUTION_ABORT_WALK);
    // The pdf arrays contain the pdf at the previous node. So bump the index by one.
    pdf = h.pdfs[node_index+1];
    pdf_rev = h.pdfs_rev[node_index+1];
    // The conversion factor "pointing" to the next node.
    fwd_conversion_factor = h.fwd_conversion_factors[node_index+1];
    h.pdfs[node_index+1] = pdf_node_scatter;
    h.pdfs_rev[node_index+1] = pdf_node_scatter_rev;
    // Replace by conversion factor for going from solid angle at this node to position at the opposite node.
    h.fwd_conversion_factors[node_index+1] = h.randomwalk_functions.PdfConversionFactor(
      h.Node(node_index), other_end_node, segment, FwdCoeffs(vol_pdf_coeff), pdf_node_scatter.IsFromDelta());
  }

  void InitEitherSideEmpty(double one_or_start_position_pdf, double one_or_angular_emission_pdf)
  {
    // If this side has zero vertices, then it is a light (or not-implemented sensor), which was randomly hit,
    // and the pdf should have the node position density in the forward pdf and 1 in the reverse pdf.
    // On the other hand,
    // If the other side has zero vertices, then this is the path that did the random walk to hit the light.
    // It's forward pdf should be set to 1, and the reverse pdf to the angular emission density.
    //assert ((one_or_angular_emission_pdf==1.) ^ (one_or_start_position_pdf==1.));
    pdf = h.pdfs[node_index+1];
    pdf_rev = h.pdfs[node_index+1];
    fwd_conversion_factor = h.fwd_conversion_factors[node_index+1];
    h.pdfs[node_index+1] = one_or_start_position_pdf;
    h.pdfs_rev[node_index+1] = one_or_angular_emission_pdf;
    h.fwd_conversion_factors[node_index+1] = 1.;
  }

public:
  BackupAndReplace(const BackupAndReplace &) = delete;
  BackupAndReplace& operator=(const BackupAndReplace &) = delete;

  BackupAndReplace(SubpathHistory &_h, int _node_index, SubpathHistory &other_h, int other_node_index, Pdf pdf, Pdf pdf_rev, const RaySegment &segment, const VolumePdfCoefficients &vol_pdf_coeff)
    : h{_h}, node_index{_node_index}
  {
    if (_node_index >= 0 && other_node_index >= 0)
      InitBothPathsNonEmpty(other_h.Node(other_node_index), pdf, pdf_rev, segment, vol_pdf_coeff);
    else
      InitEitherSideEmpty(pdf, pdf_rev);
  }

  ~BackupAndReplace()
  {
    h.pdfs[node_index+1] = pdf;
    h.fwd_conversion_factors[node_index+1] = fwd_conversion_factor;
  }
};

class BdptMis
{
public:
  BdptMis(const Scene& scene)
  {
    one_over_number_of_splat_attempts_per_pixel = 1./(scene.GetCamera().xres*scene.GetCamera().yres);
  }

  double Compute(const SubpathHistory &eye, const SubpathHistory &light, const Connection &connection)
  {
    Reset(connection);
    ComputeSpecularFlags(eye, light, connection);
    ComputeForwardSubpathDensities(eye, light, connection.eye_index, connection.light_index, eye_densities);
    ComputeForwardSubpathDensities(light, eye, connection.light_index, connection.eye_index, light_densities);
    FactorInNumberWeight();
    double result = ComputePowerHeuristicWeight(connection);
    //double result = WeightByNumberOfAdmissibleTechniques(); // For debugging!
    return result;
  }
private:
  SpecularFlagContainer specular_flags;
  PathDensityContainer eye_densities;
  PathDensityContainer light_densities;
  int total_node_count;
  double one_over_number_of_splat_attempts_per_pixel;

  void Reset(const Connection &connection)
  {
    total_node_count = connection.eye_index+1+connection.light_index+1;
    specular_flags.clear();
    eye_densities.clear();
    light_densities.clear();
  }

  void ComputeSpecularFlags(const SubpathHistory &eye, const SubpathHistory &light, const Connection &connection)
  {
    for (int i=0; i<=connection.eye_index+1; ++i)
      specular_flags.push_back(eye.pdfs[i].IsFromDelta());
    for (int i=connection.light_index+1; i>=0; --i)
      specular_flags.push_back(light.pdfs[i].IsFromDelta());
    assert(specular_flags.size() == total_node_count + 2);
  }

  void ComputeForwardSubpathDensities(const SubpathHistory &fwd_path, const SubpathHistory &reverse_path,
                                      int idx_fwd, int idx_rev, PathDensityContainer &destination)
  {
    assert (fwd_path.fwd_conversion_factors[0] == 1.);
    destination.push_back(Pdf{1.}); // When there are zero nodes on this subpath.
    for (int i=0; i<=idx_fwd; ++i)
    {
      destination.push_back(destination.back()*
        fwd_path.fwd_conversion_factors[i] * fwd_path.pdfs[i]);
    }
    if (idx_rev >= 0) // Handling the connection segment.
    {
      destination.push_back(destination.back()*
        fwd_path.fwd_conversion_factors[idx_fwd+1] * fwd_path.pdfs[idx_fwd+1]);
    }
    for (int i=idx_rev; i>=1; --i)
    {
      // Multiplying the scatter density at the node i with the conversion factor
      // leading in the reverse direction from node i.
      destination.push_back(destination.back()*
        reverse_path.bwd_conversion_factors[i] * reverse_path.pdfs_rev[i+1]);
    }
    assert(destination.size() == total_node_count + 1);
  }

  double ComputePowerHeuristicWeight(const Connection &connection) const
  {
    /* Simple way to understand the number of admissible techniques in Veach's sense:
     * Imagine there is a very glossy vertex. Sampling via bsdf is low variance because the bsdf and the pdf cancel each other in the nominator/denominator.
     * Now, sampling something else and connecting to the vertex has high variance because only very few time we get a contribution, and if so, the bsdf has very large value because it is strongly peaked.
     * Now make the bsdf sharper, narrower, take limit to Dirac-Delta function.
     * Variance goes to infinity. We cannot do this. And in fact, my 'Evaluate' & 'Pdf' functions return 0 for specular components, no matter what coordinates.
     * What is left are the sampling techniques where only non-specular vertices are connected.
     * So I simply take the segments that have no adjacent specular vertices.
     */
    double nominator = eye_densities[connection.eye_index+1]*
                       light_densities[connection.light_index+1];
    nominator = Sqr(nominator);
    assert (std::isfinite(nominator));
    double denom = 0.;
    for (int s = 0; s <= total_node_count; ++s)
    {
      bool flag = !(specular_flags[s] || specular_flags[s+1]);
      double summand = flag ? (eye_densities[s]*light_densities[total_node_count-s]) : 0.;
      denom += Sqr(summand);
      assert (std::isfinite(denom));
    }
    assert(denom > 0.);
    return nominator/denom;
  }

  void FactorInNumberWeight()
  {
    for (int i=2; i<=total_node_count; ++i)
    {
      eye_densities[i] *= one_over_number_of_splat_attempts_per_pixel;
    }
  }

  double WeightByNumberOfAdmissibleTechniques() const
  {
    int num_tech = 0;
    for (int i = 0; i <= total_node_count; ++i)
    {
      num_tech += !(specular_flags[i] || specular_flags[i+1]);
    }
    assert (num_tech >= 1); // Actually there can be purely specular paths that cannot be sampled.
    return 1./(num_tech + Epsilon);
  }
};

} // namespace BdptDetail

class BdptWorker;

class BdptAlgo : public SimplePixelByPixelRenderingDetails::SimplePixelByPixelRenderingAlgo
{
  friend class BdptWorker;
  bool enable_debug_buffers = false;
  BdptDetail::DebugBuffers debug_buffers;
  BdptDetail::DebugBuffers debug_buffers_mis;
  tbb::spin_mutex debug_buffer_mutex;
public:
  BdptAlgo(const Scene &scene_,const RenderingParameters &render_params_)
    : SimplePixelByPixelRenderingAlgo{render_params_,scene_}
  {}
protected:
  std::unique_ptr<SimplePixelByPixelRenderingDetails::Worker> AllocateWorker(int i) override;
  void PassCompleted() override;
};

class BdptWorker : public RadianceEstimatorBase, public Worker
{
  LambdaSelectionStrategy lambda_selection_factory;
  ToyVector<SensorResponse> rgb_responses;

  struct Splat : public ROI::PointEmitterArray::Response
  {
    Splat(int unit_index, const Spectral3 &weight, int _path_length) :
      ROI::PointEmitterArray::Response{unit_index, weight}, path_length{_path_length} {}
    int path_length = {};
  };

  BdptDetail::SubpathHistory eye_history;
  BdptDetail::SubpathHistory direct_light_history; // The alternate history ;-) .Where a random light is sampled for s=*,t=1 paths, instead of the original one. Contains only a single node.
  BdptDetail::SubpathHistory light_history;
  BdptDetail::BdptMis bdptmis;
  ToyVector<MediumTracker> eye_medium_tracker_before_node;
  Spectral3 total_eye_measurement_contributions;
  ToyVector<Splat> splats;
  Index3 lambda_idx;
  int pixel_index;
  PathContext eye_context;
  PathContext light_context;
  BdptAlgo* master;

public:
  BdptWorker(const Scene &_scene, const AlgorithmParameters &algo_params, BdptAlgo* master)
  : RadianceEstimatorBase{_scene, algo_params},
    lambda_selection_factory{},
    eye_history{*this},
    direct_light_history{*this},
    light_history{*this},
    bdptmis{_scene},
    eye_medium_tracker_before_node{},
    master{master}
  {
    rgb_responses.reserve(1024);
    eye_medium_tracker_before_node.reserve(32);
  }

  RGB RenderPixel(int _pixel_index) override
  {
    eye_history.Reset();
    light_history.Reset();
    total_eye_measurement_contributions = Spectral3{0.};
    splats.clear();
    eye_medium_tracker_before_node.clear();

    pixel_index = _pixel_index;
    auto lambda_selection = lambda_selection_factory.WithWeights(sampler);
    this->lambda_idx = lambda_selection.indices;
    const auto &lambda_weights = lambda_selection.weights;

    eye_context = PathContext{lambda_selection, TransportType::RADIANCE};
    light_context = PathContext{lambda_selection, TransportType::IMPORTANCE};

    {
      Pdf eye_node_pdf;
      RW::PathNode eye_node = SampleSensorEnd(pixel_index, eye_context, eye_node_pdf);
      MediumTracker eye_medium_tracker{scene};
      InitializeMediumTracker(eye_node, eye_medium_tracker);
      eye_history.Start(eye_node, lambda_weights/eye_node_pdf, eye_node_pdf);
      BdptForwardTrace(
        eye_history, eye_medium_tracker,
        [&eye_medium_tracker, this](int ) { this->eye_medium_tracker_before_node.emplace_back(eye_medium_tracker); },
        eye_context);
      eye_history.Finish();
    }

    {
      Pdf light_node_pdf;
      RW::PathNode light_node = SampleEmissiveEnd(light_context, light_node_pdf);
      MediumTracker light_medium_tracker{scene};
      InitializeMediumTracker(light_node, light_medium_tracker);
      light_history.Start(light_node, Spectral3{1./light_node_pdf}, light_node_pdf);
      BdptForwardTrace(
        light_history, light_medium_tracker,
        [](int ) {},
        light_context);
      light_history.Finish();
    }

    assert (light_history.NumNodes() <= max_path_node_count);
    assert (eye_history.NumNodes() <= max_path_node_count);

    for (int eye_idx=0; eye_idx<eye_history.NumNodes(); ++eye_idx)
    {
      if (eye_idx==0 || eye_history.Node(eye_idx).IsScatterNode())
      {
        // Only camera and scatter nodes are allowed to connect to the light path. Sensor nodes only if they are the first node.
        // In particular we must not connect light sources on the eye path to the light path.
        // Surface or volume nodes default to being scatter nodes, so I don't have to consider these explicitly in the above conditional.
        if (eye_idx+1 < max_path_node_count)
          DirectLighting(eye_idx, eye_medium_tracker_before_node[eye_idx]);
        for (int light_idx=1; light_idx<std::min(light_history.NumNodes(), max_path_node_count-eye_idx-1); ++light_idx)
        {
          ConnectWithLightPath(eye_idx, light_idx,  eye_medium_tracker_before_node[eye_idx]);
        }
      }
      if (eye_idx>0) // Would cause error on starting node
        HandleLightHit(eye_idx);
    }

    for (const auto &splat : splats)
    {
      assert(splat.path_length>=2);
      rgb_responses.push_back(SensorResponse{
        splat.unit_index,
        Color::SpectralSelectionToRGB(splat.weight, lambda_idx)
      });
    }

    return Color::SpectralSelectionToRGB(total_eye_measurement_contributions, lambda_idx);
  }

  void DirectLighting(int eye_idx, const MediumTracker &eye_medium_tracker)
  {
    Pdf pdf_sample_light;
    RW::PathNode light_node = SampleEmissiveEnd(light_context, pdf_sample_light);
    direct_light_history.Reset();
    direct_light_history.Start(light_node, Spectral3{1./pdf_sample_light}, pdf_sample_light);
    direct_light_history.Finish();

    ConnectEyeWithOtherPath(eye_idx, 0, direct_light_history, eye_medium_tracker);
  }

  void ConnectWithLightPath(int eye_idx, int light_idx, const MediumTracker &eye_medium_tracker)
  {
    ConnectEyeWithOtherPath(eye_idx, light_idx, light_history, eye_medium_tracker);
  }

  void ConnectEyeWithOtherPath(int eye_idx, int other_idx, BdptDetail::SubpathHistory &other, const MediumTracker &eye_medium_tracker)
  {
    VolumePdfCoefficients volume_pdf_coeff{};
    double eye_pdf, light_pdf;
    ConnectionSegment to_light = CalculateConnection(
      eye_history.Node(eye_idx), eye_medium_tracker, other.Node(other_idx),
      eye_context, light_context, &eye_pdf, &light_pdf, &volume_pdf_coeff);

    if (to_light.weight.isZero())
      return;

    double eye_rev_pdf = ReverseScatterPdf(eye_history.Node(eye_idx), eye_pdf, to_light.segment.ray.dir, this->eye_context);
    double light_rev_pdf = ReverseScatterPdf(light_history.Node(other_idx), light_pdf, -to_light.segment.ray.dir, this->light_context);

    double mis_weight = MisWeight(eye_history, other,
      BdptDetail::Connection{
        Pdf{eye_pdf},
        Pdf{eye_rev_pdf},
        Pdf{light_pdf},
        Pdf{light_rev_pdf},
        volume_pdf_coeff,
        eye_idx,
        other_idx,
        to_light.segment
      }
    );

    Spectral3 path_weight = eye_history.Beta(eye_idx)*other.Beta(other_idx)*to_light.weight;
    AddPathContribution(eye_idx, other_idx, mis_weight, path_weight);
  }

  // Eye node lies on a light source. Compute path contribution.
  void HandleLightHit(int eye_idx)
  {
    assert(eye_history.NumNodes()>=2);
    auto &end_node = eye_history.Node(eye_idx);

    if (!end_node.TryEnableOperationsOnEmitter(scene))
      return;

    double reverse_scatter_pdf = 0.;
    Spectral3 end_weight = Evaluate(
      end_node, -end_node.incident_dir, light_context, &reverse_scatter_pdf);

    VolumePdfCoefficients volume_pdf_coeff{}; // Already covered by the random walk. The SubpathHistory has the coefficients for the last segment.

    double mis_weight = MisWeight(eye_history, light_history,
      BdptDetail::Connection{
        Pdf{1.},
        Pdf{reverse_scatter_pdf},
        GetPdfOfGeneratingSampleOnEmitter(end_node, light_context),
        Pdf{1.},
        volume_pdf_coeff,
        eye_idx,
        -1,
        RaySegment{}
      }
    );

    Spectral3 path_weight = end_weight*eye_history.Beta(eye_idx);
    AddPathContribution(eye_idx, -1, mis_weight, path_weight);

    end_node.EnableOperationOnScattererIfFeasible();
  }

  double MisWeight(BdptDetail::SubpathHistory &eye, BdptDetail::SubpathHistory &light, BdptDetail::Connection &&connection)
  {
    using B = BdptDetail::BackupAndReplace;
    B eye_backup{eye, connection.eye_index, light, connection.light_index, connection.eye_pdf, connection.eye_pdf_rev, connection.segment, connection.volume_pdf_coeff};
    std::swap(connection.volume_pdf_coeff.pdf_scatter_bwd, connection.volume_pdf_coeff.pdf_scatter_fwd);
    B light_backup{light, connection.light_index, eye, connection.eye_index, connection.light_pdf, connection.light_pdf_rev, connection.segment, connection.volume_pdf_coeff};
    return bdptmis.Compute(eye, light, connection);
  }

  void AddPathContribution(int s, int t, double mis_weight, const Spectral3 &path_weight)
  {
    assert(path_weight.allFinite() && std::isfinite(mis_weight));
    int path_length = s+t+2; // +2 Because s and t are 0-based indices.
    assert(path_length >= 2);
    if (s == 0) // Initial eye vertex.
    {
      if (sensor_connection_unit >= 0)
      {
        int num_attempted_paths_per_unit = scene.GetCamera().xres*scene.GetCamera().yres;
        Spectral3 new_weight = path_weight/num_attempted_paths_per_unit;
        splats.emplace_back(
          sensor_connection_unit,
          mis_weight*new_weight,
          path_length
        );
        AddToDebugBuffer(sensor_connection_unit, s, t, mis_weight, new_weight);
        sensor_connection_unit = -1;
      }
    }
    else
    {
      total_eye_measurement_contributions += mis_weight*path_weight;
      AddToDebugBuffer(pixel_index, s, t, mis_weight, path_weight);
    }
  }

  template<class StepCallback>
  void BdptForwardTrace(BdptDetail::SubpathHistory &path, MediumTracker &medium_tracker, StepCallback step_callback, const PathContext &context)
  {
    step_callback(0);

    if (this->max_path_node_count <= 1)
      return;

    int path_node_count = 1;
    while (true)
    {
      VolumePdfCoefficients volume_pdf_coeff;
      auto &current_node = path.NodeFromBack(0);

      StepResult step = TakeRandomWalkStep(
        current_node, medium_tracker, context, &volume_pdf_coeff);

      if (step.node.node_type == RW::NodeType::ZERO_CONTRIBUTION_ABORT_WALK) // Hit a light or particle escaped the scene or aborting.
        break;

      // If this is a light path we must not generate paths with light sources in between the ends.
      if (context.transport==IMPORTANCE && !step.node.IsScatterNode())
        break;

      Pdf reverse_pdf = ReverseScatterPdf(current_node, step.scatter_pdf, step.segment.ray.dir, context);

      // Determine survival of russian roulette early because it modifies beta_factor!
      bool survive = SurvivalAtNthScatterNode(step.beta_factor, path_node_count+1);

      path.AddSegment(step.node, step.beta_factor, step.scatter_pdf, reverse_pdf, volume_pdf_coeff, step.segment);

      step_callback(path_node_count);

      // The new node is used definitely. However now the walk can terminate.
      // It would make more sense maybe to do RR right after evaluating the
      // scatter function. On the other hand, it should not matter how and where
      // I compute the termination (or survival) probability.
      if (!survive)
        break;

      if (step.node.node_type == RW::NodeType::ZERO_CONTRIBUTION_ABORT_WALK ||
          step.node.node_type == RW::NodeType::ENV)
        break;

      ++path_node_count;
    }
  }

  Pdf ReverseScatterPdf(const PathNode &node, Pdf forward_pdf, const Double3 &reverse_incident_dir, const PathContext &context) const
  {
    // Only surface (BSDF) sampling pdf's are allowed to be unsymmetric. There is no need for phase functions to be asymmetric.
    // And emission directions of light sources and sensors don't have a reverse path at all.
    if (node.node_type != NodeType::SURFACE_SCATTER)
      return forward_pdf;
    // Components proportional to Dirac-Delta should be symmetrical because it importance samples the symmetrical bsdf!
    // And then there is really no other place to put the peak other than the symmetrical directions.
    // In that case, the "pdf" value here represents the probability to select the Dirac-Delta component of the material.
    if (forward_pdf.IsFromDelta())
      return forward_pdf;

    const SurfaceInteraction &intersection = node.interaction.surface;
    return GetShaderOf(intersection, scene).Pdf(reverse_incident_dir, intersection, -node.incident_dir, context);
  }

  ToyVector<SensorResponse>& GetSensorResponses() override
  {
    return rgb_responses;
  }

  void AddToDebugBuffer(int unit_index, int s, int t, double mis_weight, const Spectral3 &path_weight)
  {
    if (!master->enable_debug_buffers)
      return;

    tbb::spin_mutex::scoped_lock lock(master->debug_buffer_mutex);
    auto key = std::make_pair(s, t);
    auto it = master->debug_buffers.find(key);
    if (it == master->debug_buffers.end())
    {
      bool _;
      std::tie(it, _) = master->debug_buffers.insert(std::make_pair(
        key, Spectral3ImageBuffer(scene.GetCamera().xres, scene.GetCamera().yres)));

    }
    it->second.Insert(unit_index, Color::SpectralSelectionToRGB(path_weight, lambda_idx));

    if (!path_weight.isZero())
    {
      it = master->debug_buffers_mis.find(key);
      if (it == master->debug_buffers_mis.end())
      {
        bool _;
        std::tie(it, _) = master->debug_buffers_mis.insert(std::make_pair(
          key, Spectral3ImageBuffer(scene.GetCamera().xres, scene.GetCamera().yres)));

      }
      it->second.Insert(unit_index, RGB{Color::RGBScalar{mis_weight}});
    }
  }
};

class PathTracingWorker : public Worker, public RandomWalk::RadianceEstimatorBase
{
  LambdaSelectionStrategy lambda_selection_factory;
  bool do_sample_brdf;
  bool do_sample_lights;
  ToyVector<SensorResponse> rgb_responses;
public:
  PathTracingWorker(const Scene &_scene, const AlgorithmParameters &algo_params)
  : RadianceEstimatorBase{_scene, algo_params},
    lambda_selection_factory{},
    do_sample_brdf{true},
    do_sample_lights{true}
  {
    // Which style of sampling for the last vertex of the path.
    // Defaults to both light and brdf sampling.
    if (algo_params.pt_sample_mode == "bsdf")
    {
      do_sample_lights = false;
    }
    else if (algo_params.pt_sample_mode == "lights")
    {
      do_sample_brdf = false;
    }
    rgb_responses.reserve(1024);
  }

  RGB RenderPixel(int pixel_index) override
  {
    sensor_connection_unit = -1;

    auto lambda_selection = lambda_selection_factory.WithWeights(sampler);
    PathContext context{lambda_selection, TransportType::RADIANCE};
    context.pixel_index = pixel_index;
    PathContext light_context{context}; light_context.transport = IMPORTANCE;
    MediumTracker medium_tracker{scene};
    VolumePdfCoefficients volume_pdf_coeff;
    Spectral3 path_sample_values{0.};

    RW::PathNode prev_node;

    StepResult step;
    step.node = SampleSensorEnd(pixel_index, context, step.scatter_pdf); // Note: abuse of scatter_pdf. This is not about scattering but picking the initial location.

    Spectral3 beta{lambda_selection.weights/step.scatter_pdf};

    InitializeMediumTracker(step.node, medium_tracker);

    int path_node_count = 1;
    while (true)
    {
      if (this->do_sample_lights)
      {
        // Next vertex. Sample a point on a light source. Compute geometry term. Add path weight to total weights.
        Pdf pdf_light;
        RW::PathNode light_node = SampleEmissiveEnd(context, pdf_light);
        double pdf_scatter = NaN;
        volume_pdf_coeff = VolumePdfCoefficients{};
        ConnectionSegment to_light = CalculateConnection(step.node, medium_tracker, light_node, context, light_context, &pdf_scatter, nullptr, &volume_pdf_coeff);
        double pdf_of_light_due_to_scatter = pdf_scatter*PdfConversionFactor(step.node, light_node, to_light.segment, FwdCoeffs(volume_pdf_coeff), false);
        double mis_weight = MisWeight(
          pdf_light, // Maybe this needs multiplication with pmf to select the unit_index.
          pdf_of_light_due_to_scatter);
        Spectral3 path_weight = (mis_weight/pdf_light)*to_light.weight*beta;
        if (step.node.node_type == NodeType::CAMERA)
        {
          if (sensor_connection_unit >= 0)
          {
            int num_attempted_paths = scene.GetCamera().xres*scene.GetCamera().yres;
            path_weight /= num_attempted_paths;
            rgb_responses.push_back({
              sensor_connection_unit,
              Color::SpectralSelectionToRGB(path_weight, lambda_selection.indices)
            }); // This part works via the auto-generated initializer list ctor.
          }
        }
        else
        {
          path_sample_values += path_weight;
        }
      }

      prev_node = step.node;
      volume_pdf_coeff = VolumePdfCoefficients{};
      step = TakeRandomWalkStep(prev_node, medium_tracker, context, &volume_pdf_coeff);

      beta *= step.beta_factor;

      ++path_node_count;

      if (this->do_sample_brdf && step.node.TryEnableOperationsOnEmitter(scene))
      {
        Spectral3 end_weight = Evaluate(step.node, -step.segment.ray.dir, context, nullptr);
        double pdf_direct_sample = GetPdfOfGeneratingSampleOnEmitter(step.node, context);
        Pdf pdf_due_to_scatter = PdfConversionFactor(prev_node, step.node, step.segment, FwdCoeffs(volume_pdf_coeff), step.scatter_pdf.IsFromDelta())*step.scatter_pdf;
        double mis_weight = MisWeight(pdf_due_to_scatter, pdf_direct_sample);
        path_sample_values += mis_weight*end_weight*beta;
        step.node.EnableOperationOnScattererIfFeasible();
      }

      if (step.node.node_type == RW::NodeType::ZERO_CONTRIBUTION_ABORT_WALK ||
          step.node.node_type == RW::NodeType::ENV)
        break;

      bool survive = SurvivalAtNthScatterNode(beta, path_node_count);
      if (!survive)
        break;

      assert(beta.allFinite());
    }

    return Color::SpectralSelectionToRGB(path_sample_values, lambda_selection.indices);
  }

  ToyVector<SensorResponse>& GetSensorResponses() override
  {
    return rgb_responses;
  }

  double MisWeight(Pdf pdf_or_pmf_taken, double pdf_other) const
  {
    double mis_weight = 1.;
    if (!pdf_or_pmf_taken.IsFromDelta() && this->do_sample_brdf && this->do_sample_lights)
    {
      mis_weight = PowerHeuristic(pdf_or_pmf_taken, {pdf_other});
    }
    return mis_weight;
  }
};

} // namespace

using RadianceEstimatorBase = RandomWalk::RadianceEstimatorBase;

#pragma GCC diagnostic pop // Restore command line options


#include <boost/test/unit_test.hpp>
#include "test_utils.hpp"

#include <owlapi/Vocabulary.hpp>
#include <moreorg/vocabularies/OM.hpp>
#include <moreorg/reasoning/ResourceMatch.hpp>
#include <moreorg/algebra/Connectivity.hpp>
#include <moreorg/OrganizationModel.hpp>
#include <moreorg/OrganizationModelAsk.hpp>
#include <graph_analysis/GraphAnalysis.hpp>
#include <graph_analysis/GraphIO.hpp>

using namespace owlapi;
using namespace owlapi::model;
using namespace moreorg;
using namespace moreorg::reasoning;
using namespace graph_analysis;

BOOST_AUTO_TEST_SUITE(lego)

BOOST_AUTO_TEST_CASE(list)
{
    OrganizationModel::Ptr om(new OrganizationModel(getRootDir() + "/test/data/om-lego-v0.1.owl"));
    OrganizationModelAsk ask(om);

    owlapi::vocabulary::Custom lego("http://www.rock-robotics.org/2017/10/om-lego#");

    // report
    // Statistics:
    // # graph completeness evaluations: 145
    // time in s: 119.324
    // stopped: 0
    // # propagator executions: 3032041
    // # failed nodes: 0
    // # expanded nodes: 7540
    // depth of search stack: 51
    // # restarts: 144
    // # nogoods: 144
    ModelPool modelPool;
    modelPool[ lego.resolve("YellowBlock") ] = 10;
    modelPool[ lego.resolve("GreenBlock") ] = 10;
    modelPool[ lego.resolve("RedBlock") ] = 10;
    modelPool[ lego.resolve("BlueBlock") ] = 10;
    modelPool[ lego.resolve("AdapterBlock") ] = 10;
    modelPool[ lego.resolve("Flag") ] = 1;

    BOOST_TEST_MESSAGE("Please remain patient -- this test can take up to 2-3 min");
    size_t epochs = 1;
    size_t minFeasible = 1;
    for(size_t i = 0; i < epochs; ++i)
    {
        BaseGraph::Ptr baseGraph;
        bool feasible = algebra::Connectivity::isFeasible(modelPool, ask, baseGraph, 0, minFeasible, moreorg::vocabulary::OM::resolve("MechanicalInterface") );
        BOOST_REQUIRE_MESSAGE(feasible, "Connectivity check should validate feasibility");
        if(baseGraph)
        {
            std::stringstream ss;
            ss << "/tmp/organization-model-lego-";
            ss << i;
            ss << ".dot";

            graph_analysis::io::GraphIO::write(ss.str(), baseGraph);
        }
        BOOST_TEST_MESSAGE(algebra::Connectivity::getStatistics().toString());
        usleep(200);
    }
}

BOOST_AUTO_TEST_CASE(icaps)
{
    OrganizationModel::Ptr om(new OrganizationModel(getRootDir() + "/test/data/om-icaps-v0.1.owl"));
    OrganizationModelAsk ask(om);

    owlapi::vocabulary::Custom lego("http://www.rock-robotics.org/2017/10/om-icaps#");

    ModelPool modelPool;
    modelPool[ lego.resolve("YellowAgent") ] = 4;
    modelPool[ lego.resolve("GreenAgent") ] = 3;
    modelPool[ lego.resolve("RedAgent") ] = 2;
    modelPool[ lego.resolve("BlueAgent") ] = 1;
    modelPool[ lego.resolve("Adapter") ] = 1;
    modelPool[ lego.resolve("Manipulator") ] = 1;

    size_t epochs = 1;
    size_t minFeasible = 1;
    for(size_t i = 0; i < epochs; ++i)
    {
        BaseGraph::Ptr baseGraph;
        bool feasible = algebra::Connectivity::isFeasible(modelPool, ask, baseGraph, 0, minFeasible, moreorg::vocabulary::OM::resolve("MechanicalInterface") );
        BOOST_REQUIRE_MESSAGE(feasible, "Connectivity check should validate feasibility");
        if(baseGraph)
        {
            std::stringstream ss;
            ss << "/tmp/organization-model-icaps-";
            ss << i;
            ss << ".dot";

            graph_analysis::io::GraphIO::write(ss.str(), baseGraph);
        }
        BOOST_TEST_MESSAGE(algebra::Connectivity::getStatistics().toString());
        usleep(200);
    }
}

BOOST_AUTO_TEST_SUITE_END()


//----------------------------------------------------------------------------
// Copyright (C) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the Server Side Public License, version 1,
// as published by the author.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// Server Side Public License for more details.
//
// You should have received a copy of the Server Side Public License
// along with this program. If not, see
// <https://github.com/NilFoundation/plugin/blob/master/LICENSE_1_0.txt>.
//----------------------------------------------------------------------------

#define BOOST_TEST_MODULE post_rational_vanilla_test

#include <boost/test/data/monomorphic.hpp>
#include <boost/test/data/test_case.hpp>
#include <boost/test/unit_test.hpp>

#include <nil/filecoin/storage/proofs/core/sector.hpp>

#include <nil/filecoin/storage/proofs/post/rational/vanilla.hpp>

#include "../../core/merkle/generate_tree.hpp"

using namespace nil::filecoin;

BOOST_AUTO_TEST_SUITE(post_rational_vanilla_test_suite)

template<typename MerkleTreeType>
void test_rational_post() {
    const auto rng = XorShiftRng::from_seed(crate::TEST_SEED);

    const std::uint64_t leaves = 64 * get_base_tree_count::<Tree>();
    const std::uint64_t sector_size = leaves * 32;
    const std::size_t challenges_count = 8;

    const auto pub_params = PublicParams {
        sector_size,
        challenges_count,
    };

    // Construct and store an MT using a named store.
    const auto temp_dir = tempfile::tempdir();
    const auto temp_path = temp_dir.path();

    auto data1, data2, tree1, tree2;
    const std::tie(data1, tree1) = merkletree::generate_tree<Tree>(rng, leaves, Some(temp_path.to_path_buf()));
    const std::tie(data2, tree2) = merkletree::generate_tree<Tree>(rng, leaves, Some(temp_path.to_path_buf()));

    const auto seed = (0..leaves).map(| _ | rng.gen()).collect::<Vec<u8>>();
    auto faults = OrderedSectorSet();
    faults.insert(139.into());
    faults.insert(1.into());
    faults.insert(32.into());

    auto sectors = OrderedSectorSet();
    sectors.insert(891.into());
    sectors.insert(139.into());
    sectors.insert(32.into());
    sectors.insert(1.into());

    auto trees = BTreeMap();
    trees.insert(139.into(), &tree1);    // faulty with tree
    trees.insert(891.into(), &tree2);
    // other two faults don't have a tree available

    const auto challenges = derive_challenges(challenges_count, sector_size, &sectors, &seed, &faults);

    // the only valid sector to challenge is 891
    BOOST_ASSERT_MSG(challenges.iter().all(| c | c.sector == 891.into()), "invalid challenge generated");

    const auto comm_r_lasts = challenges.iter().map(| c | trees.get(&c.sector).root()).collect::<Vec<_>>();

    std::vector<typename MerkleTreeType::hash_type::digest_type > comm_cs
        = challenges.iter().map(| _c | typename MerkleTreeType::hash_type::digest_type::random(rng)).collect();

    std::vector<typename MerkleTreeType::hash_type::digest_type > comm_rs
        = comm_cs.iter()
              .zip(comm_r_lasts.iter())
              .map(| (comm_c, comm_r_last) | {<typename MerkleTreeType::hash_type>::Function::hash2(comm_c, comm_r_last)})
              .collect();

    const auto pub_inputs = PublicInputs {
        challenges : &challenges,
        comm_rs : &comm_rs,
        faults : &faults,
    };

    const auto priv_inputs = PrivateInputs::<Tree> {
        trees : &trees,
        comm_cs : &comm_cs,
        comm_r_lasts : &comm_r_lasts,
    };

    const auto proof = RationalPoSt::<Tree>::prove(&pub_params, &pub_inputs, &priv_inputs).expect("proving failed");

    const auto is_valid = RationalPoSt::<Tree>::verify(&pub_params, &pub_inputs, &proof).expect("verification failed");

    BOOST_ASSERT (is_valid);
}

BOOST_AUTO_TEST_CASE(rational_post_pedersen) {
    test_rational_post::<LCTree<PedersenHasher, U8, U0, U0>>();
}

BOOST_AUTO_TEST_CASE(rational_post_sha256) {
    test_rational_post::<LCTree<Sha256Hasher, U8, U0, U0>>();
}

BOOST_AUTO_TEST_CASE(rational_post_blake2s) {
    test_rational_post<LCTree<Blake2sHasher, U8, U0, U0>>();
}

BOOST_AUTO_TEST_CASE(rational_post_poseidon) {
    test_rational_post<LCTree<PoseidonHasher, U8, U0, U0>>();
}

BOOST_AUTO_TEST_CASE(rational_post_poseidon_8_8) {
    test_rational_post<LCTree<PoseidonHasher, U8, U8, U0>>();
}

BOOST_AUTO_TEST_CASE(rational_post_poseidon_8_8_2) {
    test_rational_post<LCTree<PoseidonHasher, U8, U8, U2>>();
}

template<typename MerkleTreeType>
void test_rational_post_validates_challenge_identity() {
    const auto rng = XorShiftRng::from_seed(crate::TEST_SEED);

    const std::uint64_t leaves = 64 * get_base_tree_count::<Tree>();
    const std::uint64_t sector_size = leaves * 32;
    const std::size_t challenges_count = 2;

    const auto pub_params = PublicParams {
        sector_size,
        challenges_count,
    };

    // Construct and store an MT using a named store.
    const auto temp_dir = tempfile::tempdir();
    const auto temp_path = temp_dir.path();

    auto data, tree;
    const std::tie(data, tree) = merkletree::generate_tree<Tree>(rng, leaves, Some(temp_path.to_path_buf()));
    const auto seed = (0..leaves).map(| _ | rng.gen()).collect::<Vec<u8>>();
    auto faults = OrderedSectorSet();
    faults.insert(1.into());
    auto sectors = OrderedSectorSet();
    sectors.insert(0.into());
    sectors.insert(1.into());

    auto trees = BTreeMap();
    trees.insert(0.into(), &tree);

    const auto challenges = derive_challenges(challenges_count, sector_size, &sectors, &seed, &faults);
    const auto comm_r_lasts = challenges.iter().map(| c | trees.get(&c.sector).root()).collect::<Vec<_>>();

    const std::vector<typename MerkleTreeType::hash_type::digest_type > comm_cs
        = challenges.iter().map(| _c | typename MerkleTreeType::hash_type::digest_type::random(rng)).collect();

    const std::vector<typename MerkleTreeType::hash_type::digest_type > comm_rs
        = comm_cs.iter()
              .zip(comm_r_lasts.iter())
              .map(| (comm_c, comm_r_last) | {<typename MerkleTreeType::hash_type>::Function::hash2(comm_c, comm_r_last)})
              .collect();

    const auto pub_inputs = PublicInputs {
        challenges : &challenges,
        faults : &faults,
        comm_rs : &comm_rs,
    };

    const auto priv_inputs = PrivateInputs::<Tree> {
        trees : &trees,
        comm_cs : &comm_cs,
        comm_r_lasts : &comm_r_lasts,
    };

    const auto proof = RationalPoSt::<Tree>::prove(&pub_params, &pub_inputs, &priv_inputs).expect("proving failed");

    const auto seed = (0..leaves).map(| _ | rng.gen()).collect::<Vec<u8>>();
    const auto challenges = derive_challenges(challenges_count, sector_size, &sectors, &seed, &faults);
    const auto comm_r_lasts = challenges.iter().map(| _c | tree.root()).collect::<Vec<_>>();

    const std::vector <typename MerkleTreeType::hash_type::digest_type > comm_cs
        = challenges.iter().map(| _c | typename MerkleTreeType::hash_type::digest_type::random(rng)).collect();

    const std::vector <typename MerkleTreeType::hash_type::digest_type > comm_rs
        = comm_cs.iter()
              .zip(comm_r_lasts.iter())
              .map(| (comm_c, comm_r_last) | {<typename MerkleTreeType::hash_type>::Function::hash2(comm_c, comm_r_last)})
              .collect();

    const auto different_pub_inputs = PublicInputs {
        challenges : &challenges,
        faults : &faults,
        comm_rs : &comm_rs,
    };

    const auto verified = RationalPoSt::<Tree>::verify(&pub_params, &different_pub_inputs, &proof);

    // A proof created with a the wrong challenge not be verified!
    BOOST_CHECK(!verified);
}

BOOST_AUTO_TEST_CASE(rational_post_actually_validates_challenge_identity_sha256) {
    test_rational_post_validates_challenge_identity<LCTree<Sha256Hasher, U8, U0, U0>>();
}

BOOST_AUTO_TEST_CASE(rational_post_actually_validates_challenge_identity_blake2s) {
    test_rational_post_validates_challenge_identity<LCTree<Blake2sHasher, U8, U0, U0>>();
}

BOOST_AUTO_TEST_CASE(rational_post_actually_validates_challenge_identity_pedersen) {
    test_rational_post_validates_challenge_identity<LCTree<PedersenHasher, U8, U0, U0>>();
}

BOOST_AUTO_TEST_CASE(rational_post_actually_validates_challenge_identity_poseidon) {
    test_rational_post_validates_challenge_identity<LCTree<PoseidonHasher, U8, U0, U0>>();
}

BOOST_AUTO_TEST_CASE(rational_post_actually_validates_challenge_identity_poseidon_8_8) {
    test_rational_post_validates_challenge_identity<LCTree<PoseidonHasher, U8, U8, U0>>();
}

BOOST_AUTO_TEST_CASE(rational_post_actually_validates_challenge_identity_poseidon_8_8_2) {
    test_rational_post_validates_challenge_identity<LCTree<PoseidonHasher, U8, U8, U2>>();
}

BOOST_AUTO_TEST_CASE(test_derive_challenges_fails_on_all_faulty) {
    use std::collections::BTreeSet;

    auto sectors = BTreeSet();
    sectors.insert(SectorId::from(1));
    sectors.insert(SectorId::from(2));

    auto faults = BTreeSet();
    faults.insert(SectorId::from(1));
    faults.insert(SectorId::from(2));

    const std::vector<auto> seed = {0u8};

    BOOST_ASSERT (derive_challenges(10, 1024, &sectors, &seed, &faults).is_err());
}

BOOST_AUTO_TEST_SUITE_END()


// Copyright © 2017-2021 Trust Wallet
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#include "Address.h"
#include "../Base32.h"
#include "../HexCoding.h"
#include "../Crc.h"

#include <boost/algorithm/string.hpp>
#include <array>

using namespace TW::Stacks;
using namespace boost::algorithm;

const char* Address::BASE32_ALPHABET_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";

TW::Data Address::deconstruct(const std::string& string) {
    if ((string.length() < (checksumSize + 2)) || (string.length() > size)) {
        return {};
    }

    // Check that it decodes correctly
    Data data;
    auto normalise = to_upper_copy(string);
    auto pad = size - normalise.length();
    if (pad) {
        normalise.insert(2, std::string(pad, '0'));
    }
    replace_all(normalise, "O", "0");
    replace_all(normalise, "L", "1");
    replace_all(normalise, "I", "1");
    if ((normalise[0] != 'S') || !Base32::decode(normalise.substr(1), data, BASE32_ALPHABET_CROCKFORD)) {
        return {};
    }

    // Verify that checksums match
    data[0] >>= 3;
    auto checksum = Hash::sha256(Hash::sha256(&data[0], bytesSize));
    if (!std::equal(data.end() - checksumSize, data.end(), checksum.begin())) {
	return {};
    }
    return Data(data.begin(), data.begin() + bytesSize);
}

bool Address::isValid(const std::string& string, const std::vector<TW::byte>& validPrefixes) {
    auto data = deconstruct(string);
    return data.size() && (!validPrefixes.size() || std::find(validPrefixes.begin(), validPrefixes.end(), data[0]) != validPrefixes.end());
}

Address::Address(const std::string& string) {
    // Ensure address is valid
    auto data = deconstruct(string);
    if (!data.size()) {
        throw std::invalid_argument("Invalid address data");
    }
    std::copy(data.begin(), data.end(), bytes.begin());
}

Address::Address(const PublicKey& publicKey, TW::byte prefix) {
    if ((publicKey.type != TWPublicKeyTypeSECP256k1) && (publicKey.type != TWPublicKeyTypeSECP256k1Extended)) {
        throw std::invalid_argument("Invalid public key type");
    }
    auto data = publicKey.hash({}, Hash::sha256ripemd);
    std::copy(data.begin(), data.end(), bytes.begin() + 1);
    bytes[0] = prefix;
}

std::string Address::string() const {
    static_assert((8 * (bytesSize + checksumSize)) % 5 == 0);
    auto data = Data(bytes.begin(), bytes.end());
    auto checksum = Hash::sha256(Hash::sha256(data));
    data.insert(data.end(), checksum.begin(), checksum.begin() + checksumSize);
    TW::byte prefix = data[0] << 3;
    data[0] = 0;
    auto encoded = Base32::encode(data, BASE32_ALPHABET_CROCKFORD);
    encoded.erase(0, encoded.find_first_not_of('0'));
    for (int i = 1; i < data.size() && !data[i]; i++) {
       encoded.insert(0, "0");
    }
    return std::string("S") + Base32::encode({prefix}, BASE32_ALPHABET_CROCKFORD)[0] + encoded;
}


// experiment with posterior regularization on a simple, artificial clustering problem.
#include "Generic/common/leak_detection.h"
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <fstream>
#include <iostream>
#include <cstring>
#include <iomanip>
#include <errno.h>
#include <time.h>
#include <math.h>
#include <boost/foreach.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include "Generic/common/foreach_pair.hpp"

#include "PosteriorRegularization.h"
#include "constraints/ClassRequiresFeatureConstraint.h"
#include "constraints/AtMostNOfClassConstraint.h"
#include "constraints/AtLeastNOfClassIfFeatureConstraint.h"
#include "../GraphicalModels/VectorUtils.h"
#include "../GraphicalModels/DataSet.h"
#include "../GraphicalModels/Graph.h"
#include "../GraphicalModels/Factor.h"
#include "../GraphicalModels/Variable.h"
#include "../GraphicalModels/Message.h"
#include "../GraphicalModels/learning/EM.h"
#include "../GraphicalModels/learning/PREM.h"
#include "../GraphicalModels/io/Reporter.h"
#include "../GraphicalModels/pr/FeatureImpliesClassConstraint.h"

wchar_t* CLASS_NAMES[] = { L"AIR", L"FN", L"MOD", L"LOC", L"CO", L"GAR", L"GAR_LOC" };
const double BMMFeatureFactor::SMOOTHING = 0.1;
const double ArgTypePriorFactor::SMOOTHING = 0.5;

using namespace std;
using boost::make_shared;
using boost::static_pointer_cast;
using boost::shared_ptr;
using std::vector;
using GraphicalModel::DataSet;
using GraphicalModel::Constraint;
using GraphicalModel::Message_ptr;
using GraphicalModel::SymmetricDirichlet;
using GraphicalModel::FeatureImpliesClassConstraint;

typedef boost::shared_ptr<GraphicalModel::DataSet<Document> > DataSet_ptr;

void Document::finalize(const std::vector<FV_ptr>& featureVectors) {
	assert(slots.size() == featureVectors.size());

	for (size_t i=0; i<slots.size(); ++i) {
		const SlotVariable_ptr& slot = slots[i];

		BMMFeatureFactor_ptr ff = make_shared<BMMFeatureFactor>(slot);
		BOOST_FOREACH(const std::wstring& feature, *featureVectors[i]) {
			ff->addFeature(feature);
		}
		connect(*ff, *slot);
		featureFactors.push_back(ff);

		ArgTypePriorFactor_ptr apf = make_shared<ArgTypePriorFactor>();
		connect(*apf, *slot);
		priorFactors.push_back(apf);
	}
}

void BMMFeatureFactor::finishedLoading(unsigned int n_classes) {
	BMMFeatureFactorParent::finishedLoading(n_classes, false,
			make_shared<SymmetricDirichlet>(SMOOTHING));
}

void ArgTypePriorFactor::finishedLoading(unsigned int n_classes) {
	ATPFParent::finishedLoading(n_classes,
			make_shared<SymmetricDirichlet>(ArgTypePriorFactor::SMOOTHING));
}

void Document::finishedLoading() {
	BMMFeatureFactor::finishedLoading(NUM_CLASSES);
	ArgTypePriorFactor::finishedLoading(NUM_CLASSES);
}

void Document::clearFactorModificationsImpl() {
	BOOST_FOREACH(const BMMFeatureFactor_ptr& featureFactor, featureFactors) {
		featureFactor->clearModifiers();
	}
	BOOST_FOREACH(const ArgTypePriorFactor_ptr& priorFactor, priorFactors) {
		priorFactor->clearModifiers();
	}
}

void Document::numberFactorsImpl(unsigned int& next_id) {
	BOOST_FOREACH(const BMMFeatureFactor_ptr& featureFactor, featureFactors) {
		featureFactor->setID(next_id++);
	}
	BOOST_FOREACH(const ArgTypePriorFactor_ptr& priorFactor, priorFactors) {
		priorFactor->setID(next_id++);
	}
}

unsigned int Document::nFactorsImpl() const {
	return featureFactors.size() + priorFactors.size();
}

unsigned int Document::nVariablesImpl() const {
	return slots.size();
}

/*unsigned int Document::nRootFactors() const {
	return featureFactors.size() + priorFactors.size();
	}

SlotVariable& Document::variable(unsigned int i) const {
	return *slots[i];
}

GraphicalModel::ModifiableUnaryFactor& Document::factor(unsigned int i) const {
	if (i<featureFactors.size()) {
		return *featureFactors[i];
	} else {
		return *priorFactors[i - featureFactors.size()];
	}
}

GraphicalModel::ModifiableUnaryFactor& Document::rootFactor(unsigned int i) const {
	return factor(i);
}
*/

/*
void Document::clearFactorModifications() {
	BOOST_FOREACH(const BMMFeatureFactor_ptr& featureFactor, featureFactors) {
		featureFactor->clearModifiers();
	}
	BOOST_FOREACH(const ArgTypePriorFactor_ptr& priorFactor, priorFactors) {
		priorFactor->clearModifiers();
	}
}*/

double Document::inferenceImpl() {
	double LL = 0.0;
	// first reset everything
	BOOST_FOREACH(const SlotVariable_ptr& slot, slots) {
		slot->clearMessages();
	}
	BOOST_FOREACH(const BMMFeatureFactor_ptr& featureFactor, featureFactors) {
		featureFactor->clearMessages();
	}
	BOOST_FOREACH(const ArgTypePriorFactor_ptr& priorFactor, priorFactors) {
		priorFactor->clearMessages();
	}

	// all factors are roots and all roots are factors, so simply send messages
	// from all factors and we are done.
	BOOST_FOREACH(const BMMFeatureFactor_ptr& featureFactor, featureFactors) {
		featureFactor->sendMessages();
	}
	BOOST_FOREACH(const ArgTypePriorFactor_ptr& priorFactor, priorFactors) {
		priorFactor->sendMessages();
	}
	std::vector<double> messageProducts(NUM_CLASSES);
	BOOST_FOREACH(const SlotVariable_ptr& slot, slots) {
		slot->receiveMessage();

		slot->productOfIncomingMessages(messageProducts);
		LL += logSumExp(messageProducts);
	}
	return LL;
}

void SlotVariable::dump() const {
	wcout << L"Incoming msgs: " << endl;
	BOOST_FOREACH(const Message_ptr& msg, incomingMessages()) {
		wcout << L"\t";
		for (unsigned int i =0; i<msg->size(); ++i) {
			wcout << (*msg)[i] << L"\t";
		}
		wcout << endl;
	}
	wcout << L"Marginals: ";
	BOOST_FOREACH(double p, marginals()) {
		wcout << p << L"\t";
	}
	wcout << endl;
}

void Document::initializeRandomlyImpl() {
	std::vector<double> tmp(NUM_CLASSES);

	BOOST_FOREACH(const SlotVariable_ptr& slot, slots) {
		double sum = 0;
		BOOST_FOREACH(double& x, tmp) {
			x = rand() % 25 + 5;
			sum += x;
		}
		BOOST_FOREACH(double& x, tmp) {
			x/=sum;
		}
		slot->setMarginals(tmp);
	}
}

void Document::observeImpl() {
	for (size_t i=0; i<slots.size(); ++i) {
		featureFactors[i]->observe(*slots[i]);
		priorFactors[i]->observe(*slots[i]);
	}
}

void Document::clearCountsImpl() {
	ArgTypePriorFactor::clearCounts();
	BMMFeatureFactor::clearCounts();
}

template <typename Dumper>
void Document::updateParametersFromCounts(const Dumper& dumper) {
	ArgTypePriorFactor::updateParametersFromCounts(dumper);
	BMMFeatureFactor::updateParametersFromCounts(dumper);
}

FV_ptr fv(unsigned int slotNum, unsigned int slotIdx, const wstring& slotEntityType,
		const vector<wstring>& words)
{
	FV_ptr vec = make_shared<FV>();
	wstring token = words[slotIdx];
	vec->push_back(token);
	std::vector<wstring> tokParts;
	boost::split(tokParts, token, boost::is_any_of(L" "));

	if (slotEntityType != L"O") {
		vec->push_back(slotEntityType + L"_entity_type");
	}

	if (tokParts.size() > 0) {
		BOOST_FOREACH(const wstring& part, tokParts) {
			vec->push_back(part + L"_part");
		}
	}

	switch(tokParts.size()) {
		case 1:
			vec->push_back(L"single_word");
			break;
		case 2:
			vec->push_back(L"two_words");
			break;
		case 3:
			vec->push_back(L"three_words");
			break;
		case 4:
			vec->push_back(L"four_words");
			break;
		default:
			vec->push_back(L"more_than_four_words");
			break;
	}

	if (slotNum == 0) {
		vec->push_back(L"first_slot");
	} else if (slotNum == 1) {
		vec->push_back(L"second_slot");
	} else if (slotNum == 2) {
		vec->push_back(L"third_slot");
	} else if (slotNum == 3) {
		vec->push_back(L"fourth_or_later_slot");
	}

	if (slotIdx > 0) {
		vec->push_back(words[slotIdx-1] + L"_prev_unigram");
		if (slotIdx > 1) {
			vec->push_back(words[slotIdx-2]
						+ L"_" + words[slotIdx-1] + L"_prev_bigram");
		}
	}

	if (slotIdx < words.size() - 1) {
		vec->push_back(words[slotIdx+1] + L"_next_unigram");
		if (slotIdx < words.size() -2) {
			vec->push_back(words[slotIdx+1] + L"_"
						+ words[slotIdx+2] + L"_next_bigram");
		}
	}

	return vec;
}

class Label {
	public:
		Label(const wstring& str) {
			bco = str[0];
			if (str == L"O") {
				gs = L"null";
			} else {
				if (str.length() < 3 || str[1] != L'-') {
					wcerr << str << endl;
					throw BadLineException(L"Cannot parse label");
				}
				gs = str.substr(2);
			}
		}
		wchar_t bco;
		wstring gs;
};

struct SlotEntry {
	SlotEntry(unsigned int index, const wstring& type, const wstring& entityType)
		: index(index), type(type), entityType(entityType) {}
	unsigned int index;
	wstring type;
	wstring entityType;
};

void finishDocument(bool& in_slot, vector<SlotEntry>& slotIndicesAndTypes,
		vector<wstring>& words, wstring& accum, wstring& slotLabel,
		wstring& slotEntityType, bool annotated,
		const DataSet_ptr& data)
{
	if (in_slot) {
		slotIndicesAndTypes.push_back(
				SlotEntry(words.size(), slotLabel, slotEntityType));
		words.push_back(accum);
		accum = L"";
	}
	Document_ptr docInstances = make_shared<Document>();
	unsigned int slot_num = 0;
	vector<FV_ptr> fvs;

	BOOST_FOREACH(SlotEntry indexAndType, slotIndicesAndTypes) {
		SlotVariable_ptr slotInstance = make_shared<SlotVariable>();
		slotInstance->annotated = annotated;
		slotInstance->gold_label = SlotVariable::classIndex(indexAndType.type);
		slotInstance->tok_idx = indexAndType.index;
		docInstances->slots.push_back(slotInstance);
		fvs.push_back(fv(slot_num, indexAndType.index, indexAndType.entityType,
				words));
		++slot_num;
	}

	if (!docInstances->slots.empty()) {
		docInstances->words = words;
		docInstances->finalize(fvs);
		//data->documents.push_back(docInstances);
		data->addGraph(docInstances);
	}
	words.clear();
	accum = L"";
	slotLabel = L"";
	slotEntityType = L"";
	slotIndicesAndTypes.clear();
	in_slot = false;
}

void loadData(const string& filename, const DataSet_ptr& data)
{
	wifstream inp(filename.c_str());

	wstring line;
	bool first_line = true;
	bool annotated = false;
	bool in_slot = false;
	vector<wstring> words;
	wstring accum = L"";
	vector<SlotEntry> slotIndicesAndTypes;
	wstring slotLabel = L"";
	wstring slotEntityType = L"";
	wstring entityType = L"";

	try {
		while (getline(inp, line)) {
			if (!line.empty()) {
				if (line.find(L"Biography of") == 0) {
					if (first_line) {
						first_line = false;
					} else {
						finishDocument(in_slot, slotIndicesAndTypes, words, accum,
								slotLabel, slotEntityType, annotated, data);
					}

					wstring year = line.substr(13,4);
					if (year ==  L"2006" || year == L"2007" || year == L"2008"
							|| year == L"2009")
					{
						annotated = true;
					} else {
						annotated = false;
					}
				} else {
					vector<wstring> lineParts;
					boost::split(lineParts, line, boost::is_any_of(L" "));
					if (lineParts.size() != 4 || lineParts[1].empty()) {
						throw BadLineException(L"Wrong number or empty parts");
					} else {
						wstring token = lineParts[0];
						transform(token.begin(), token.end(), token.begin(), ::tolower);
						for(unsigned int i = 0; i < token.size(); ++i) {
							if (iswdigit(token[i])) {
								token[i] = L'$';
							}
						}

						Label label = Label(lineParts[1]);
						wstring entityType = lineParts[2];

						if (in_slot) {
							if (label.bco == L'B') {
								slotIndicesAndTypes.push_back(
										SlotEntry(words.size(), slotLabel,
											slotEntityType));
								words.push_back(accum);
								accum = token;
								slotLabel = label.gs;
								slotEntityType = entityType;
							} else if (label.bco == L'O') {
								slotIndicesAndTypes.push_back(
										SlotEntry(words.size(), slotLabel,
											slotEntityType));
								words.push_back(accum);
								accum = L"";
								in_slot = false;
								words.push_back(token);
							} else if (label.bco == L'C') {
								accum += L" " + token;
							} else {
								throw BadLineException(L"Unknown label, in slot");
							}
						} else {
							if (label.bco == L'O') {
								words.push_back(token);
							} else if (label.bco == 'B') {
								accum = token;
								in_slot = true;
								slotLabel = label.gs;
								slotEntityType = entityType;
							} else {
								throw BadLineException(L"Unknown label, out slot");
							}
						}
					}
				}
			}
		}
		finishDocument(in_slot, slotIndicesAndTypes, words, accum,
				slotLabel, slotEntityType, annotated, data);
	} catch (BadLineException& e) {
		wcerr << L"Failed to parse line: " << line << endl;
		wcerr << e.msg << endl;
		throw;
	}
	wcout << L"Loaded " << data->graphs.size() << L" documents." << endl;
}

double score(const GraphicalModel::DataSet<Document>& data, int cls, int cluster) {
	int true_positives = 0;
	int false_positives = 0;
	int false_negatives = 0;

	BOOST_FOREACH(Document_ptr doc, data.graphs) {
		BOOST_FOREACH(SlotVariable_ptr slot, doc->slots) {
			if (slot->annotated) {
				unsigned int best_label = (unsigned int)slot->bestLabel();
				if (best_label == cluster) {
					if (slot->gold_label == cls) {
						++true_positives;
					} else {
						++false_positives;
					}
				} else if (slot->gold_label == cls) {
					++false_negatives;
				}
			}
		}
	}

	if (true_positives == 0) {
		return 0.0;
	} else {
		double precision = ((double)true_positives) / (true_positives + false_positives);
		double recall = ((double)true_positives) / (true_positives + false_negatives);
		return 2.0*precision*recall/(precision+recall);
	}
}

vector<unsigned int> score(const GraphicalModel::DataSet<Document>& data) {
	vector<unsigned int> bestAlignment(NUM_CLASSES);

	for (int cls=0; cls<NUM_CLASSES; ++cls) {
		int best = -1;
		double best_score = -1.0;
		for (int cluster = 0; cluster<NUM_CLASSES; ++cluster) {
			double x = score(data, cls, cluster);
			wcout << L"align class " << cls << L" to cluster " << cluster
				<< L" results in F " << x << endl;
			if (x > best_score) {
				best = cluster;
				best_score = x;
			}
		}
		wcout << L"Best alignment for class " << CLASS_NAMES[cls]
			<< L" is to cluster " << best << L" with an f-measure of "
			<< best_score << endl;
		bestAlignment[cls] = best;
	}
	return bestAlignment;
}

wchar_t * COLOR_CSS = L".cluster0 { background-color: #FF0033; }\n"
L".cluster1 { background-color: #99FF00; }\n"
L".cluster2 { background-color: #99CCFF; }\n"
L".cluster3 { background-color: #FFFFCC; }\n"
L".cluster4 { background-color: #9966FF; }\n"
L".cluster5 { background-color: #FFCC33; }\n"
L".cluster6 { background-color: #00FF7F; }\n";

void dumpFeatureVector(int cnt, Document_ptr doc) {
	wcout << "Document " << cnt << endl;
	for (unsigned int slot_count = 0; slot_count < doc->slots.size(); ++slot_count) {
		wcout << L"Slot " << slot_count << L": ";
		BOOST_FOREACH(unsigned int feat, doc->featureFactors[slot_count]->features()) {
			wcout << BMMFeatureFactor::featureName(feat) << L" ";
		}
		wcout << endl;
		wcout << L"\t";
		for (int i=0; i<NUM_CLASSES; ++i) {
			wcout << CLASS_NAMES[i] << L"=" << setprecision(2)
				<< doc->slots[slot_count]->marginals()[i] << L"\t";
		}
		wcout << endl;
	}
}

template <typename Model>
void applyModel(const string& modelName, Model& model,
		GraphicalModel::DataSet<Document>& data) {
	model.e(data);
	unsigned int cnt = 0;
	vector<unsigned int> bestAlignments = score(data);
	wofstream out((string("debug_") + modelName + ".html").c_str());
	out << L"<title>foo</title>";
	out << "LKey: ";
	for (int i = 0; i < NUM_CLASSES; ++i) {
		out << L"<div class='cluster" << bestAlignments[i] << L"'>" << CLASS_NAMES[i]
			<< L"
";
	}
	BOOST_FOREACH(const Document_ptr& doc, data.graphs) {
		++cnt;
		vector<SlotVariable_ptr>::const_iterator slotIt = doc->slots.begin();
		/*dumpFeatureVector(cnt, doc, alphabet); */
		out << L"
";
		for (int i=0; i<doc->words.size(); ++i) {
			if (slotIt != doc->slots.end()) {
				if (i == (*slotIt)->tok_idx) {
					out << L"<span class='cluster" << (*slotIt)->bestLabel()
						<< L"'>";
				}
			}
			out << doc->words[i] << L" ";
			if (slotIt != doc->slots.end()) {
				if (i == (*slotIt)->tok_idx) {
					out << L"";
					++slotIt;
				}
			}
		}
		out << L"
";
	}
}

void createConstraints(const GraphicalModel::DataSet<Document>& dataset,
		vector<shared_ptr<GraphicalModel::Constraint<Document> > >& constraints)
{
	vector<unsigned int> flightClasses, airlineClasses, modelClasses,
		garbageClasses, locationClasses, countryClasses, locOrCoClasses,
		countryOrGarbageLocClasses, locLikeClasses, locOrGarLocClasses,
		locationOnlyClasses, atMostOneCountryClasses, atMostTwoLocationsClasses,
		atLeastOneLocationIfLocClasses;
	vector<wstring> flightFeatures, airlineFeatures, modelFeatures,
		garbageFeatures, locationFeatures, countryFeatures, locOrCoFeatures,
		countryOrGarbageLocFeatures, locLikeFeatures, locOrGarLocFeatures,
		locationOnlyFeatures, atLeastOneLocationIfLocFeatures;

	flightClasses.push_back(FLIGHT_NUMBER);
	airlineClasses.push_back(AIRLINE);
	modelClasses.push_back(AIRCRAFT_MODEL);
	garbageClasses.push_back(GARBAGE);
	locationClasses.push_back(LOCATION);
	locOrCoClasses.push_back(LOCATION);
	locOrCoClasses.push_back(COUNTRY);
	countryClasses.push_back(COUNTRY);
	countryOrGarbageLocClasses.push_back(COUNTRY);
	countryOrGarbageLocClasses.push_back(GAR_LOC);
	locLikeClasses.push_back(LOCATION);
	locLikeClasses.push_back(COUNTRY);
	locLikeClasses.push_back(GAR_LOC);
	locOrGarLocClasses.push_back(LOCATION);
	locOrGarLocClasses.push_back(GAR_LOC);
	locationOnlyClasses.push_back(LOCATION);
	locationOnlyClasses.push_back(COUNTRY);
	locationOnlyClasses.push_back(GAR_LOC);

	atMostOneCountryClasses.push_back(COUNTRY);
	atMostTwoLocationsClasses.push_back(LOCATION);
	atLeastOneLocationIfLocClasses.push_back(LOCATION);

	flightFeatures.push_back(L"flight_part");

	airlineFeatures.push_back(L"airlines_part");
	airlineFeatures.push_back(L"airways_part");

	modelFeatures.push_back(L"boeing_part");
	modelFeatures.push_back(L"airbus_part");
	modelFeatures.push_back(L"vickers_part");
	modelFeatures.push_back(L"dc-$$_part");
	modelFeatures.push_back(L"dc-$_part");
	modelFeatures.push_back(L"f-$$_part");

	garbageFeatures.push_back(L"decompression_part");
	garbageFeatures.push_back(L"collision_part");
	garbageFeatures.push_back(L"crash_part");
	garbageFeatures.push_back(L"crashed_part");
	garbageFeatures.push_back(L"crashes_part");
	garbageFeatures.push_back(L"explodes_part");
	garbageFeatures.push_back(L"collides_part");
	garbageFeatures.push_back(L"bird strike");
	garbageFeatures.push_back(L"hijacked_part");
	garbageFeatures.push_back(L"including_prev_unigram");
	garbageFeatures.push_back(L"including_the_prev_bigram");
	garbageFeatures.push_back(L"PERSON_entity_type");

	locationFeatures.push_back(L"into_prev_unigram");
	locationFeatures.push_back(L"near_prev_unigram");
	locationFeatures.push_back(L"over_the_prev_bigram");
	locationFeatures.push_back(L"north_of_prev_bigram");
	locationFeatures.push_back(L"mountains_part");
	locationFeatures.push_back(L"mount_part");
	locationFeatures.push_back(L"at_prev_unigram");
	locationFeatures.push_back(L"takeoff_from_prev_bigram");
	locationFeatures.push_back(L"landing_at_prev_bigram");
	locationFeatures.push_back(L"river_part");

	countryOrGarbageLocFeatures.push_back(L"belgium");
	countryOrGarbageLocFeatures.push_back(L"finland");
	countryOrGarbageLocFeatures.push_back(L"norway");
	countryOrGarbageLocFeatures.push_back(L"scotland");
	countryOrGarbageLocFeatures.push_back(L"egypt");
	countryOrGarbageLocFeatures.push_back(L"ireland");
	countryOrGarbageLocFeatures.push_back(L"canada");
	countryOrGarbageLocFeatures.push_back(L"france");

	locLikeFeatures.push_back(L"LOCATION_entity_type");

	locOrCoFeatures.push_back(L"over_prev_unigram");
	locOrCoFeatures.push_back(L"in_prev_unigram");

	locOrGarLocFeatures.push_back(L"indiana");
	locOrGarLocFeatures.push_back(L"california");
	locOrGarLocFeatures.push_back(L"to_the_prev_bigram");

	locationOnlyFeatures.push_back(L"LOCATION_entity_type");

	atLeastOneLocationIfLocFeatures.push_back(L"LOCATION_entity_type");

	typedef FeatureImpliesClassConstraint<BMMFactorConstraintView> FICC;
	constraints.push_back(make_shared<FICC>(dataset, flightFeatures,
				flightClasses, 0.8, BMMFactorConstraintView()));
	constraints.push_back(make_shared<FICC>(dataset, airlineFeatures,
				airlineClasses, 0.9, BMMFactorConstraintView()));
	constraints.push_back(make_shared<FICC>(dataset,
				modelFeatures, modelClasses, 0.9, BMMFactorConstraintView()));
	constraints.push_back(make_shared<FICC>(dataset,
				garbageFeatures, garbageClasses, 0.8, BMMFactorConstraintView()));
	constraints.push_back(make_shared<FICC>(dataset,
				locationFeatures, locationClasses, 0.7, BMMFactorConstraintView()));
	constraints.push_back(make_shared<FICC>(dataset,
				locOrCoFeatures, locOrCoClasses, 0.8, BMMFactorConstraintView()));
	constraints.push_back(make_shared<FICC>(dataset,
				countryOrGarbageLocFeatures, countryOrGarbageLocClasses, 0.9,
				BMMFactorConstraintView()));
	constraints.push_back(make_shared<FICC>(dataset,
				locLikeFeatures, locLikeClasses, 0.9, BMMFactorConstraintView()));
	constraints.push_back(make_shared<FICC>(dataset,
				locOrGarLocFeatures, locOrGarLocClasses, 0.9, BMMFactorConstraintView()));
	constraints.push_back(make_shared<ClassRequiresFeatureConstraint>(dataset,
	 locationOnlyFeatures, locationOnlyClasses, 0.1));
	constraints.push_back(make_shared<AtMostNOfClassConstraint>(
				atMostOneCountryClasses, 1.0));
	constraints.push_back(make_shared<AtMostNOfClassConstraint>(
				atMostTwoLocationsClasses, 2.0));
	constraints.push_back(make_shared<AtLeastNOfClassIfFeatureConstraint>(
				dataset, atLeastOneLocationIfLocClasses,
				atLeastOneLocationIfLocFeatures, 1.0));
}

struct DataDumper {
	void postE(unsigned int i, double ll, const DataSet<Document>& data) const {
		wcout << L"Iteration " << i << L", LL=" << ll << endl;
	}
	void postE(const DataSet<Document>& data) const {

	}
	void postM(unsigned int i, const DataSet<Document>& data) const {}
};

class FixMe {};

int main(int argc, char** argv) {
	srand((unsigned int)time(NULL));
	if (argc < 2 ) {
		cerr << "Must provide data file" << endl;
	} else {
		DataSet_ptr data = make_shared<GraphicalModel::DataSet<Document> >();
		loadData(argv[1], data);
		Document::finishedLoading();
		wcout << "\n\n\nTrying PR..." << endl;
		vector<shared_ptr<Constraint<Document> > > constraints;
		createConstraints(*data, constraints);
		//GraphicalModel::PREM<Document> prModel(*data, NUM_CLASSES, constraints);
		//prModel.em(*data, 15, Reporter());
		const char* source = "posterior regularization main";
		const char* msg = "Change to logging broke this. Fix it if you plan to use it.";
		throw FixMe();
		//applyModel("PR", prModel, *data);
	}
}