#ifndef LLVM_SUPPORT_AOCC_DEVOUTIL_H
#define LLVM_SUPPORT_AOCC_DEVOUTIL_H
#if AOCC_BUILD
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Path.h"
#include "llvm/ADT/StringRef.h"
#include <cstdlib>

namespace llvm {

class Value;
class Module;
class BasicBlock;
class StoreInst;
class raw_ostream;
class BranchInst;
class CallBase;
class ReturnInst;
namespace aocc
{
namespace streamopt
{
  template<class T>
  struct NULL_WRAP_CL
  {
    const T* const D;
    NULL_WRAP_CL(const T* const D) :D(D) { }
  };
  template<class T>
  inline raw_ostream &operator<<(raw_ostream& OS, const NULL_WRAP_CL<T> &V) {
    if (V.D)
      return (OS << *(V.D));
    else
      return (OS << "(null)");
  }
  template<class T>
  inline NULL_WRAP_CL<T> nullwrap(const T* const V) { return NULL_WRAP_CL<T>(V); }

  template<class T>
  struct IDENT_CL
  {
    const T* const D;
    const Module *M;
    IDENT_CL(const T * const D, const Module *M) :D(D), M(M) {}
  };
  template<class T>
  void printIdentT(raw_ostream &OS, const T * const tt, const Module *M)
  {
    tt->printAsOperand(OS, M);
  }
  template<> void printIdentT(raw_ostream &OS, const StoreInst * const V, const Module *M);
  template<> void printIdentT(raw_ostream &OS, const BasicBlock * const V, const Module *M);
  template<> void printIdentT(raw_ostream &OS, const BranchInst * const V, const Module *M);
  template<> void printIdentT(raw_ostream &OS, const Value *const V, const Module *M);
  template<> void printIdentT(raw_ostream &OS, const CallBase *const V, const Module *M);
  template<> void printIdentT(raw_ostream &OS, const ReturnInst *const V, const Module *M);
  template<class T>
  inline raw_ostream &operator<<(raw_ostream &OS, const IDENT_CL<T> &V) {
    if (V.D)
      printIdentT(OS, V.D, V.M);
    else
      OS << "<null>";
    return OS;
  }
  template<class T>
  inline IDENT_CL<T> operand(const T* const V, const Module * M = nullptr) { return IDENT_CL<T>(V, M); }
}
}
}

#define IDENT(X) llvm::aocc::streamopt::operand(X)
#define IDENTM(X,M) llvm::aocc::streamopt::operand(X,M)
#define NULL_WRAP(X) llvm::aocc::streamopt::nullwrap(X)
#define FIDENT(X) (X ? (isa<Instruction>(X) ? (cast<Instruction>(X)->getParent() ? cast<Instruction>(X)->getFunction()->getName() : "?") : \
                       (isa<Argument>(X) ? cast<Argument>(X)->getParent()->getName() : \
                       (isa<GlobalObject>(X) ? "global" : "?"))) : "?") << '/' << IDENT(X)

namespace llvm{
  class Function;
  class Value;
namespace aocc{
  bool isOptDisabledByFlowConfig(StringRef optName, const Function& F);
  bool isOptDisabledByFlowConfig(StringRef optName, const Value *);
  bool isOptRequiredByFlowConfig(StringRef optName, const Function& F);
  bool isOptRequiredByFlowConfig(StringRef optName, const Value *);
}
}
#define AOCC_FLOW_ENABLED(XX, ...) (!llvm::aocc::isOptDisabledByFlowConfig(#XX, __VA_ARGS__))
#define AOCC_FLOW_DISABLED(XX, ...) (llvm::aocc::isOptDisabledByFlowConfig(#XX, __VA_ARGS__))
#define AOCC_FLOW_REQUIRED(XX, ...) (llvm::aocc::isOptRequiredByFlowConfig(#XX, __VA_ARGS__))
#define AOCC_FLOW_CONTEXT_TEST(XX, ...) llvm::aocc::OptAOCCFlowConfig::Test _AOCC_FLOWCONTEXT_##XX(#XX, __VA_ARGS__)
#define AOCC_FLOW_MARK_LTO_START FlowMarkerPass("aocc::flowcxt::lto", "start")
#define AOCC_FLOW_MARK_LTO_STOP FlowMarkerPass("aocc::flowcxt::lto", "stop")

