/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Neofonie GmbH
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package de.neofonie.surlgen.processor.spring;

import com.helger.jcodemodel.*;
import de.neofonie.surlgen.processor.core.AbstractGenerator;
import de.neofonie.surlgen.processor.core.ClassWriter;
import de.neofonie.surlgen.processor.core.Options;
import de.neofonie.surlgen.processor.core.UrlMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import org.springframework.web.util.UriComponentsBuilder;

import javax.lang.model.element.ExecutableElement;
import java.util.HashMap;
import java.util.Map;

public class UrlFactoryServiceGenerator extends AbstractGenerator {

    private static final Map<String, FactoryModel> map = new HashMap<>();

    @Override
    protected void handleElement(ExecutableElement elem) {
        FactoryModel factoryModel = map
                .computeIfAbsent(elem.getEnclosingElement().toString(), key -> new FactoryModel(key));
        factoryModel.appendMethod(elem);
    }

    private static class FactoryModel {

        private final String name;
        private final JDefinedClass definedClass;
        private final JMethod baseMvcUriComponentsMethod;

        private FactoryModel(String name) {
            this.name = name;
            try {
                definedClass = ClassWriter.createClass(name + Options.getValue(Options.OptionEnum.ServiceClassName));
                definedClass.annotate(Service.class);
                definedClass.javadoc().add("Generated with " + UrlFactoryServiceGenerator.class.getCanonicalName());
                baseMvcUriComponentsMethod = appendBaseMvcUriComponentsBuilderMethod();
            } catch (JClassAlreadyExistsException e) {
                throw new IllegalStateException(e);
            }
        }

        private JMethod appendBaseMvcUriComponentsBuilderMethod() {
            JMethod getBaseMvcUriComponentsBuilder = definedClass.method(JMod.PROTECTED, UriComponentsBuilder.class, "getBaseUriComponentsBuilder");
            getBaseMvcUriComponentsBuilder.body()._return(JExpr._null());
            getBaseMvcUriComponentsBuilder.javadoc().add("Extension-Point to use another Base-UriComponentsBuilder than the default one");
            return getBaseMvcUriComponentsBuilder;
        }

        void appendMethod(ExecutableElement method) {
            UrlMethod parameters = new UrlMethod(method);

            JMethod mvcUriComponentsBuilderMethod = appendMvcUriComponentsBuilderMethod(parameters.getMethodName(), parameters);
            appendUriStringMethod(parameters.getMethodName(), parameters, mvcUriComponentsBuilderMethod);
        }

        private void appendUriStringMethod(String methodName, UrlMethod parameters, JMethod mvcUriComponentsBuilderMethod) {
            JMethod uriStringMethod = definedClass.method(JMod.PUBLIC, String.class, methodName + "UriString");

            JInvocation invocation = JExpr.invoke(mvcUriComponentsBuilderMethod);
            uriStringMethod.body()._return(invocation.invoke("toUriString"));
            parameters.appendParams(uriStringMethod, invocation);
        }

        private JMethod appendMvcUriComponentsBuilderMethod(String methodName, UrlMethod parameters) {
            JMethod urlMethod = definedClass.method(JMod.PUBLIC, UriComponentsBuilder.class, methodName);
            JBlock body = urlMethod.body();
            JVar uriComponentsBuilder = body.decl(ClassWriter.ref(UriComponentsBuilder.class), "uriComponentsBuilder")
                    .init(createMvcUriComponentsBuilderInvocation(parameters, methodName, urlMethod));
            parameters.handleUriComponentsBuilderPostRequest(urlMethod, uriComponentsBuilder);
            body._return(uriComponentsBuilder);
            return urlMethod;
        }

        private JInvocation createMvcUriComponentsBuilderInvocation(UrlMethod parameters, String methodName, JMethod urlMethod) {
            AbstractJClass mvcUriComponentsBuilder = ClassWriter.ref(MvcUriComponentsBuilder.class);
            JInvocation fromMethodName = mvcUriComponentsBuilder.staticInvoke("fromMethodName");
            fromMethodName.arg(JExpr.invoke(baseMvcUriComponentsMethod));
            fromMethodName.arg(ClassWriter.ref(name).dotclass());
            fromMethodName.arg(methodName);
            fromMethodName.arg(parameters.createVarArgArray(urlMethod));
            return fromMethodName;
        }
    }
}

@@ -2989,24 +2989,193 @@
 rMethod() %7B%0A
+            try %7B%0A                MvcUriComponentsBuilder.class.getMethod(%22fromMethodName%22, UriComponentsBuilder.class, Class.class, String.class, Object%5B%5D.class);%0A%0A    
             
@@ -3315,32 +3315,36 @@
 %22);%0A            
+    
 getBaseMvcUriCom
@@ -3393,32 +3393,36 @@
 ));%0A            
+   
+ 
 getBaseMvcUriCom
@@ -3540,24 +3540,28 @@
             
+    
 return getBa
@@ -3587,16 +3587,130 @@
 uilder;%0A
+            %7D catch (NoSuchMethodException e) %7B%0A// prior to Spring 4.2%0A                return null;%0A            %7D%0A
         
@@ -5529,24 +5529,82 @@
 thodName%22);%0A
+            if (baseMvcUriComponentsMethod != null) %7B%0A    
             
@@ -5657,32 +5657,46 @@
 onentsMethod));%0A
+            %7D%0A
             from

fix Spring <4.2.0 Error