/*******************************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 ******************************************************************************/
package org.apache.olingo.odata2.core.debug;

import java.io.IOException;
import java.io.Writer;

import org.apache.olingo.odata2.api.edm.EdmException;
import org.apache.olingo.odata2.api.edm.EdmProperty;
import org.apache.olingo.odata2.api.exception.ODataApplicationException;
import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode;
import org.apache.olingo.odata2.api.uri.UriInfo;
import org.apache.olingo.odata2.api.uri.expression.BinaryExpression;
import org.apache.olingo.odata2.api.uri.expression.CommonExpression;
import org.apache.olingo.odata2.api.uri.expression.ExceptionVisitExpression;
import org.apache.olingo.odata2.api.uri.expression.ExpressionKind;
import org.apache.olingo.odata2.api.uri.expression.ExpressionParserException;
import org.apache.olingo.odata2.api.uri.expression.FilterExpression;
import org.apache.olingo.odata2.api.uri.expression.MemberExpression;
import org.apache.olingo.odata2.api.uri.expression.MethodExpression;
import org.apache.olingo.odata2.api.uri.expression.OrderByExpression;
import org.apache.olingo.odata2.api.uri.expression.OrderExpression;
import org.apache.olingo.odata2.api.uri.expression.UnaryExpression;
import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
import org.apache.olingo.odata2.core.uri.ExpandSelectTreeCreator;
import org.apache.olingo.odata2.core.uri.ExpandSelectTreeNodeImpl;
import org.apache.olingo.odata2.core.uri.expression.JsonVisitor;

/**
 * URI parser debug information.
 */
public class DebugInfoUri implements DebugInfo {

  private final UriInfo uriInfo;
  private final FilterExpression filter;
  private final OrderByExpression orderBy;
  private final ExpandSelectTreeNodeImpl expandSelectTree;
  private final ExpressionParserException exception;

  public DebugInfoUri(final UriInfo uriInfo, final ExpressionParserException exception) {
    this.uriInfo = uriInfo;
    filter = uriInfo == null ? null : uriInfo.getFilter();
    orderBy = uriInfo == null ? null : uriInfo.getOrderBy();
    expandSelectTree = uriInfo == null ? null : getExpandSelect();
    this.exception = exception;
  }

  private ExpandSelectTreeNodeImpl getExpandSelect() {
    try {
      return uriInfo.getExpand().isEmpty() && uriInfo.getSelect().isEmpty() ? null :
          new ExpandSelectTreeCreator(uriInfo.getSelect(), uriInfo.getExpand()).create();
    } catch (final EdmException e) {
      return null;
    }
  }

  @Override
  public String getName() {
    return "URI";
  }

  @Override
  public void appendJson(final JsonStreamWriter jsonStreamWriter) throws IOException {
    jsonStreamWriter.beginObject();

    if (exception != null && exception.getFilterTree() != null) {
      jsonStreamWriter.name("error")
          .beginObject()
          .namedStringValue("expression", exception.getFilterTree().getUriLiteral())
          .endObject();
      if (filter != null || orderBy != null || expandSelectTree != null) {
        jsonStreamWriter.separator();
      }
    }

    if (filter != null) {
      String filterString;
      try {
        filterString = (String) filter.accept(new JsonVisitor());
      } catch (final ExceptionVisitExpression e) {
        filterString = null;
      } catch (final ODataApplicationException e) {
        filterString = null;
      }
      jsonStreamWriter.name("filter").unquotedValue(filterString);
      if (orderBy != null || expandSelectTree != null) {
        jsonStreamWriter.separator();
      }
    }

    if (orderBy != null) {
      String orderByString;
      try {
        orderByString = (String) orderBy.accept(new JsonVisitor());
      } catch (final ExceptionVisitExpression e) {
        orderByString = null;
      } catch (final ODataApplicationException e) {
        orderByString = null;
      }
      jsonStreamWriter.name("orderby").unquotedValue(orderByString);
      if (expandSelectTree != null) {
        jsonStreamWriter.separator();
      }
    }

    if (expandSelectTree != null) {
      jsonStreamWriter.name("expandSelect").unquotedValue(expandSelectTree.toJsonString());
    }

    jsonStreamWriter.endObject();
  }