namespace llvm {
namespace aocc {
  struct Indent {
    static int i;
    int off;
    raw_ostream& OS;
    Indent(raw_ostream& OS, int off = 1) :OS(OS), off(off) { i += off; }
    Indent(Indent& Other, int off = 0) :OS(Other.OS), off(off) { i += off; }
    ~Indent() { i -= off; }
    raw_ostream& operator ()(int xi = 0) { return OS.indent(2 * (i + xi)); }
    operator raw_ostream& () { return OS; }

  };

  struct Clock {
#define NCLOCKS 100
    time_t marks[NCLOCKS] = {0};
    struct {
      const char * s;
      int l;
    } tags[NCLOCKS] = {};
    int nmarks = 0;
    int enable = false;
    Indent& II;
    Clock(Indent& II, const char *t, bool enable = false) :II(II), enable(enable) { mark(t); }
    void mark(const char *t, int l = 0) {
      if (!enable) return;
      if (nmarks >= NCLOCKS) { ++nmarks; return; }
      struct timespec tp;
#if defined(_WIN32) || defined (_WIN64)
      timespec_get(&tp, TIME_UTC);
#else
      clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp);
#endif
      marks[nmarks] = tp.tv_sec * (1000 * 1000 * 1000) + tp.tv_nsec;
      tags[nmarks].s = t;
      tags[nmarks].l = (l ? l : strlen(t));
      ++nmarks;
    }
    void split(time_t tx, time_t& tns, time_t& tus, time_t& tms, time_t& ts)
    {
      tns = tx % 1000; tx /= 1000;
      tus = tx % 1000; tx /= 1000;
      tms = tx % 1000; tx /= 1000;
      ts = tx % 1000; tx /= 1000;
    }
    ~Clock() {
      if (nmarks) {
        if (nmarks >= NCLOCKS) {
          II() << "too many clocks. max: " << NCLOCKS << ", ask: " << nmarks << "\n";
          nmarks = NCLOCKS;
        }
        time_t tns, tus, tms, ts;
        split(marks[nmarks-1] - marks[0] + 1, tns, tus, tms, ts);
        II() << "clock " << std::string(tags[0].s, tags[0].l) << " (" << ts << "s " << tms << "ms " << tus << "us " << tns << "ns) " << "{\n";
        time_t last = marks[0];
        for (int i = 1; i < nmarks; ++i) {
          auto  tx = (marks[i] - last + 1);
          split(tx, tns, tus, tms, ts);
          II(1) << std::string(tags[i].s, tags[i].l) << ": " << tx << "ns (" << ts << "s " << tms << "ms " << tus << "us " << tns << "ns)" << '\n';
          last = marks[i];
        }
        II() << "}\n";
      }
    }
  };
}
}

#if AOCC_DEVO_UTIL_MACRO_CONTROLFLOW

#if AOCC_DEVO_UTIL_DEBUG_CONTROLFLOW
#define CF_MARK(_AOCC_DEVO_ARG_X) LLVM_DEBUG(dbgs() << __FILE__ << ':' << __LINE__ << ' ' << _AOCC_DEVO_ARG_X << '\n')
#define CF_MARK_EXP(_AOCC_DEVO_ARG_X,_AOCC_DEVO_ARG_V) ((DEBUG_WITH_TYPE_ENABLED(DEBUG_TYPE) && (dbgs() << __FILE__ << ':' << __LINE__ << ' ' << _AOCC_DEVO_ARG_X << '\n')), _AOCC_DEVO_ARG_V)
#else
#define CF_MARK(X)
#define CF_MARK_EXP(X,VV) VV
#endif // AOCC_DEVO_UTIL_DEBUG_CONTROLFLOW

