/*
 * 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.solr.rest;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.core.SolrCore;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.response.QueryResponseWriter;
import org.apache.solr.response.QueryResponseWriterUtil;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.servlet.ResponseUtils;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Status;
import org.restlet.representation.OutputRepresentation;
import org.restlet.resource.ResourceException;
import org.restlet.resource.ServerResource;
import org.slf4j.Logger;

import static org.apache.solr.common.params.CommonParams.JSON;

/**
 * Base class of all Solr Restlet server resource classes.
 */
public abstract class BaseSolrResource extends ServerResource {
  protected static final String SHOW_DEFAULTS = "showDefaults";
  public static final String UPDATE_TIMEOUT_SECS = "updateTimeoutSecs";

  private SolrCore solrCore;
  private IndexSchema schema;
  private SolrQueryRequest solrRequest;
  private SolrQueryResponse solrResponse;
  private QueryResponseWriter responseWriter;
  private String contentType;
  private int updateTimeoutSecs = -1;

  public SolrCore getSolrCore() { return solrCore; }
  public IndexSchema getSchema() { return schema; }
  public SolrQueryRequest getSolrRequest() { return solrRequest; }
  public SolrQueryResponse getSolrResponse() { return solrResponse; }
  public String getContentType() { return contentType; }
  protected int getUpdateTimeoutSecs() { return updateTimeoutSecs; }

  protected BaseSolrResource() {
    super();
  }

  /**
   * Pulls the SolrQueryRequest constructed in SolrDispatchFilter
   * from the SolrRequestInfo thread local, then gets the SolrCore
   * and IndexSchema and sets up the response.
   * writer.
   * <p>
   * If an error occurs during initialization, setExisting(false) is
   * called and an error status code and message is set; in this case,
   * Restlet will not continue servicing the request (by calling the
   * method annotated to associate it with GET, etc., but rather will
   * send an error response.
   */
  @Override
  public void doInit() throws ResourceException {
    super.doInit();
    setNegotiated(false); // Turn off content negotiation for now
    if (isExisting()) {
      try {
        SolrRequestInfo solrRequestInfo = SolrRequestInfo.getRequestInfo();
        if (null == solrRequestInfo) {
          final String message = "No handler or core found in " + getRequest().getOriginalRef().getPath();
          doError(Status.CLIENT_ERROR_BAD_REQUEST, message);
          setExisting(false);
        } else {
          solrRequest = solrRequestInfo.getReq();
          if (null == solrRequest) {
            final String message = "No handler or core found in " + getRequest().getOriginalRef().getPath();
            doError(Status.CLIENT_ERROR_BAD_REQUEST, message);
            setExisting(false);
          } else {
            solrResponse = solrRequestInfo.getRsp();
            solrCore = solrRequest.getCore();
            schema = solrRequest.getSchema();
            String responseWriterName = solrRequest.getParams().get(CommonParams.WT);
            if (null == responseWriterName) {
              responseWriterName = JSON; // Default to json writer
            }
            String indent = solrRequest.getParams().get("indent");
            if (null == indent || ! ("off".equals(indent) || "false".equals(indent))) {
              // indent by default
              ModifiableSolrParams newParams = new ModifiableSolrParams(solrRequest.getParams());
              newParams.remove(indent);
              newParams.add("indent", "on");
              solrRequest.setParams(newParams);
            }
            responseWriter = solrCore.getQueryResponseWriter(responseWriterName);
            contentType = responseWriter.getContentType(solrRequest, solrResponse);
            final String path = getRequest().getRootRef().getPath();
            if ( ! RestManager.SCHEMA_BASE_PATH.equals(path)) {
              // don't set webapp property on the request when context and core/collection are excluded 
              final int cutoffPoint = path.indexOf("/", 1);
              final String firstPathElement = -1 == cutoffPoint ? path : path.substring(0, cutoffPoint);
              solrRequest.getContext().put("webapp", firstPathElement); // Context path
            }
            SolrCore.preDecorateResponse(solrRequest, solrResponse);

            // client application can set a timeout for update requests
            Object updateTimeoutSecsParam = getSolrRequest().getParams().get(UPDATE_TIMEOUT_SECS);
            if (updateTimeoutSecsParam != null)
              updateTimeoutSecs = (updateTimeoutSecsParam instanceof Number)
                  ? ((Number) updateTimeoutSecsParam).intValue()
                  : Integer.parseInt(updateTimeoutSecsParam.toString());

          }
        }
      } catch (Throwable t) {
        if (t instanceof OutOfMemoryError) {
          throw (OutOfMemoryError) t;
        }
        setExisting(false);
        throw new ResourceException(t);
      }
    }
  }

  /**
   * This class serves as an adapter between Restlet and Solr's response writers. 
   */
  public class SolrOutputRepresentation extends OutputRepresentation {
    
    public SolrOutputRepresentation() {
      // No normalization, in case of a custom media type
      super(MediaType.valueOf(contentType));
      // TODO: For now, don't send the Vary: header, but revisit if/when content negotiation is added
      getDimensions().clear();
    }
    
    
    /** Called by Restlet to get the response body */
    @Override
    public void write(OutputStream outputStream) throws IOException {
      if (getRequest().getMethod() != Method.HEAD) {
        QueryResponseWriterUtil.writeQueryResponse(outputStream, responseWriter, solrRequest, solrResponse, contentType);
      }
    }
  }

  /**
   * Deal with an exception on the SolrResponse, fill in response header info,
   * and log the accumulated messages on the SolrResponse.
   */
  protected void handlePostExecution(Logger log) {
    
    handleException(log);
    
    // TODO: should status=0 (success?) be left as-is in the response header?
    SolrCore.postDecorateResponse(null, solrRequest, solrResponse);
    addDeprecatedWarning();

    if (log.isInfoEnabled() && solrResponse.getToLog().size() > 0) {
      log.info(solrResponse.getToLogAsString(solrCore.getLogId()));
    }
  }

  protected void addDeprecatedWarning(){
    solrResponse.add("warn","This API is deprecated");

  }

  /**
   * If there is an exception on the SolrResponse:
   * <ul>
   *   <li>error info is added to the SolrResponse;</li>
   *   <li>the response status code is set to the error code from the exception; and</li>
   *   <li>the exception message is added to the list of things to be logged.</li>
   * </ul>
   */
  protected void handleException(Logger log) {
    Exception exception = getSolrResponse().getException();
    if (null != exception) {
      NamedList info = new SimpleOrderedMap();
      int code = ResponseUtils.getErrorInfo(exception, info, log);
      setStatus(Status.valueOf(code));
      getSolrResponse().add("error", info);
      String message = (String)info.get("msg");
      if (null != message && ! message.trim().isEmpty()) {
        getSolrResponse().getToLog().add("msg", "{" + message.trim() + "}");
      }
    }
  }

  /** Decode URL-encoded strings as UTF-8, and avoid converting "+" to space */
  protected static String urlDecode(String str) throws UnsupportedEncodingException {
    return URLDecoder.decode(str.replace("+", "%2B"), "UTF-8");
  }
}
