package org.springframework.data.rest.shell.commands;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.EnvironmentAccessor;
import org.springframework.context.expression.MapAccessor;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ParserContext;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.springframework.stereotype.Component;

/**
 * @author Jon Brisbin
 */
@Component
public class ContextCommands implements CommandMarker, InitializingBean {

  private static final Logger LOG = LoggerFactory.getLogger(ContextCommands.class);

  private static final PropertyAccessor MAP_ACCESOR  = new MapAccessor();
  private static final PropertyAccessor BEAN_ACCESOR = new ReflectivePropertyAccessor();
  private static final PropertyAccessor ENV_ACCESSOR = new EnvironmentAccessor();
  private static final Environment      ENV          = new StandardEnvironment();

  final Map<String, Object> variables = new HashMap<String, Object>();
  StandardEvaluationContext evalCtx;

  private final SpelExpressionParser parser              = new SpelExpressionParser();
  private final ParserContext        parserContext       = new TemplateParserContext();
  private final ObjectMapper         mapper              = new ObjectMapper();
  private       BeanResolver         beanFactoryResolver = null;

  private ApplicationContext userAppCtx;

  {
    mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
    mapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
  }

  @Override public void afterPropertiesSet() throws Exception {
    userAppCtx = new ClassPathXmlApplicationContext("classpath*:META-INF/rest-shell/**/*.xml");
    beanFactoryResolver = new BeanFactoryResolver(userAppCtx);
    clear();
  }

  @CliAvailabilityIndicator({"var clear", "var set", "var get", "var list"})
  public boolean available() {
    return true;
  }

  @CliCommand(value = "var clear", help = "Clear this shell's variable context")
  public void clear() {
    if(variables.containsKey("links")) {
      evalCtx.removePropertyAccessor(((Links)variables.get("links")).getPropertyAccessor());
    }
    variables.clear();
    setup();
    if(LOG.isDebugEnabled()) {
      LOG.debug("Cleared context variables...");
    }
  }

  @CliCommand(value = "var set", help = "Set a variable in this shell's context")
  public void set(
      @CliOption(
          key = "name",
          mandatory = true,
          help = "The name of the variable to set") String name,
      @CliOption(
          key = "value",
          mandatory = false,
          help = "The value of the variable to set") String value) {

    if(null == value) {
      variables.remove(name);
      return;
    }

    if(value.contains("#{")) {
      Object val = eval(value);
      variables.put(name, val);
    } else if(value.startsWith("{")) {
      try {
        variables.put(name, mapper.readValue(value.replaceAll("\\\\", "").replaceAll("'", "\""), Map.class));
      } catch(IOException e) {
        throw new IllegalArgumentException(e);
      }
    } else if(value.startsWith("[")) {
      try {
        variables.put(name, mapper.readValue(value.replaceAll("\\\\", "").replaceAll("'", "\""), List.class));
      } catch(IOException e) {
        throw new IllegalArgumentException(e);
      }
    } else if(userAppCtx.containsBean(value)) {
      variables.put(name, userAppCtx.getBean(value));
    } else {
      variables.put(name, value);
    }

    if(LOG.isDebugEnabled()) {
      LOG.debug("Set context variable '" + name + "' to " + variables.get(name));
    }
  }

  @CliCommand(value = "var get", help = "Get a variable in this shell's context")
  public Object get(
      @CliOption(
          key = "name",
          mandatory = false,
          help = "The name of the variable to get") String name,
      @CliOption(
          key = "value",
          mandatory = false,
          help = "The value of the variable to set") String value) {

    if(null != name) {
      if(variables.containsKey(name)) {
        return variables.get(name);
      } else if(userAppCtx.containsBean(name)) {
        return userAppCtx.getBean(name);
      }
    }

    if(null != value && value.contains("#{")) {
      return eval(value);
    }

    return null;
  }

  @CliCommand(value = "var list", help = "List variables currently set in this shell's context")
  public String list() {
    try {
      variables.remove("env");
      return mapper.writeValueAsString(variables);
    } catch(IOException e) {
      throw new IllegalStateException(e);
    } finally {
      variables.put("env", ENV);
    }
  }

  public Object eval(String expr) {
    if(null == expr || !expr.contains("#{")) {
      return expr;
    }
    return parser.parseExpression(expr, parserContext).getValue(evalCtx);
  }

  public String evalAsString(String expr) {
    Object o = eval(expr);
    if(null != o) {
      return o.toString();
    }
    return null;
  }

  private void setup() {
    evalCtx = new StandardEvaluationContext(variables);
    List<PropertyAccessor> accessors = new ArrayList<PropertyAccessor>();
    accessors.add(MAP_ACCESOR);
    accessors.add(BEAN_ACCESOR);
    accessors.add(ENV_ACCESSOR);
    evalCtx.setPropertyAccessors(accessors);
    evalCtx.setBeanResolver(beanFactoryResolver);
    variables.put("env", ENV);
  }

}

@@ -5141,36 +5141,30 @@
 The 
-value of the variable to set
+expression to evaluate
 %22) S

Tweak help text.