#define NEXT_IF_S(_AOCC_DEVO_ARG_X, _AOCC_DEVO_ARG_S,...) if ((_AOCC_DEVO_ARG_X)) { CF_MARK("continue " << #_AOCC_DEVO_ARG_X  __VA_OPT__(<< ' ' <<) __VA_ARGS__); _AOCC_DEVO_ARG_S; continue; } else { }
#define BREAK_IF_S(_AOCC_DEVO_ARG_X, _AOCC_DEVO_ARG_S,...) if ((_AOCC_DEVO_ARG_X)) { CF_MARK("break " << #_AOCC_DEVO_ARG_X __VA_OPT__(<< ' ' <<) __VA_ARGS__); _AOCC_DEVO_ARG_S; break; } else { }
#define RETURN_IF_S(_AOCC_DEVO_ARG_X,_AOCC_DEVO_ARG_R,_AOCC_DEVO_ARG_S,...) if ((_AOCC_DEVO_ARG_X)) { CF_MARK("return " << #_AOCC_DEVO_ARG_X __VA_OPT__(<< ' ' <<) __VA_ARGS__); _AOCC_DEVO_ARG_S; return(_AOCC_DEVO_ARG_R); } else { }
#define VRETURN_IF_S(_AOCC_DEVO_ARG_X,_AOCC_DEVO_ARG_S,...) if ((_AOCC_DEVO_ARG_X)) {  CF_MARK("return " << #_AOCC_DEVO_ARG_X __VA_OPT__(<< ' ' <<) __VA_ARGS__); _AOCC_DEVO_ARG_S; return; } else { }
#define RETURN_S(_AOCC_DEVO_ARG_R,_AOCC_DEVO_ARG_S,...) { CF_MARK("return " __VA_OPT__(<< ": " <<) __VA_ARGS__); _AOCC_DEVO_ARG_S; return(_AOCC_DEVO_ARG_R); }
#define VRETURN_S(_AOCC_DEVO_ARG_S) { CF_MARK("return"); _AOCC_DEVO_ARG_S; return; }

#define NEXT_IF(_AOCC_DEVO_ARG_X,...) if ((_AOCC_DEVO_ARG_X)) { CF_MARK("continue " << #_AOCC_DEVO_ARG_X __VA_OPT__(<< ' ' <<) __VA_ARGS__); continue; } else { }
#define BREAK_IF(_AOCC_DEVO_ARG_X,...) if ((_AOCC_DEVO_ARG_X)) { CF_MARK("break " << #_AOCC_DEVO_ARG_X __VA_OPT__(<< ' ' <<) __VA_ARGS__); break; } else { }
#define RETURN_IF(_AOCC_DEVO_ARG_X,_AOCC_DEVO_ARG_R,...) if ((_AOCC_DEVO_ARG_X)) { CF_MARK("return " << #_AOCC_DEVO_ARG_X __VA_OPT__(<< ' ' <<) __VA_ARGS__); return(_AOCC_DEVO_ARG_R); } else { }
#define VRETURN_IF(_AOCC_DEVO_ARG_X,...) if ((_AOCC_DEVO_ARG_X)) { CF_MARK("return " << #_AOCC_DEVO_ARG_X __VA_OPT__(<< ' ' <<) __VA_ARGS__); return; } else { }
#define RETURN(_AOCC_DEVO_ARG_R,...) { CF_MARK("return " __VA_OPT__(<< ": " <<) __VA_ARGS__); return(_AOCC_DEVO_ARG_R); }
#define VRETURN { CF_MARK("return"); return; }
#define TEST(_AOCC_DEVO_ARG_CC) ((_AOCC_DEVO_ARG_CC) ? (CF_MARK_EXP("istrue " << #_AOCC_DEVO_ARG_CC, true), true) : (CF_MARK_EXP("isfalse " << #_AOCC_DEVO_ARG_CC, true), false))

#define RETURN_IF_VNULL(_AOCC_DEVO_ARG_VV,_AOCC_DEVO_ARG_DD,_AOCC_DEVO_ARG_RR) auto *_AOCC_DEVO_ARG_VV = _AOCC_DEVO_ARG_DD; RETURN_IF(!(_AOCC_DEVO_ARG_VV), _AOCC_DEVO_ARG_RR)
#define NEXT_IF_VNULL_S(_AOCC_DEVO_ARG_VV,_AOCC_DEVO_ARG_DD,_AOCC_DEVO_ARG_SS) auto *_AOCC_DEVO_ARG_VV = _AOCC_DEVO_ARG_DD; NEXT_IF_S(!(_AOCC_DEVO_ARG_VV), _AOCC_DEVO_ARG_SS)
#define NEXT_IF_VNULL(_AOCC_DEVO_ARG_VV,_AOCC_DEVO_ARG_DD)      auto *_AOCC_DEVO_ARG_VV = _AOCC_DEVO_ARG_DD; NEXT_IF(!(_AOCC_DEVO_ARG_VV))

#endif // AOCC_DEVO_UTIL_MACRO_CONTROLFLOW

#endif // AOCC_BUILD
#endif
