#include <iostream>
#include <stdlib.h>

using namespace std;

attribute noreturn();

namespace gnu {
  attribute my_attr();
  attribute unused();  // this exists
}

// Here we use an attribute before it is define. This is allowed
// according to the language reference manual.
void funcXX [[out::funcAttr("qwertz")]] () {}

typedef const char *STR;
enum EEE { S1, S2, S3 };
namespace out {
  attribute classAttr(EEE);
  attribute funcAttr(STR AP);
  attribute varAttr();
  attribute namespaceAttr();
  attribute innerNsAttr();
  attribute stmtAttr(int param);
  attribute excludeAttr();
  attribute otherAttr();
}

namespace in1 {
  namespace in2 {
    attribute in2_attr_a();
    attribute in2_attr_b();
  }
  attribute in1_attr();
}

namespace gnu {
  attribute unused();
}

[[ using in1 : in2::in2_attr_b, in1_attr ]] void crazy_attrs_1() {}
[[ using in1::in2 : in2_attr_b, in2_attr_a ]] void crazy_attrs_2() {}
[[ using gnu : my_attr, unused ]] void shouldnt_be_called() {}

namespace [[ out::namespaceAttr ]] N {
  const EEE eee = S3;
  struct [[out::classAttr(eee)]] myClass2 {};
}

struct [[out::classAttr((EEE)(4-1))]] myClass;

struct myClass {
  [[noreturn]] [[out::funcAttr("abc")]] void func [[out::otherAttr]] () {exit(0);}
  [[out::varAttr]] int var [[out::otherAttr ()]]; // here we check otherAttr with empty parameter list
  static const EEE eee2 = S1;
  struct [[out::classAttr(eee2)]] innerClass {};
};

namespace N {
  namespace [[out::innerNsAttr]] N2 {
    void func() { }
    struct [[out::classAttr(S3)]] C {
      static void func() {}
    };
  }
}

int main()
{
    myClass obj;
    obj.var = 0;
    N::N2::func();
    N::N2::C::func();
    // test statements with standard C++ attributes
    switch (obj.var) {
    case 0:
      [[out::stmtAttr(0), fallthrough]];
    case 1:
      [[fallthrough]];
    case 2:
      break;
    }
    // test custom attributes for statements
    [[out::stmtAttr(1)]] {
      N::N2::func();
      [[out::stmtAttr(1+1), out::excludeAttr]] {
	N::N2::C::func();
	obj.var = 1;
      }
    }
    crazy_attrs_1();
    crazy_attrs_2();
    shouldnt_be_called();
    [[out::excludeAttr, out::stmtAttr(3)]] obj.func(); // This call ends the test (exit)

    return 0;
}


aspect TestAttributes {

    pointcut all() = out::classAttr() || out::funcAttr();

    advice execution(all()) || construction(all()) : before() {
        cout << "Execution or Construction" << endl;
    }

    advice construction(out::otherAttr()) : before() {
        cout << "Failed: This attribute should have no affect!" << endl;
    }

    advice execution(out::otherAttr()) : before() {
        cout << "Execution of other Attr" << endl;
    }

    advice execution(member(out::classAttr() && member(out::namespaceAttr()))) : before() {
        cout << "Execution of funny member" << endl;
    }

    advice execution(out::namespaceAttr()) : before() {
        cout << "Execution of namespaceAttr" << endl;
    }

    advice execution(out::namespaceAttr() && !out::innerNsAttr()) : before() {
        cout << "Failed: This execution should be filtered!" << endl;
    }

    advice set(out::varAttr()) : before() {
        cout << "var set!" << endl;
    }

    advice set(out::otherAttr()) : before() {
        cout << "var set - other Attr!" << endl;
    }

    advice execution(noreturn()) : before() {
        cout << "This function will not return!!" << endl;
    }

    advice within(out::stmtAttr() && !out::excludeAttr()): before() {
      cout << "JP with stmtAttr: " << JoinPoint::signature() << endl;
    }

    advice within(out::excludeAttr()): before() {
      cout << "Excluded: " << JoinPoint::signature() << endl;
    }

    advice gnu::my_attr() : slice struct { int whatever; };

    advice call(in1::in1_attr()) : before() {
      cout << "function with attr in1::in1_attr called" << endl;
    }
    advice call(in1::in2::in2_attr_b()) : before() {
      cout << "function with attr in1::in2::in2_attr_b called" << endl;
    }
    advice call(gnu::unused()) : before() {
      cout << "unused function \"" << JoinPoint::signature() << "\" called" << endl;
    }
};
