/**
 * Copyright (C) 2012-2014 the original author or authors.
 *
 * Licensed 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 ninja;

import java.io.InputStream;
import java.util.Properties;

import javax.management.RuntimeErrorException;

import ninja.exceptions.BadRequestException;
import ninja.i18n.Messages;
import ninja.lifecycle.LifecycleService;
import ninja.utils.Message;
import ninja.utils.NinjaConstant;
import ninja.utils.ResultHandler;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Optional;
import com.google.inject.Inject;


@SuppressWarnings({ "unchecked", "rawtypes" })
public class NinjaDefault implements Ninja {
    
    private static final Logger logger = LoggerFactory.getLogger(NinjaDefault.class);

    /**
     * The most important thing: A cool logo.
     */
    private final String NINJA_LOGO = "\n"
            + " _______  .___ _______        ____.  _____   \n"
            + " \\      \\ |   |\\      \\      |    | /  _  \\  \n"
            + " /   |   \\|   |/   |   \\     |    |/  /_\\  \\ \n"
            + "/    |    \\   /    |    \\/\\__|    /    |    \\  http://www.ninjaframework.org\n"
            + "\\____|__  /___\\____|__  /\\________\\____|__  /  @ninjaframework\n"
            + "     web\\/framework   \\/                  \\/   {}\n";
    

    @Inject
    protected LifecycleService lifecycleService;
    
    @Inject
    protected Router router;
    
    @Inject
    protected ResultHandler resultHandler;
    
    @Inject
    Messages messages;


    @Override
    public void onRouteRequest(Context.Impl context) {
        
        String httpMethod = context.getMethod();

        Route route = router.getRouteFor(httpMethod, context.getRequestPath());

        context.setRoute(route);

        if (route != null) {

            try {
                
                Result result = route.getFilterChain().next(context);

                resultHandler.handleResult(result, context);
                
            } catch (Exception exception) {
                
                Result result = onException(context, exception);
                renderErrorResultAndCatchAndLogExceptions(result, context);
                            
            }

        } else {
            // throw a 404 "not found" because we did not find the route
            Result result = getNotFoundResult(context);
            renderErrorResultAndCatchAndLogExceptions(result, context);

        }
    

        
    }
    
    @Override
    public void renderErrorResultAndCatchAndLogExceptions(
            Result result, Context context) {
    
        try {
            if (context.isAsync()) {
                context.returnResultAsync(result);
            } else {
                resultHandler.handleResult(result, context);
            }
        } catch (Exception exceptionCausingRenderError) {
            logger.error("Unable to handle result. "
                    + "That's really realy fishy. ",
                    exceptionCausingRenderError);
        }
    }
    
    @Override
    public void onFrameworkStart() {

        showSplashScreenViaLogger();
                
        lifecycleService.start();
    }

    @Override
    public void onFrameworkShutdown() {
        lifecycleService.stop();
    }
    
    ////////////////////////////////////////////////////////////////////////////
    // Results for exceptions (404, 500 etc)
    ////////////////////////////////////////////////////////////////////////////
    @Override
    public Result onException(Context context, Exception exception) {
        
        Result result;
        
        if (exception instanceof BadRequestException) {
            
            result = getBadRequestResult(context, exception);
        
        } else {
            
            result = getInternalServerErrorResult(context, exception);

        }
        
        return result;
        
    }
    
    @Override
    public Result getInternalServerErrorResult(Context context, Exception exception) {
            
        logger.error(
                "Emitting bad request 500. Something really wrong when calling route: {} (class: {} method: {})",
                context.getRequestPath(), 
                context.getRoute().getControllerClass(), 
                context.getRoute().getControllerMethod(), 
                exception);
        
        String messageI18n 
                = messages.getWithDefault(
                        NinjaConstant.I18N_NINJA_SYSTEM_INTERNAL_SERVER_ERROR_TEXT_KEY,
                        NinjaConstant.I18N_NINJA_SYSTEM_INTERNAL_SERVER_ERROR_TEXT_DEFAULT,
                        context,
                        Optional.<Result>absent());
        
        Message message = new Message(messageI18n);

        Result result = Results
                .internalServerError()
                .supportedContentTypes(Result.TEXT_HTML, Result.APPLICATON_JSON, Result.APPLICATION_XML)
                .fallbackContentType(Result.TEXT_HTML)
                .render(message)
                .template(NinjaConstant.LOCATION_VIEW_FTL_HTML_INTERNAL_SERVER_ERROR);
        

        return result;
    }
    
    @Override
    public Result getNotFoundResult(Context context) {
            
        String messageI18n
                = messages.getWithDefault(
                        NinjaConstant.I18N_NINJA_SYSTEM_NOT_FOUND_TEXT_KEY,
                        NinjaConstant.I18N_NINJA_SYSTEM_NOT_FOUND_TEXT_DEFAULT,
                        context,
                        Optional.<Result>absent());
        
        Message message = new Message(messageI18n); 
        
        Result result = Results
                        .notFound()
                        .supportedContentTypes(Result.TEXT_HTML, Result.APPLICATON_JSON, Result.APPLICATION_XML)
                        .fallbackContentType(Result.TEXT_HTML)
                        .render(message)
                        .template(NinjaConstant.LOCATION_VIEW_FTL_HTML_NOT_FOUND);
          
        return result;

    }
    
    @Override
    public Result getBadRequestResult(Context context, Exception exception) {
        
        String messageI18n 
                = messages.getWithDefault(
                        NinjaConstant.I18N_NINJA_SYSTEM_BAD_REQUEST_TEXT_KEY,
                        NinjaConstant.I18N_NINJA_SYSTEM_BAD_REQUEST_TEXT_DEFAULT,
                        context,
                        Optional.<Result>absent());
        
        Message message = new Message(messageI18n); 
           
        Result result = Results
                        .badRequest()
                        .supportedContentTypes(Result.TEXT_HTML, Result.APPLICATON_JSON, Result.APPLICATION_XML)
                        .fallbackContentType(Result.TEXT_HTML)
                        .render(message)
                        .template(NinjaConstant.LOCATION_VIEW_FTL_HTML_BAD_REQUEST);
        
        return result;

    }
    
    @Override
    public Result getUnauthorizedResult(Context context) {

        String messageI18n
                = messages.getWithDefault(
                        NinjaConstant.I18N_NINJA_SYSTEM_UNAUTHORIZED_REQUEST_TEXT_KEY,
                        NinjaConstant.I18N_NINJA_SYSTEM_UNAUTHORIZED_REQUEST_TEXT_DEFAULT,
                        context,
                        Optional.<Result>absent());

        Message message = new Message(messageI18n);

        // WWW-Authenticate must be included per the spec
        // http://www.ietf.org/rfc/rfc2617.txt 3.2.1 The WWW-Authenticate Response Header
        Result result = Results
                        .unauthorized()
                        .addHeader(Result.WWW_AUTHENTICATE, "None")
                        .supportedContentTypes(Result.TEXT_HTML, Result.APPLICATON_JSON, Result.APPLICATION_XML)
                        .fallbackContentType(Result.TEXT_HTML)
                        .render(message)
                        .template(NinjaConstant.LOCATION_VIEW_FTL_HTML_UNAUTHORIZED);

        return result;

    }

    @Override
    public Result getForbiddenResult(Context context) {
        
        String messageI18n 
                = messages.getWithDefault(
                        NinjaConstant.I18N_NINJA_SYSTEM_FORBIDDEN_REQUEST_TEXT_KEY,
                        NinjaConstant.I18N_NINJA_SYSTEM_FORBIDDEN_REQUEST_TEXT_DEFAULT,
                        context,
                        Optional.<Result>absent());
        
        Message message = new Message(messageI18n); 
           
        Result result = Results
                        .forbidden()
                        .supportedContentTypes(Result.TEXT_HTML, Result.APPLICATON_JSON, Result.APPLICATION_XML)
                        .fallbackContentType(Result.TEXT_HTML)
                        .render(message)
                        .template(NinjaConstant.LOCATION_VIEW_FTL_HTML_FORBIDDEN);
        
        return result;

    }
    
    /**
     * Simply reads a property resource file that contains the version of this
     * Ninja build. Helps to identify the Ninja version currently running.
     * 
     * @return The version of Ninja. Eg. "1.6-SNAPSHOT" while developing of "1.6" when released.
     */
    private final String readNinjaVersion() {
        
        // location of the properties file
        String LOCATION_OF_NINJA_BUILTIN_PROPERTIES = "ninja/ninja-builtin.properties";
        // and the key inside the properties file.
        String NINJA_VERSION_PROPERTY_KEY = "ninja.version";
        
        String ninjaVersion;
         
        try {

            Properties prop = new Properties();
            InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(LOCATION_OF_NINJA_BUILTIN_PROPERTIES);
            prop.load(stream);
            
            ninjaVersion = prop.getProperty(NINJA_VERSION_PROPERTY_KEY);
        
        } catch (Exception e) {
            //this should not happen. Never.
            throw new RuntimeErrorException(new Error("Something is wrong with your build. Cannot find resource " + LOCATION_OF_NINJA_BUILTIN_PROPERTIES));
        }
        
        return ninjaVersion;
        
    }
    
    private final void showSplashScreenViaLogger() {
        
        String ninjaVersion = readNinjaVersion();
        
        // log Ninja splash screen
        logger.info(NINJA_LOGO, ninjaVersion);
        
    }

}
