import jetbrains.youtrack.workflow.model.*;
import jetbrains.youtrack.workflow.wrappers.*;
Rule rule;

isString(s) {
  String.class.isInstance(s);
}

ScriptedGuardImpl(condition) {
    guard(context) {
      context = EntityWrapperWrapper(context);
      return eval(condition);
    }
    return this;
}

ScriptedActionImpl(action) {
    action(context) {
      context = EntityWrapperWrapper(context);
      eval(action);
      return;
    }
    return this;
}

//used to wrap the context as well
EntityWrapperWrapper(entityWrapper) {
  $() {
    return EntityWrapperWrapperWrapper(entityWrapper);
  }return this;
}

EntityWrapperWrapperWrapper(entityWrapper) {
  invoke(name, args) {
    ret = entityWrapper.invokeEntityMethod(name, args);
    if (ret == null) {
      return null;
    }
    if (EntityWrapper.class.isInstance(ret)) {
      return EntityWrapperWrapper(ret);
    } else if (EntityIterableWrapper.class.isInstance(ret)) {
      return EntityIterableWrapperWrapper(ret);
    } else {
      return ret;
    }
  }return this;
}

EntityIterableWrapperWrapper(entityIterableWrapper) {
  it = entityIterableWrapper.iterator();
  touched = false;

  next() {
    if (it.hasNext()) {
      touched = true;
      return EntityWrapperWrapper(it.next());
    } else {
      return null;
    }
  }

  remove() {
    if (touched) {
       it.remove();
    }
  }

  remove(entityWrapperWrapper) {
    entityIterableWrapper.remove(entityWrapperWrapper.entityWrapper);
  }

  add(entityWrapperWrapper) {
    entityIterableWrapper.add(entityWrapperWrapper.entityWrapper);
  }
}

//----------------- State Machine related stuff ----------------------------
StateWrapper(state) {

  String event;
  Guard condition;
  Action action;
  Action enterAction;//don't confuse with interaction, dudes
  Action exitAction;

  on(e) {
    event = e;
    return this;
  }

  onEnter(a) {
    enterAction = isString(a) ? ScriptedActionImpl(a) : a;
    return this;
  }

  onExit(a) {
    exitAction = isString(a) ? ScriptedActionImpl(a) : a;
    return this;
  }

  when(c) {
    condition = isString(c) ? ScriptedGuardImpl(c) : c;
    return this;
  }

  perform(a) {
    action = isString(a) ? ScriptedActionImpl(a) : a;
    return this;
  }

  invoke(name, targetStatePath) {
    if ("transitTo".equals(name)) {
      targetState = rule.getState(true, toStrings(targetStatePath));
      return create(targetState);
    }
  }

  loop() {
    create(null);
  }

  create(targetState) {
    try {
      state.addTransition(rule.getEvent(this.event),
          condition,
          action,
          targetState);
      state.enter = enterAction;
      state.exit = exitAction;
    } finally {
      event = null;
      condition = null;
      action = null;
      return this;
    }
  }

  return this;
}

statemachine(name, fieldName) {
  rule = new StateMachine(name, RequiredField.create(fieldName));
}

toStrings(Object[] args) {
  strings = new String[args.length];
  for (i = 0; i < args.length; i++) {
    strings[i] = (String) args[i];
  }
  return strings;
}

stateFactory() {
  invoke(name, statePath) {
    if ("state".equals(name)) {
      return StateWrapper(rule.getState(true, toStrings(statePath)));
    }
    return null;
  }
  return this;
}
from = stateFactory();

//-----------End of State Machine related stuff ----------------------------

//---------------- Stateless Rule related stuff ----------------------------
ScriptedGuardedActionImpl(c, a) {
    guard(context) {
      if (c != null) {
        return c.guard(context);
      } else {
        return true;
      }
    }
    action(context) {
      if (a != null) {
        a.action(context);
      }
      return;
    }
    return this;
}

StatelessRuleWrapper(name) {

  String event;
  Guard condition;
  Action action;

  on(e) {
    event = e;
    create();
    return this;
  }

  when(c) {
    condition = isString(c) ? ScriptedGuardImpl(c) : c;
    create();
    return this;
  }

  perform(a) {
    action = isString(a) ? ScriptedActionImpl(a) : a;
    create();
    return this;
  }

  create() {
    g = (GuardedAction) ScriptedGuardedActionImpl(condition, action);
    rule = new StatelessRule(name, new Event(event), g);
  }

  return this;
}

statelessrule(name) {StatelessRuleWrapper(name);}
statelessrule(name, e, c, a) {StatelessRuleWrapper(name).on(e).when(c).perform(a);}
statelessrule(name, e, guardedAction) {rule = new StatelessRule(name, new Event(e), guardedAction);}
//----------End of Stateless Rule related stuff ----------------------------
