/*
   +----------------------------------------------------------------------+
   | HipHop for PHP                                                       |
   +----------------------------------------------------------------------+
   | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com)  |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
*/

#ifndef incl_HPHP_EVAL_DEBUGGER_BREAK_POINT_H_
#define incl_HPHP_EVAL_DEBUGGER_BREAK_POINT_H_

#include <list>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "hphp/runtime/base/type-variant.h"
#include "hphp/runtime/vm/unit.h"

namespace HPHP {
///////////////////////////////////////////////////////////////////////////////

struct ActRec;
struct DebuggerThriftBuffer;

namespace Eval {
///////////////////////////////////////////////////////////////////////////////

/*
 * The type of interrupt that is sent from the server to notify the debugger
 * client about a notable event during execution.
 */
enum InterruptType : int8_t {
  // The server is now ready to interact with the debugger
  SessionStarted,
  // The server has terminated the debug session.
  SessionEnded,
  // The server has received a web request
  RequestStarted,
  // The server has sent a response to the web request
  RequestEnded,
  // The server has finished all processing of a web request
  // also known as Post Send Processing has Ended.
  PSPEnded,
  // The server has executed f_hphpd_break()
  HardBreakPoint,
  // The server has reached a point where it has been told to stop and wait
  // for the debugger to tell it to resume execution. For example,
  // a user breakpoint has been reached, or a step command has completed.
  BreakPointReached,
  // The server is about throw an exception
  ExceptionThrown,

  // The server has reached the start of an exception handler.
  ExceptionHandler,
  // The above type of interrupt is not sent from the server to the debugger
  // but is used for flow control inside the server. We could consider exposing
  // this type of interrupt to clients, and thus allowing users to request the
  // server to break execution when an interrupt handler is reached, but the
  // value seems quite low at this time.
  // We have assertions that check that these interrupts stays server-side.
};

/*
 * Represents a site in the code, at the source level.  Forms an InterruptSite
 * by looking at the current thread's current PC and grabbing source data out of
 * the corresponding Unit.
 */
struct InterruptSite {
  InterruptSite(bool hardBreakPoint, const Variant& e);

  const InterruptSite *getCallingSite() const;
  const char *getFile() const { return m_file.data(); }
  const char *getClass() const { return m_class ? m_class : ""; }
  const char *getFunction() const { return m_function.data(); }
  // Placeholder for future namespace support.
  const char *getNamespace() const { return nullptr; }
  int getFileLen() const;

  int32_t getLine0() const { return m_line0; }
  int32_t getChar0() const { return m_char0; }
  int32_t getLine1() const { return m_line1; }
  int32_t getChar1() const { return m_char1; }

  // Optionally provided by VM, could be an exception object, a string, or null
  // depending on the context.
  const Variant& getError() { return m_error; }

  std::string &url() const { return m_url; }
  std::string desc() const;

  const SourceLoc* getSourceLoc() const { return &m_sourceLoc; }
  const Offset getCurOffset() const { return m_offset; }
  const Unit* getUnit() const { return m_unit; }

  bool valid() const { return m_valid; }
  bool funcEntry() const { return m_funcEntry; }
  bool isBuiltin() const { return m_builtin; }

  // for unique_ptr constructor
  InterruptSite(ActRec* fp, Offset offset, const Variant& error);

private:
  void Initialize(ActRec *fp);

  Variant m_error;
  ActRec *m_activationRecord;

  // cached
  mutable req::unique_ptr<const InterruptSite> m_callingSite;
  mutable const char *m_class;
  mutable std::string m_function;
  mutable String m_file;
  mutable std::string m_url;

  int32_t m_line0;
  int32_t m_char0;
  int32_t m_line1;
  int32_t m_char1;

  SourceLoc m_sourceLoc;
  Offset m_offset;
  Unit* m_unit;
  bool m_valid;
  bool m_funcEntry;
  bool m_builtin;
};

///////////////////////////////////////////////////////////////////////////////

struct BreakPointInfo;
struct DebuggerProxy;
struct DFunctionInfo;

using BreakPointInfoPtr = std::shared_ptr<BreakPointInfo>;
using DebuggerProxyPtr = std::shared_ptr<DebuggerProxy>;

struct BreakPointInfo {
  // The state of the break point
  enum State : int8_t {
    Always   = -1, // always break when reaching this break point
    Once     = 1, // break the first time, then disable
    Disabled = 0, // carry on with execution when reaching this break point
  };

