/*
 * Compton - a compositor for X11
 *
 * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
 *
 * Copyright (c) 2011-2013, Christopher Jeffrey
 * See LICENSE for more information.
 *
 */

#include "common.h"

#include <fnmatch.h>
#include <ctype.h>

// libpcre
#ifdef CONFIG_REGEX_PCRE
#include <pcre.h>

// For compatiblity with <libpcre-8.20
#ifndef PCRE_STUDY_JIT_COMPILE
#define PCRE_STUDY_JIT_COMPILE    0
#define LPCRE_FREE_STUDY(extra)   pcre_free(extra)
#else
#define LPCRE_FREE_STUDY(extra)   pcre_free_study(extra)
#endif

#endif

#define C2_MAX_LEVELS 10

typedef struct _c2_b c2_b_t;
typedef struct _c2_l c2_l_t;

/// Pointer to a condition tree.
typedef struct {
  bool isbranch : 1;
//  union {
    c2_b_t *b;
    c2_l_t *l;
//  };
} c2_ptr_t;

/// Initializer for c2_ptr_t.
#define C2_PTR_INIT { \
  .isbranch = false, \
  .l = NULL, \
}

const static c2_ptr_t C2_PTR_NULL = C2_PTR_INIT;

/// Operator of a branch element.
typedef enum {
  C2_B_OUNDEFINED,
  C2_B_OAND,
  C2_B_OOR,
  C2_B_OXOR,
} c2_b_op_t;

/// Structure for branch element in a window condition
struct _c2_b {
  bool neg      : 1;
  c2_b_op_t op;
  c2_ptr_t opr1;
  c2_ptr_t opr2;
};

/// Initializer for c2_b_t.
#define C2_B_INIT { \
  .neg = false, \
  .op = C2_B_OUNDEFINED, \
  .opr1 = C2_PTR_INIT, \
  .opr2 = C2_PTR_INIT, \
}

/// Structure for leaf element in a window condition
struct _c2_l {
  bool neg    : 1;
  enum {
    C2_L_OEXISTS,
    C2_L_OEQ,
    C2_L_OGT,
    C2_L_OGTEQ,
    C2_L_OLT,
    C2_L_OLTEQ,
  } op        : 3;
  enum {
    C2_L_MEXACT,
    C2_L_MSTART,
    C2_L_MCONTAINS,
    C2_L_MWILDCARD,
    C2_L_MPCRE,
  } match     : 3;
  bool match_ignorecase : 1;
  char *tgt;
  Atom tgtatom;
  bool tgt_onframe;
  int index;
  enum {
    C2_L_PUNDEFINED,
    C2_L_PID,
    C2_L_PX,
    C2_L_PY,
    C2_L_PX2,
    C2_L_PY2,
    C2_L_PWIDTH,
    C2_L_PHEIGHT,
    C2_L_PWIDTHB,
    C2_L_PHEIGHTB,
    C2_L_PBDW,
    C2_L_PFULLSCREEN,
    C2_L_POVREDIR,
    C2_L_PARGB,
    C2_L_PFOCUSED,
    C2_L_PWMWIN,
    C2_L_PBSHAPED,
    C2_L_PROUNDED,
    C2_L_PCLIENT,
    C2_L_PWINDOWTYPE,
    C2_L_PLEADER,
    C2_L_PNAME,
    C2_L_PCLASSG,
    C2_L_PCLASSI,
    C2_L_PROLE,
  } predef;
  enum c2_l_type {
    C2_L_TUNDEFINED,
    C2_L_TSTRING,
    C2_L_TCARDINAL,
    C2_L_TWINDOW,
    C2_L_TATOM,
    C2_L_TDRAWABLE,
  } type;
  int format;
  enum {
    C2_L_PTUNDEFINED,
    C2_L_PTSTRING,
    C2_L_PTINT,
  } ptntype;
  char *ptnstr;
  long ptnint;
#ifdef CONFIG_REGEX_PCRE
  pcre *regex_pcre;
  pcre_extra *regex_pcre_extra;
#endif
};

/// Initializer for c2_l_t.
#define C2_L_INIT { \
  .neg = false, \
  .op = C2_L_OEXISTS, \
  .match = C2_L_MEXACT, \
  .match_ignorecase = false, \
  .tgt = NULL, \
  .tgtatom = 0, \
  .tgt_onframe = false, \
  .predef = C2_L_PUNDEFINED, \
  .index = -1, \
  .type = C2_L_TUNDEFINED, \
  .format = 0, \
  .ptntype = C2_L_PTUNDEFINED, \
  .ptnstr = NULL, \
  .ptnint = 0, \
}

const static c2_l_t leaf_def = C2_L_INIT;

/// Linked list type of conditions.
struct _c2_lptr {
  c2_ptr_t ptr;
  void *data;
  struct _c2_lptr *next;
};

/// Initializer for c2_lptr_t.
#define C2_LPTR_INIT { \
  .ptr = C2_PTR_INIT, \
  .data = NULL, \
  .next = NULL, \
}

/// Structure representing a predefined target.
typedef struct {
  const char *name;
  enum c2_l_type type;
  int format;
} c2_predef_t;

