// Copyright (C) 2020-2023 Free Software Foundation, Inc.

// This file is part of GCC.

// GCC is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3, or (at your option) any later
// version.

// GCC 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 GNU General Public License
// for more details.

// You should have received a copy of the GNU General Public License
// along with GCC; see the file COPYING3.  If not see
// <http://www.gnu.org/licenses/>.

#ifndef RUST_TYTY_CMP_H
#define RUST_TYTY_CMP_H

#include "rust-diagnostics.h"
#include "rust-tyty.h"
#include "rust-tyty-visitor.h"
#include "rust-hir-map.h"
#include "rust-hir-type-check.h"

namespace Rust {
namespace TyTy {

// we need to fix this properly by implementing the match for assembling
// candidates
extern bool autoderef_cmp_flag;

class BaseCmp : public TyConstVisitor
{
public:
  virtual bool can_eq (const BaseType *other)
  {
    if (other->get_kind () == TypeKind::PARAM)
      {
	const ParamType *p = static_cast<const ParamType *> (other);
	other = p->resolve ();
      }
    if (other->get_kind () == TypeKind::PLACEHOLDER)
      {
	const PlaceholderType *p = static_cast<const PlaceholderType *> (other);
	if (p->can_resolve ())
	  {
	    other = p->resolve ();
	  }
      }
    if (other->get_kind () == TypeKind::PROJECTION)
      {
	const ProjectionType *p = static_cast<const ProjectionType *> (other);
	other = p->get ();
      }

    other->accept_vis (*this);
    return ok;
  }

  virtual void visit (const TupleType &type) override
  {
    ok = false;

    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const ADTType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const InferType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const FnType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const FnPtr &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const ArrayType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const SliceType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const BoolType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const IntType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const UintType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const USizeType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const ISizeType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const FloatType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const ErrorType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const CharType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const ReferenceType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const PointerType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const StrType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const NeverType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const ProjectionType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const PlaceholderType &type) override
  {
    // it is ok for types to can eq to a placeholder
    ok = true;
  }

  virtual void visit (const ParamType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const DynamicObjectType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

  virtual void visit (const ClosureType &type) override
  {
    ok = false;
    if (emit_error_flag)
      {
	Location ref_locus = mappings->lookup_location (type.get_ref ());
	Location base_locus
	  = mappings->lookup_location (get_base ()->get_ref ());
	RichLocation r (ref_locus);
	r.add_range (base_locus);
	rust_error_at (r, "expected [%s] got [%s]",
		       get_base ()->as_string ().c_str (),
		       type.as_string ().c_str ());
      }
  }

protected:
  BaseCmp (const BaseType *base, bool emit_errors)
    : mappings (Analysis::Mappings::get ()),
      context (Resolver::TypeCheckContext::get ()), ok (false),
      emit_error_flag (emit_errors)
  {}

  Analysis::Mappings *mappings;
  Resolver::TypeCheckContext *context;

  bool ok;
  bool emit_error_flag;

private:
  /* Returns a pointer to the ty that created this rule. */
  virtual const BaseType *get_base () const = 0;
};

class InferCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  InferCmp (const InferType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const BoolType &type) override
  {
    bool is_valid
      = (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL);
    if (is_valid)
      {
	ok = true;
	return;
      }

    BaseCmp::visit (type);
  }

  void visit (const IntType &type) override
  {
    bool is_valid
      = (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL)
	|| (base->get_infer_kind ()
	    == TyTy::InferType::InferTypeKind::INTEGRAL);
    if (is_valid)
      {
	ok = true;
	return;
      }

    BaseCmp::visit (type);
  }

  void visit (const UintType &type) override
  {
    bool is_valid
      = (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL)
	|| (base->get_infer_kind ()
	    == TyTy::InferType::InferTypeKind::INTEGRAL);
    if (is_valid)
      {
	ok = true;
	return;
      }

    BaseCmp::visit (type);
  }

