package me.chyxion.tigon.webmvc;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.net.URLEncoder;
import java.util.Collection;
import lombok.extern.slf4j.Slf4j;
import me.chyxion.tigon.model.Mappable;
import org.springframework.util.ClassUtils;
import org.springframework.http.MediaType;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpHeaders;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.Resource;
import org.apache.commons.lang3.CharEncoding;
import org.springframework.http.ResponseEntity;
import org.springframework.core.MethodParameter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor;

/**
 * @version 0.0.4
 * @since 0.0.1
 * @author Donghuang <br>
 * donghuang@wacai.com <br>
 * Jul 16, 2014 10:03:15 PM
 */
@Slf4j
public class TigonReturnValueHandler
    implements HandlerMethodReturnValueHandler {

    @Value("${tigon.webmvc.jsonview.null-jsonarray-as-empty:true}")
    private boolean writeNullJsonArrayAsEmpty;

    @Autowired
    private JSONViewConfig jsonViewConfig;
    private HttpEntityMethodProcessor httpEntityProcessor =
        new HttpEntityMethodProcessor(Arrays.asList(
            new ResourceHttpMessageConverter()));
    
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return true;
    }
   
    /**
     * {@inheritDoc}
     */
    @Override
    public void handleReturnValue(Object returnValue,
            MethodParameter returnType, 
            ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest) throws Exception {
        log.debug("Return value handler, handle return value [{}], return type [{}].",
            returnValue, returnType);
        if (!handleResource(returnValue, 
                returnType, mavContainer, webRequest)) {
            final JSONViewDataModel model;
            if (returnValue instanceof JSONViewDataModel) {
                log.debug("Controller return value is JSONViewDataModel, return.");
                model = (JSONViewDataModel) returnValue;
            }
            else if (returnValue instanceof Mappable) {
                log.debug("Controller return value is mappable, call #toMap.");
                model = new JSONViewDataModel(((Mappable) returnValue).toMap());
            }
            else {
                final Class<?> returnTypeClass = returnType.getMethod().getReturnType();
                if (writeNullJsonArrayAsEmpty && returnValue == null &&
                    (returnTypeClass.isArray() ||
                        ClassUtils.isAssignable(Collection.class, returnTypeClass))) {
                    log.info("Wrap controller return null collection to data model.");
                    model = new JSONViewDataModel(new Object[0]);
                }
                else {
                    log.debug("Wrap controller return value to data model.");
                    model = new JSONViewDataModel(returnValue);
                }
            }
            mavContainer.setView(new JSONView(model, jsonViewConfig));
        }
    }

    /**
     * handle resource
     * @param returnValue return value
     * @param returnType return type
     * @param mavContainer mvc container
     * @param webRequest web request
     * @return true if handle resource
     * @throws Exception on error caused
     */
    private boolean handleResource(Object returnValue,
        MethodParameter returnType, 
        ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest) throws Exception {

        ResourceModel resourceModel = null;
        HttpEntity<?> entity = null;
        if (returnValue instanceof ResourceModel) {
            log.debug("Found download model return.");
            resourceModel = (ResourceModel) returnValue;
        }
        else if (returnValue instanceof HttpEntity) {
            log.debug("Found http entity return.");
            entity = (HttpEntity<?>) returnValue;
        }
        else if (returnValue instanceof File) {
            log.debug("Found file [{}] return.", returnValue);
            resourceModel = new ResourceModel((File) returnValue, null, null);
        }
         else if (returnValue instanceof Resource) {
             log.debug("Found resource [{}] return.", returnValue);
             resourceModel = new ResourceModel((Resource) returnValue, null, 0, null);
         }
        else if (returnValue instanceof byte[]) {
            log.debug("Found byte array return.");
            resourceModel = new ResourceModel((byte[]) returnValue, null, null);
        }
        // assemble http entity
        if (resourceModel != null) {
            final HttpHeaders headers = new HttpHeaders();
            String name = resourceModel.getName();
            if (StringUtils.isNotBlank(name)) {
                log.info("Found resource name [{}], set Content-Disposition header.", name);
                name = URLEncoder.encode(name, StandardCharsets.UTF_8.name());
                final StringBuilder sbDispo =
                    new StringBuilder("attachment; filename=\"")
                    .append(name)
                    .append("\"; filename*=utf-8''")
                    .append(name);
                headers.set(HttpHeaders.CONTENT_DISPOSITION, 
                    sbDispo.toString());
            }
            headers.setContentType(
                MediaType.valueOf(resourceModel.getContentType()));
            headers.setContentLength(resourceModel.getContentLength());
            entity = new ResponseEntity<>(
                resourceModel.getResource(), headers, HttpStatus.OK);
        }
        boolean handled = false;
        if (entity != null) {
            httpEntityProcessor.handleReturnValue(entity,
                returnType, mavContainer, webRequest);
            handled = true;
        }
        return handled;
    }
}