  // Does the break point correspond to a known executable location?
  enum BindState : int8_t {
    KnownToBeValid, // Breakpoint refers to valid location or member
    KnownToBeInvalid, // Breakpoint cannot be bound (no such class or line)
    Unknown, // The file or class referenced by breakpoint is not loaded
  };

  static const char *ErrorClassName;

  static const char *GetInterruptName(InterruptType interrupt);
  static bool MatchFile(const char *haystack, int haystack_len,
                        const std::string &needle);
  static bool MatchFile(const std::string& file, const std::string& fullPath);

  BreakPointInfo() : m_index(0) {} // for thrift
  BreakPointInfo(bool regex, State state, const std::string &file, int line);
  BreakPointInfo(bool regex, State state, InterruptType interrupt,
                 const std::string &url);
  BreakPointInfo(bool regex, State state, InterruptType interrupt,
                 const std::string &exp, const std::string &file);
  ~BreakPointInfo();

  void setClause(const std::string &clause, bool check);
  void toggle();
  void transferStack(BreakPointInfoPtr bpi);
  void setState(State state) { m_state = state; }

  bool valid();
  bool same(BreakPointInfoPtr bpi);
  bool match(DebuggerProxy &proxy, InterruptType interrupt,
      InterruptSite &site);
  bool cmatch(DebuggerProxy &proxy, InterruptType interrupt,
      InterruptSite &site);

  int index() const { return m_index;}
  std::string state(bool padding) const;
  std::string desc() const;

  std::string site() const;
  std::string regex(const std::string &name) const;

  void sendImpl(int version, DebuggerThriftBuffer &thrift);
  void recvImpl(int version, DebuggerThriftBuffer &thrift);

  static void SendImpl(int version,
                       const std::vector<BreakPointInfoPtr>& bps,
                       DebuggerThriftBuffer &thrift);
  static void RecvImpl(int version,
                       std::vector<BreakPointInfoPtr>& bps,
                       DebuggerThriftBuffer &thrift);

  bool breakable(int stackDepth, Offset offset) const;
  void unsetBreakable(int stackDepth, Offset offset);
  void setBreakable(int stackDepth);

  int16_t m_index; // client side index number

  State m_state; // Always, Once, Disabled
  BindState m_bindState; // KnownToBeValid, KnownToBeInvalid, Unknown
  bool m_valid; // false if syntactically invalid
  InterruptType m_interruptType; // Why this break point was reached

  // file::line1-line2
  std::string m_file;
  int32_t m_line1;
  int32_t m_line2;
  int32_t m_char1;
  int32_t m_char2;

  // class::func()
  std::vector<std::shared_ptr<DFunctionInfo>> m_funcs;

  std::string getNamespace() const;
  std::string getClass() const;
  std::string getFunction() const;
  std::string getFuncName() const;
  std::string getExceptionClass() const { return m_class; }

  // URL
  std::string m_url;

  // whether strings are regex
  bool m_regex;

  // "if", "&&" clause
  bool m_check;
  std::string m_clause;
  std::string m_php; // cached

  // server results
  std::string m_output;
  std::string m_exceptionClass;
  std::string m_exceptionObject;

private:
  // exception class
  std::string m_namespace;
  std::string m_class;
  // Records the stack depth and offset of first operation for each break point
  // that is currently disabled except at deeper stack levels.
  std::list<std::pair<int, Offset>> m_stack;

  static bool Match(const char *haystack, int haystack_len,
                    const std::string &needle, bool regex, bool exact);
  static bool MatchClass(const char *fcls, const std::string &bcls,
                         bool regex, const char *func);
  bool match(DebuggerProxy &proxy, InterruptType interrupt,
      InterruptSite &site, bool evalClause);

  void createIndex();
  std::string descBreakPointReached() const;
  std::string descExceptionThrown() const;

  void parseExceptionThrown(const std::string &exp);
  void parseBreakPointReached(const std::string &exp, const std::string &file);
  int32_t parseFileLocation(const std::string &str, int32_t offset);
  bool parseLines(const std::string &token);

  bool checkExceptionOrError(const Variant& e);
  bool checkUrl(std::string &url);
  bool checkLines(int line);
  bool checkStack(InterruptSite &site);
  bool checkClause(DebuggerProxy &proxy);
};

///////////////////////////////////////////////////////////////////////////////
}}

#endif // incl_HPHP_EVAL_DEBUGGER_BREAK_POINT_H_