// Predefined targets.
const static c2_predef_t C2_PREDEFS[] = {
  [C2_L_PID         ] = { "id"                , C2_L_TCARDINAL  , 0  },
  [C2_L_PX          ] = { "x"                 , C2_L_TCARDINAL  , 0  },
  [C2_L_PY          ] = { "y"                 , C2_L_TCARDINAL  , 0  },
  [C2_L_PX2         ] = { "x2"                , C2_L_TCARDINAL  , 0  },
  [C2_L_PY2         ] = { "y2"                , C2_L_TCARDINAL  , 0  },
  [C2_L_PWIDTH      ] = { "width"             , C2_L_TCARDINAL  , 0  },
  [C2_L_PHEIGHT     ] = { "height"            , C2_L_TCARDINAL  , 0  },
  [C2_L_PWIDTHB     ] = { "widthb"            , C2_L_TCARDINAL  , 0  },
  [C2_L_PHEIGHTB    ] = { "heightb"           , C2_L_TCARDINAL  , 0  },
  [C2_L_PBDW        ] = { "border_width"      , C2_L_TCARDINAL  , 0  },
  [C2_L_PFULLSCREEN ] = { "fullscreen"        , C2_L_TCARDINAL  , 0  },
  [C2_L_POVREDIR    ] = { "override_redirect" , C2_L_TCARDINAL  , 0  },
  [C2_L_PARGB       ] = { "argb"              , C2_L_TCARDINAL  , 0  },
  [C2_L_PFOCUSED    ] = { "focused"           , C2_L_TCARDINAL  , 0  },
  [C2_L_PWMWIN      ] = { "wmwin"             , C2_L_TCARDINAL  , 0  },
  [C2_L_PBSHAPED    ] = { "bounding_shaped"   , C2_L_TCARDINAL  , 0  },
  [C2_L_PROUNDED    ] = { "rounded_corners"   , C2_L_TCARDINAL  , 0  },
  [C2_L_PCLIENT     ] = { "client"            , C2_L_TWINDOW    , 0  },
  [C2_L_PWINDOWTYPE ] = { "window_type"       , C2_L_TSTRING    , 0  },
  [C2_L_PLEADER     ] = { "leader"            , C2_L_TWINDOW    , 0  },
  [C2_L_PNAME       ] = { "name"              , C2_L_TSTRING    , 0  },
  [C2_L_PCLASSG     ] = { "class_g"           , C2_L_TSTRING    , 0  },
  [C2_L_PCLASSI     ] = { "class_i"           , C2_L_TSTRING    , 0  },
  [C2_L_PROLE       ] = { "role"              , C2_L_TSTRING    , 0  },
};

#define mstrncmp(s1, s2) strncmp((s1), (s2), strlen(s1))

/**
 * Compare next word in a string with another string.
 */
static inline int
strcmp_wd(const char *needle, const char *src) {
  int ret = mstrncmp(needle, src);
  if (ret)
    return ret;

  char c = src[strlen(needle)];
  if (isalnum(c) || '_' == c)
    return 1;
  else
    return 0;
}

/**
 * Return whether a c2_ptr_t is empty.
 */
static inline bool
c2_ptr_isempty(const c2_ptr_t p) {
  return !(p.isbranch ? (bool) p.b: (bool) p.l);
}

/**
 * Reset a c2_ptr_t.
 */
static inline void
c2_ptr_reset(c2_ptr_t *pp) {
  if (pp)
    memcpy(pp, &C2_PTR_NULL, sizeof(c2_ptr_t));
}

/**
 * Combine two condition trees.
 */
static inline c2_ptr_t
c2h_comb_tree(c2_b_op_t op, c2_ptr_t p1, c2_ptr_t p2) {
 c2_ptr_t p = {
   .isbranch = true,
   .b = malloc(sizeof(c2_b_t))
 };

 p.b->opr1 = p1;
 p.b->opr2 = p2;
 p.b->op = op;

 return p;
}

/**
 * Get the precedence value of a condition branch operator.
 */
static inline int
c2h_b_opp(c2_b_op_t op) {
  switch (op) {
    case C2_B_OAND:   return 2;
    case C2_B_OOR:    return 1;
    case C2_B_OXOR:   return 1;
    default:          break;
  }

  assert(0);
  return 0;
}

/**
 * Compare precedence of two condition branch operators.
 *
 * Associativity is left-to-right, forever.
 *
 * @return positive number if op1 > op2, 0 if op1 == op2 in precedence,
 *         negative number otherwise
 */
static inline int
c2h_b_opcmp(c2_b_op_t op1, c2_b_op_t op2) {
  return c2h_b_opp(op1) - c2h_b_opp(op2);
}

static int
c2_parse_grp(session_t *ps, const char *pattern, int offset, c2_ptr_t *presult, int level);

static int
c2_parse_target(session_t *ps, const char *pattern, int offset, c2_ptr_t *presult);

static int
c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult);

static int
c2_parse_pattern(session_t *ps, const char *pattern, int offset, c2_ptr_t *presult);

static int
c2_parse_legacy(session_t *ps, const char *pattern, int offset, c2_ptr_t *presult);

static bool
c2_l_postprocess(session_t *ps, c2_l_t *pleaf);

static void
c2_free(c2_ptr_t p);

/**
 * Wrapper of c2_free().
 */
static inline void
c2_freep(c2_ptr_t *pp) {
  if (pp) {
    c2_free(*pp);
    c2_ptr_reset(pp);
  }
}

static const char *
c2h_dump_str_tgt(const c2_l_t *pleaf);

static const char *
c2h_dump_str_type(const c2_l_t *pleaf);

static void
c2_dump_raw(c2_ptr_t p);

/**
 * Wrapper of c2_dump_raw().
 */
static inline void
c2_dump(c2_ptr_t p) {
  c2_dump_raw(p);
  printf("\n");
  fflush(stdout);
}

static Atom
c2_get_atom_type(const c2_l_t *pleaf);

static bool
c2_match_once(session_t *ps, win *w, const c2_ptr_t cond);