  void visit (const USizeType &type) override
  {
    bool is_valid
      = (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL)
	|| (base->get_infer_kind ()
	    == TyTy::InferType::InferTypeKind::INTEGRAL);
    if (is_valid)
      {
	ok = true;
	return;
      }

    BaseCmp::visit (type);
  }

  void visit (const ISizeType &type) override
  {
    bool is_valid
      = (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL)
	|| (base->get_infer_kind ()
	    == TyTy::InferType::InferTypeKind::INTEGRAL);
    if (is_valid)
      {
	ok = true;
	return;
      }

    BaseCmp::visit (type);
  }

  void visit (const FloatType &type) override
  {
    bool is_valid
      = (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL)
	|| (base->get_infer_kind () == TyTy::InferType::InferTypeKind::FLOAT);
    if (is_valid)
      {
	ok = true;
	return;
      }

    BaseCmp::visit (type);
  }

  void visit (const ArrayType &type) override
  {
    bool is_valid
      = (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL);
    if (is_valid)
      {
	ok = true;
	return;
      }

    BaseCmp::visit (type);
  }

  void visit (const SliceType &type) override
  {
    bool is_valid
      = (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL);
    if (is_valid)
      {
	ok = true;
	return;
      }

    BaseCmp::visit (type);
  }

  void visit (const ADTType &type) override
  {
    bool is_valid
      = (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL);
    if (is_valid)
      {
	ok = true;
	return;
      }

    BaseCmp::visit (type);
  }

  void visit (const TupleType &type) override
  {
    bool is_valid
      = (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL);
    if (is_valid)
      {
	ok = true;
	return;
      }

    BaseCmp::visit (type);
  }

  void visit (const InferType &type) override
  {
    switch (base->get_infer_kind ())
      {
      case InferType::InferTypeKind::GENERAL:
	ok = true;
	return;

	case InferType::InferTypeKind::INTEGRAL: {
	  if (type.get_infer_kind () == InferType::InferTypeKind::INTEGRAL)
	    {
	      ok = true;
	      return;
	    }
	  else if (type.get_infer_kind () == InferType::InferTypeKind::GENERAL)
	    {
	      ok = true;
	      return;
	    }
	}
	break;

	case InferType::InferTypeKind::FLOAT: {
	  if (type.get_infer_kind () == InferType::InferTypeKind::FLOAT)
	    {
	      ok = true;
	      return;
	    }
	  else if (type.get_infer_kind () == InferType::InferTypeKind::GENERAL)
	    {
	      ok = true;
	      return;
	    }
	}
	break;
      }

    BaseCmp::visit (type);
  }

  void visit (const CharType &type) override
  {
    {
      bool is_valid
	= (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL);
      if (is_valid)
	{
	  ok = true;
	  return;
	}

      BaseCmp::visit (type);
    }
  }

  void visit (const ReferenceType &type) override
  {
    bool is_valid
      = (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL);
    if (is_valid)
      {
	ok = true;
	return;
      }

    BaseCmp::visit (type);
  }

  void visit (const PointerType &type) override
  {
    bool is_valid
      = (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL);
    if (is_valid)
      {
	ok = true;
	return;
      }

    BaseCmp::visit (type);
  }

  void visit (const ParamType &) override { ok = true; }

  void visit (const DynamicObjectType &type) override
  {
    bool is_valid
      = (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL);
    if (is_valid)
      {
	ok = true;
	return;
      }

    BaseCmp::visit (type);
  }

  void visit (const ClosureType &type) override
  {
    bool is_valid
      = (base->get_infer_kind () == TyTy::InferType::InferTypeKind::GENERAL);
    if (is_valid)
      {
	ok = true;
	return;
      }

    BaseCmp::visit (type);
  }

private:
  const BaseType *get_base () const override { return base; }
  const InferType *base;
};

class FnCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  FnCmp (const FnType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const InferType &type) override
  {
    ok = type.get_infer_kind () == InferType::InferTypeKind::GENERAL;
  }

