// SLOC is a "source location".
// We need this abstraction in order to have location-parameterized asserts.
struct SLOC
  {
  char* filename;
  unsigned int lineNumber;
	
  friend int operator==(const SLOC& lhs, const SLOC& rhs);

  SLOC(char* filenameT, unsigned int lineNumberT) :
    filename(filenameT),
    lineNumber(lineNumberT)
      {
      }
  SLOC(const SLOC& rhs)
    {
    this->filename = rhs.filename;
    this->lineNumber = rhs.lineNumber;
    }
  };

int operator==(const SLOC& lhs, const SLOC& rhs)
  {
  if (lhs.lineNumber != rhs.lineNumber)
    return false;
  if (!strcmp(lhs.filename, rhs.filename))
    return false;
  return true;
  }

// if you use _d_ twice on the same source line, you will get the same sloc.  To get different SLOCs, put a line break.
#define _d_ SLOC(__FILE__, __LINE__)
#define slocNil SLOC("", 0)

// *you* must define this!
extern void MalfunctionCore(const char* message, const char* title);

inline void Malfunction(SLOC sloc, const char* message, const char* title) 
  {
  std::ostringstream message_with_sloc;
  message_with_sloc <<
    title <<
    " in file " << 
    sloc.fileName <<
    ", line #" << 
    sloc.uiLine << 
    ":\n" << 
    message;

  MalfunctionCore(message_with_sloc.c_str(), title);
  }

inline void Assert(SLOC sloc, bool condition) 
  {
  if (!condition)
    Malfunction(sloc, "Assertion Failure", "Assertion Failure");
  }

inline void NotReached(SLOC sloc) 
  {
  Malfunction(sloc, "Unreachable Condition", "Unreachable Condition");
  }

inline void NotImplemented(SLOC sloc) 
  {
  Malfunction(sloc, "Not Implemented", "Not Implemented");
  }

inline void AssertMsg(SLOC sloc, bool condition, const char* message) 
  {
  if (!condition)
    Malfunction(sloc, message, "Assertion Failure");
  }

class TRT
  {
protected:
  SLOC slocLast;
  const char* description;

  void AssertHelper(SLOC sloc, bool expected, char* value_as_string) const
    {
    std::ostringstream message;
    message <<
      (expected ? "Expected " : "Didn't expect") <<
      this->description << 
      " to be " << 
      value_as_string << 
      (expected ? " and it was not" : " and it was") <<
      "; last assignment was at line #" <<
      this->slocLast.lineNumber <<
      " in file " <<
      this->slocLast.filename;
    
    Malfunction(sloc, message.c_str());
    }

public:
  TRT (SLOC sloc, const char* descriptionT) :
      slocLast(sloc)
    {
    this->description = descriptionT;
    }
};

// TRacker; use only for small types
// helpful for keeping track of where a value was assigned last
// costs a bit of overhead
template <class T> class TR : public TRT
  {
private:
  T value;

public:
  TR (SLOC sloc, T const t, const char* descriptionT) :
      TRT(sloc, descriptionT)
    {
    this->value = t;
    }
  void Set(SLOC sloc, T const valueT)
    {
    this->value = valueT;
    this->slocLast = sloc;
    }
  T Get()
    {
    return this->value;
    }
  operator const T&() 
    { 
    return this->value; 
    }
  T& operator-> ()
    {
    return this->value;
    }
  void Ensure(SLOC sloc, T const valueT)
    {
    if (this->value != valueT)
      this->Set(sloc, valueT);
    }
  void AssertValue(SLOC sloc, T const valueT, const char* value_as_string) const
    {
    if (this->value == valueT)
      return;

    this->AssertHelper(sloc, true, value_as_string);
    }
  void AssertNotValue(SLOC sloc, T const valueT, const char* value_as_string) const
    {
    if (this->value != valueT)
      return;

    this->AssertHelper(sloc, false, value_as_string);
    }
};

// Demonstration class for TRacker using boolean
class TR_bool : public TR<bool>
  {
public:
  TR_bool(SLOC sloc, bool valueT, const char* descriptionT) :
      TR<bool>(sloc, valueT, descriptionT)
    {
    }
  void AssertTrue(SLOC sloc) const
    {
    this->AssertValue(sloc, true, "true");
    }
  void AssertFalse(SLOC sloc) const
    {
    this->AssertValue(sloc, false, "false");
    }
  void ChangeToTrue(SLOC sloc)
    {
    this->AssertFalse(sloc);
    this->Set(sloc, true);
    }
  void ChangeToFalse(SLOC sloc)
    {
    this->AssertTrue(sloc);
    this->Set(sloc, false);
    }
  void EnsureFalse(SLOC sloc)
    {
    this->Ensure(sloc, false);
    }
  void EnsureTrue(SLOC sloc)
    {
    this->Ensure(sloc, true);
    }
};