  @Override
  public void appendHtml(final Writer writer) throws IOException {
    if (exception != null && exception.getFilterTree() != null) {
      writer.append("<h2>Expression Information</h2>\n")
          .append("<pre class=\"code\">").append(exception.getFilterTree().getUriLiteral())
          .append("</pre>\n");
      // TODO: filter error position, filter tokens, filter tree
    }
    if (filter != null) {
      writer.append("<h2>Filter</h2>\n")
          .append("<ul class=\"expr\"><li>");
      appendExpression(filter.getExpression(), writer);
      writer.append("</li></ul>\n");
    }
    if (orderBy != null) {
      writer.append("<h2>Orderby</h2>\n")
          .append(orderBy.getOrdersCount() == 1 ? "<ul" : "<ol").append(" class=\"expr\">\n");
      for (final OrderExpression order : orderBy.getOrders()) {
        writer.append("<li>");
        appendExpression(order.getExpression(), writer);
        final ExpressionKind kind = order.getExpression().getKind();
        if (kind == ExpressionKind.PROPERTY || kind == ExpressionKind.LITERAL) {
          writer.append("<br />");
        }
        writer.append("<span class=\"order\">")
            .append(order.getSortOrder().toString())
            .append("</span></li>\n");
      }
      writer.append(orderBy.getOrdersCount() == 1 ? "</ul" : "</ol").append(">\n");
    }
    if (expandSelectTree != null) {
      writer.append("<h2>Expand/Select</h2>\n");
      appendExpandSelect(expandSelectTree, writer);
    }
  }

  private void appendExpression(final CommonExpression expression, final Writer writer) throws IOException {
    final ExpressionKind kind = expression.getKind();
    writer.append("<span class=\"kind\">")
        .append(kind.toString())
        .append("</span> <span class=\"literal\">")
        .append(kind == ExpressionKind.MEMBER ? ((MemberExpression) expression).getProperty().getUriLiteral() :
            expression.getUriLiteral())
        .append("</span>, type <span class=\"type\">")
        .append(expression.getEdmType().toString())
        .append("</span>");
    if (kind == ExpressionKind.UNARY) {
      writer.append("<ul class=\"expr\"><li>");
      appendExpression(((UnaryExpression) expression).getOperand(), writer);
      writer.append("</li></ul>");
    } else if (kind == ExpressionKind.BINARY) {
      writer.append("<ol class=\"expr\"><li>");
      appendExpression(((BinaryExpression) expression).getLeftOperand(), writer);
      writer.append("</li><li>");
      appendExpression(((BinaryExpression) expression).getRightOperand(), writer);
      writer.append("</li></ol>");
    } else if (kind == ExpressionKind.METHOD) {
      final MethodExpression methodExpression = (MethodExpression) expression;
      if (methodExpression.getParameterCount() > 0) {
        writer.append("<ol class=\"expr\">");
        for (final CommonExpression parameter : methodExpression.getParameters()) {
          writer.append("<li>");
          appendExpression(parameter, writer);
          writer.append("</li>");
        }
        writer.append("</ol>");
      }
    } else if (kind == ExpressionKind.MEMBER) {
      writer.append("<ul class=\"expr\"><li>");
      appendExpression(((MemberExpression) expression).getPath(), writer);
      writer.append("</li></ul>");
    }
  }

  private void appendExpandSelect(final ExpandSelectTreeNode expandSelect, final Writer writer) throws IOException {
    writer.append("<ul class=\"expand\">\n")
        .append("<li>");
    if (expandSelect.isAll()) {
      writer.append("all properties");
    } else {
      for (final EdmProperty property : expandSelect.getProperties()) {
        try {
          writer.append("property <span class=\"prop\">")
              .append(property.getName())
              .append("</span><br />");
        } catch (final EdmException e) {}
      }
    }
    writer.append("</li>\n");
    if (!expandSelect.getLinks().isEmpty()) {
      for (final String name : expandSelect.getLinks().keySet()) {
        writer.append("<li>link <span class=\"link\">").append(name).append("</span>");
        final ExpandSelectTreeNode link = expandSelect.getLinks().get(name);
        if (link != null) {
          writer.append('\n');
          appendExpandSelect(link, writer);
        }
        writer.append("</li>\n");
      }
    }
    writer.append("</ul>\n");
  }
}