  void visit (const FnType &type) override
  {
    if (base->num_params () != type.num_params ())
      {
	BaseCmp::visit (type);
	return;
      }

    for (size_t i = 0; i < base->num_params (); i++)
      {
	auto a = base->param_at (i).second;
	auto b = type.param_at (i).second;

	if (!a->can_eq (b, emit_error_flag))
	  {
	    emit_error_flag = false;
	    BaseCmp::visit (type);
	    return;
	  }
      }

    if (!base->get_return_type ()->can_eq (type.get_return_type (),
					   emit_error_flag))
      {
	emit_error_flag = false;
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

private:
  const BaseType *get_base () const override { return base; }
  const FnType *base;
};

class FnptrCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  FnptrCmp (const FnPtr *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const InferType &type) override
  {
    if (type.get_infer_kind () != InferType::InferTypeKind::GENERAL)
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

  void visit (const FnPtr &type) override
  {
    if (base->num_params () != type.num_params ())
      {
	BaseCmp::visit (type);
	return;
      }

    auto this_ret_type = base->get_return_type ();
    auto other_ret_type = type.get_return_type ();
    if (!this_ret_type->can_eq (other_ret_type, emit_error_flag))
      {
	BaseCmp::visit (type);
	return;
      }

    for (size_t i = 0; i < base->num_params (); i++)
      {
	auto this_param = base->param_at (i);
	auto other_param = type.param_at (i);
	if (!this_param->can_eq (other_param, emit_error_flag))
	  {
	    BaseCmp::visit (type);
	    return;
	  }
      }

    ok = true;
  }

  void visit (const FnType &type) override
  {
    if (base->num_params () != type.num_params ())
      {
	BaseCmp::visit (type);
	return;
      }

    auto this_ret_type = base->get_return_type ();
    auto other_ret_type = type.get_return_type ();
    if (!this_ret_type->can_eq (other_ret_type, emit_error_flag))
      {
	BaseCmp::visit (type);
	return;
      }

    for (size_t i = 0; i < base->num_params (); i++)
      {
	auto this_param = base->param_at (i);
	auto other_param = type.param_at (i).second;
	if (!this_param->can_eq (other_param, emit_error_flag))
	  {
	    BaseCmp::visit (type);
	    return;
	  }
      }

    ok = true;
  }

private:
  const BaseType *get_base () const override { return base; }
  const FnPtr *base;
};

class ClosureCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  ClosureCmp (const ClosureType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const InferType &type) override
  {
    if (type.get_infer_kind () != InferType::InferTypeKind::GENERAL)
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

  void visit (const ClosureType &type) override
  {
    if (base->get_def_id () != type.get_def_id ())
      {
	BaseCmp::visit (type);
	return;
      }

    if (!base->get_parameters ().can_eq (&type.get_parameters (), false))
      {
	BaseCmp::visit (type);
	return;
      }

    if (!base->get_result_type ().can_eq (&type.get_result_type (), false))
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

private:
  const BaseType *get_base () const override { return base; }
  const ClosureType *base;
};

class ArrayCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  ArrayCmp (const ArrayType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const ArrayType &type) override
  {
    // check base type
    const BaseType *base_element = base->get_element_type ();
    const BaseType *other_element = type.get_element_type ();
    if (!base_element->can_eq (other_element, emit_error_flag))
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

  void visit (const InferType &type) override
  {
    if (type.get_infer_kind () != InferType::InferTypeKind::GENERAL)
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

private:
  const BaseType *get_base () const override { return base; }
  const ArrayType *base;
};

class SliceCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  SliceCmp (const SliceType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const SliceType &type) override
  {
    // check base type
    const BaseType *base_element = base->get_element_type ();
    const BaseType *other_element = type.get_element_type ();
    if (!base_element->can_eq (other_element, emit_error_flag))
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

  void visit (const InferType &type) override
  {
    if (type.get_infer_kind () != InferType::InferTypeKind::GENERAL)
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

private:
  const BaseType *get_base () const override { return base; }
  const SliceType *base;
};

class BoolCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  BoolCmp (const BoolType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const BoolType &type) override { ok = true; }

  void visit (const InferType &type) override
  {
    ok = type.get_infer_kind () == InferType::InferTypeKind::GENERAL;
  }

private:
  const BaseType *get_base () const override { return base; }
  const BoolType *base;
};

class IntCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  IntCmp (const IntType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const InferType &type) override
  {
    ok = type.get_infer_kind () != InferType::InferTypeKind::FLOAT;
  }

  void visit (const IntType &type) override
  {
    ok = type.get_int_kind () == base->get_int_kind ();
  }

private:
  const BaseType *get_base () const override { return base; }
  const IntType *base;
};

class UintCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  UintCmp (const UintType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const InferType &type) override
  {
    ok = type.get_infer_kind () != InferType::InferTypeKind::FLOAT;
  }

  void visit (const UintType &type) override
  {
    ok = type.get_uint_kind () == base->get_uint_kind ();
  }

private:
  const BaseType *get_base () const override { return base; }
  const UintType *base;
};

class FloatCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  FloatCmp (const FloatType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const InferType &type) override
  {
    ok = type.get_infer_kind () != InferType::InferTypeKind::INTEGRAL;
  }

  void visit (const FloatType &type) override
  {
    ok = type.get_float_kind () == base->get_float_kind ();
  }

private:
  const BaseType *get_base () const override { return base; }
  const FloatType *base;
};

class ADTCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  ADTCmp (const ADTType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const ADTType &type) override
  {
    if (base->get_adt_kind () != type.get_adt_kind ())
      {
	BaseCmp::visit (type);
	return;
      }

