/*
 * 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.jena.fuseki.servlets;

import java.io.IOException ;
import java.io.PrintWriter ;
import java.util.concurrent.atomic.AtomicLong ;

import javax.servlet.http.HttpServlet ;
import javax.servlet.http.HttpServletRequest ;
import javax.servlet.http.HttpServletResponse ;

import org.apache.jena.fuseki.Fuseki ;
import org.apache.jena.fuseki.HttpNames ;
import org.apache.jena.web.HttpSC ;
import org.slf4j.Logger ;

/**
 * An abstract HTTP Servlet. Contains implementation methods for setting the request status in a HTTP Action,
 * and a mechanism to allocate unique ID's to new requests.
 */
public abstract class ServletBase extends HttpServlet
{
    protected static final Logger log = Fuseki.requestLog ;
    public final boolean verboseLogging = Fuseki.verboseLogging ;
    private static AtomicLong requestIdAlloc = new AtomicLong(0) ;

    protected ServletBase()     { }
    
    /**
     * Helper method which gets a unique request ID and appends it as a header to the response
     * @param request  HTTP Request
     * @param response HTTP Response
     * @return Request ID
     */
    protected long allocRequestId(HttpServletRequest request, HttpServletResponse response) {
        long id = requestIdAlloc.incrementAndGet();
        addRequestId(response, id);
        return id;
    }
    
    /**
     * Helper method for attaching a request ID to a response as a header
     * @param response Response
     * @param id Request ID
     */
    protected void addRequestId(HttpServletResponse response, long id) {
        response.addHeader("Fuseki-Request-ID", Long.toString(id));
    }
    
    protected void responseSendError(HttpServletResponse response, int statusCode, String message)
    {
        try { response.sendError(statusCode, message) ; }
        catch (IOException ex) { errorOccurred(ex) ; }
        catch (IllegalStateException ex) { }
    }
    
    protected void responseSendError(HttpServletResponse response, int statusCode)
    {
        try { response.sendError(statusCode) ; }
        catch (IOException ex) { errorOccurred(ex) ; }
    }

    /**
     * Returns the HTTP request URL, appended with any additional URL parameters used.
     *
     * @param request HTTP request
     * @return complete request URL
     */
    protected static String wholeRequestURL(HttpServletRequest request)
    {
        StringBuffer sb = request.getRequestURL() ;
        String queryString = request.getQueryString() ;
        if ( queryString != null )
        {
            sb.append("?") ;
            sb.append(queryString) ;
        }
        return sb.toString() ;
    }
    
    protected static void successNoContent(HttpAction action)
    {
        success(action, HttpSC.NO_CONTENT_204);
    }
    
    protected static void success(HttpAction action)
    {
        success(action, HttpSC.OK_200);
    }

    protected static void successCreated(HttpAction action)
    {
        success(action, HttpSC.CREATED_201);
    }
    
    // When 404 is no big deal e.g. HEAD
    protected static void successNotFound(HttpAction action) 
    {
        success(action, HttpSC.NOT_FOUND_404) ;
    }

    //
    protected static void success(HttpAction action, int httpStatusCode)
    {
        action.response.setStatus(httpStatusCode);
    }
    
    protected static void successPage(HttpAction action, String message)
    {
        try {
            action.response.setContentType("text/html");
            action.response.setStatus(HttpSC.OK_200);
            PrintWriter out = action.response.getWriter() ;
            out.println("<html>") ;
            out.println("<head>") ;
            out.println("</head>") ;
            out.println("<body>") ;
            out.println("<h1>Success</h1>");
            if ( message != null )
            {
                out.println("<p>") ;
                out.println(message) ;
                out.println("</p>") ;
            }
            out.println("</body>") ;
            out.println("</html>") ;
            out.flush() ;
        } catch (IOException ex) { errorOccurred(ex) ; }
    }
    
    protected static void warning(String string)
    {
        log.warn(string) ;
    }
    
    protected static void warning(String string, Throwable thorwable)
    {
        log.warn(string, thorwable) ;
    }
    
    protected static void errorBadRequest(String string)
    {
        error(HttpSC.BAD_REQUEST_400, string) ;
    }

    protected static void errorNotFound(String string)
    {
        error(HttpSC.NOT_FOUND_404, string) ;
    }

    protected static void errorNotImplemented(String msg)
    {
        error(HttpSC.NOT_IMPLEMENTED_501, msg) ;
    }
    
    protected static void errorMethodNotAllowed(String method)
    {
        error(HttpSC.METHOD_NOT_ALLOWED_405, "HTTP method not allowed: "+method) ;
    }

    protected static void errorForbidden(String msg)
    {
        if ( msg != null )
            error(HttpSC.FORBIDDEN_403, msg) ;
        else
            error(HttpSC.FORBIDDEN_403, "Forbidden") ;
    }
    
    protected static void error(int statusCode)
    {
        throw new ActionErrorException(null, null, statusCode) ;
    }
    

    protected static void error(int statusCode, String string)
    {
        throw new ActionErrorException(null, string, statusCode) ;
    }
    
    protected static void errorOccurred(String message)
    {
        errorOccurred(message, null) ;
    }

    protected static void errorOccurred(Throwable ex)
    {
        errorOccurred(null, ex) ;
    }

    protected static void errorOccurred(String message, Throwable ex)
    {
        throw new ActionErrorException(ex, message, HttpSC.INTERNAL_SERVER_ERROR_500) ;
    }
    
    protected static String formatForLog(String string)
    {
        string = string.replace('\n', ' ') ;
        string = string.replace('\r', ' ') ;
        return string ; 
    }

    static String varyHeaderSetting = 
            String.join(",", 
                         HttpNames.hAccept, 
                         HttpNames.hAcceptEncoding, 
                         HttpNames.hAcceptCharset ) ;
    
    public static void setVaryHeader(HttpServletResponse httpResponse)
    {
        httpResponse.setHeader(HttpNames.hVary, varyHeaderSetting) ;
    }

    public static void setCommonHeadersForOptions(HttpServletResponse httpResponse)
    {
        httpResponse.setHeader(HttpNames.hAccessControlAllowHeaders, "X-Requested-With, Content-Type, Authorization") ;
        setCommonHeaders(httpResponse) ;
    }
    
    public static void setCommonHeaders(HttpServletResponse httpResponse)
    {
        httpResponse.setHeader(HttpNames.hAccessControlAllowOrigin, "*") ;
        httpResponse.setHeader(HttpNames.hServer, Fuseki.serverHttpName) ;
    }
}