    if (base->get_identifier ().compare (type.get_identifier ()) != 0)
      {
	BaseCmp::visit (type);
	return;
      }

    if (base->number_of_variants () != type.number_of_variants ())
      {
	BaseCmp::visit (type);
	return;
      }

    for (size_t i = 0; i < type.number_of_variants (); ++i)
      {
	TyTy::VariantDef *a = base->get_variants ().at (i);
	TyTy::VariantDef *b = type.get_variants ().at (i);

	if (a->num_fields () != b->num_fields ())
	  {
	    BaseCmp::visit (type);
	    return;
	  }

	for (size_t j = 0; j < a->num_fields (); j++)
	  {
	    TyTy::StructFieldType *base_field = a->get_field_at_index (j);
	    TyTy::StructFieldType *other_field = b->get_field_at_index (j);

	    TyTy::BaseType *this_field_ty = base_field->get_field_type ();
	    TyTy::BaseType *other_field_ty = other_field->get_field_type ();

	    if (!this_field_ty->can_eq (other_field_ty, emit_error_flag))
	      {
		BaseCmp::visit (type);
		return;
	      }
	  }
      }

    ok = true;
  }

  void visit (const InferType &type) override
  {
    if (type.get_infer_kind () != InferType::InferTypeKind::GENERAL)
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

private:
  const BaseType *get_base () const override { return base; }
  const ADTType *base;
};

class TupleCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  TupleCmp (const TupleType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const TupleType &type) override
  {
    if (base->num_fields () != type.num_fields ())
      {
	BaseCmp::visit (type);
	return;
      }

    for (size_t i = 0; i < base->num_fields (); i++)
      {
	BaseType *bo = base->get_field (i);
	BaseType *fo = type.get_field (i);

	if (!bo->can_eq (fo, emit_error_flag))
	  {
	    BaseCmp::visit (type);
	    return;
	  }
      }

    ok = true;
  }

  void visit (const InferType &type) override
  {
    if (type.get_infer_kind () != InferType::InferTypeKind::GENERAL)
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

private:
  const BaseType *get_base () const override { return base; }
  const TupleType *base;
};

class USizeCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  USizeCmp (const USizeType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const InferType &type) override
  {
    ok = type.get_infer_kind () != InferType::InferTypeKind::FLOAT;
  }

  void visit (const USizeType &type) override { ok = true; }

private:
  const BaseType *get_base () const override { return base; }
  const USizeType *base;
};

class ISizeCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  ISizeCmp (const ISizeType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const InferType &type) override
  {
    ok = type.get_infer_kind () != InferType::InferTypeKind::FLOAT;
  }

  void visit (const ISizeType &type) override { ok = true; }

private:
  const BaseType *get_base () const override { return base; }
  const ISizeType *base;
};

class CharCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  CharCmp (const CharType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const InferType &type) override
  {
    ok = type.get_infer_kind () == InferType::InferTypeKind::GENERAL;
  }

  void visit (const CharType &type) override { ok = true; }

private:
  const BaseType *get_base () const override { return base; }
  const CharType *base;
};

class ReferenceCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  ReferenceCmp (const ReferenceType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const ReferenceType &type) override
  {
    auto base_type = base->get_base ();
    auto other_base_type = type.get_base ();

    bool mutability_ok = base->is_mutable () ? type.is_mutable () : true;
    if (autoderef_cmp_flag)
      mutability_ok = base->mutability () == type.mutability ();

    if (!mutability_ok)
      {
	BaseCmp::visit (type);
	return;
      }

    if (!base_type->can_eq (other_base_type, emit_error_flag))
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

  void visit (const InferType &type) override
  {
    if (type.get_infer_kind () != InferType::InferTypeKind::GENERAL)
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

private:
  const BaseType *get_base () const override { return base; }
  const ReferenceType *base;
};

class PointerCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  PointerCmp (const PointerType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const PointerType &type) override
  {
    auto base_type = base->get_base ();
    auto other_base_type = type.get_base ();

    bool mutability_ok = base->is_mutable () ? type.is_mutable () : true;
    if (autoderef_cmp_flag)
      mutability_ok = base->mutability () == type.mutability ();

    if (!mutability_ok)
      {
	BaseCmp::visit (type);
	return;
      }

    if (!base_type->can_eq (other_base_type, emit_error_flag))
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

  void visit (const InferType &type) override
  {
    if (type.get_infer_kind () != InferType::InferTypeKind::GENERAL)
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

private:
  const BaseType *get_base () const override { return base; }
  const PointerType *base;
};

class ParamCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  ParamCmp (const ParamType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  // param types are a placeholder we shouldn't have cases where we unify
  // against it. eg: struct foo<T> { a: T }; When we invoke it we can do either:
  //
  // foo<i32>{ a: 123 }.
  // Then this enforces the i32 type to be referenced on the
  // field via an hirid.
  //
  // rust also allows for a = foo{a:123}; Where we can use an Inference Variable
  // to handle the typing of the struct
  bool can_eq (const BaseType *other) override
  {
    if (!base->can_resolve ())
      return BaseCmp::can_eq (other);

    auto lookup = base->resolve ();
    return lookup->can_eq (other, emit_error_flag);
  }

  // imagine the case where we have:
  // struct Foo<T>(T);
  // Then we declare a generic impl block
  // impl <X>Foo<X> { ... }
  // both of these types are compatible so we mostly care about the number of
  // generic arguments
  void visit (const ParamType &) override { ok = true; }

  void visit (const TupleType &) override { ok = true; }

  void visit (const InferType &) override { ok = true; }

  void visit (const FnType &) override { ok = true; }

  void visit (const FnPtr &) override { ok = true; }

  void visit (const ADTType &) override { ok = true; }

  void visit (const ArrayType &) override { ok = true; }

  void visit (const SliceType &) override { ok = !autoderef_cmp_flag; }

  void visit (const BoolType &) override { ok = true; }

  void visit (const IntType &) override { ok = true; }

  void visit (const UintType &) override { ok = true; }

  void visit (const USizeType &) override { ok = true; }

  void visit (const ISizeType &) override { ok = true; }

  void visit (const FloatType &) override { ok = true; }

  void visit (const CharType &) override { ok = true; }

  void visit (const ReferenceType &) override { ok = true; }

  void visit (const PointerType &) override { ok = true; }

  void visit (const StrType &) override { ok = true; }

  void visit (const NeverType &) override { ok = true; }

  void visit (const DynamicObjectType &) override { ok = true; }

  void visit (const PlaceholderType &type) override
  {
    ok = base->get_symbol ().compare (type.get_symbol ()) == 0;
  }

private:
  const BaseType *get_base () const override { return base; }
  const ParamType *base;
};

class StrCmp : public BaseCmp
{
  // FIXME we will need a enum for the StrType like ByteBuf etc..
  using Rust::TyTy::BaseCmp::visit;

public:
  StrCmp (const StrType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const StrType &type) override { ok = true; }

  void visit (const InferType &type) override
  {
    if (type.get_infer_kind () != InferType::InferTypeKind::GENERAL)
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

private:
  const BaseType *get_base () const override { return base; }
  const StrType *base;
};

class NeverCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  NeverCmp (const NeverType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const NeverType &type) override { ok = true; }

  void visit (const InferType &type) override
  {
    if (type.get_infer_kind () != InferType::InferTypeKind::GENERAL)
      {
	BaseCmp::visit (type);
	return;
      }

    ok = true;
  }

private:
  const BaseType *get_base () const override { return base; }
  const NeverType *base;
};

class PlaceholderCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  PlaceholderCmp (const PlaceholderType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  bool can_eq (const BaseType *other) override
  {
    if (!base->can_resolve ())
      return BaseCmp::can_eq (other);

    BaseType *lookup = base->resolve ();
    return lookup->can_eq (other, emit_error_flag);
  }

  void visit (const TupleType &) override { ok = true; }

  void visit (const ADTType &) override { ok = true; }

  void visit (const InferType &) override { ok = true; }

  void visit (const FnType &) override { ok = true; }

  void visit (const FnPtr &) override { ok = true; }

  void visit (const ArrayType &) override { ok = true; }

  void visit (const BoolType &) override { ok = true; }

  void visit (const IntType &) override { ok = true; }

  void visit (const UintType &) override { ok = true; }

  void visit (const USizeType &) override { ok = true; }

  void visit (const ISizeType &) override { ok = true; }

  void visit (const FloatType &) override { ok = true; }

  void visit (const ErrorType &) override { ok = true; }

  void visit (const CharType &) override { ok = true; }

  void visit (const ReferenceType &) override { ok = true; }

  void visit (const ParamType &) override { ok = true; }

  void visit (const StrType &) override { ok = true; }

  void visit (const NeverType &) override { ok = true; }

  void visit (const SliceType &) override { ok = true; }

private:
  const BaseType *get_base () const override { return base; }

  const PlaceholderType *base;
};

class DynamicCmp : public BaseCmp
{
  using Rust::TyTy::BaseCmp::visit;

public:
  DynamicCmp (const DynamicObjectType *base, bool emit_errors)
    : BaseCmp (base, emit_errors), base (base)
  {}

  void visit (const DynamicObjectType &type) override
  {
    if (base->num_specified_bounds () != type.num_specified_bounds ())
      {
	BaseCmp::visit (type);
	return;
      }

    Location ref_locus = mappings->lookup_location (type.get_ref ());
    ok = base->bounds_compatible (type, ref_locus, false);
  }

private:
  const BaseType *get_base () const override { return base; }

  const DynamicObjectType *base;
};

} // namespace TyTy
} // namespace Rust

#endif // RUST_TYTY_CMP_H
