/*
 * Copyright 2015 Ben Manes. All Rights Reserved.
 *
 * 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 com.github.benmanes.caffeine.cache;

import java.util.Set;

import com.squareup.javapoet.CodeBlock;

/**
 * @author ben.manes@gmail.com (Ben Manes)
 */
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public final class LocalCacheSelectorCode {
  private final CodeBlock.Builder block;

  private LocalCacheSelectorCode() {
    block = CodeBlock.builder()
        .addStatement("$T sb = new $T()", StringBuilder.class, StringBuilder.class);
  }

  private LocalCacheSelectorCode keys() {
    block.beginControlFlow("if (builder.isStrongKeys())")
            .addStatement("sb.append('S')")
        .nextControlFlow("else")
            .addStatement("sb.append('W')")
        .endControlFlow();
    return this;
  }

  private LocalCacheSelectorCode values() {
    block.beginControlFlow("if (builder.isStrongValues())")
            .addStatement("sb.append('S')")
        .nextControlFlow("else")
            .addStatement("sb.append('I')")
        .endControlFlow();
    return this;
  }

  private LocalCacheSelectorCode removalListener() {
    block.beginControlFlow("if (builder.removalListener != null)")
            .addStatement("sb.append(\"Li\")")
        .endControlFlow();
    return this;
  }

  private LocalCacheSelectorCode stats() {
    block.beginControlFlow("if (builder.isRecordingStats())")
            .addStatement("sb.append('S')")
        .endControlFlow();
    return this;
  }

  private LocalCacheSelectorCode maximum() {
    block.beginControlFlow("if (builder.evicts())")
            .addStatement("sb.append('M')")
            .beginControlFlow("if (builder.isWeighted())")
                .addStatement("sb.append('W')")
            .nextControlFlow("else")
                .addStatement("sb.append('S')")
            .endControlFlow()
        .endControlFlow();
    return this;
  }

  private LocalCacheSelectorCode expires() {
    block.beginControlFlow("if (builder.expiresAfterAccess() || builder.expiresVariable())")
            .addStatement("sb.append('A')")
        .endControlFlow()
        .beginControlFlow("if (builder.expiresAfterWrite())")
            .addStatement("sb.append('W')")
        .endControlFlow()
        .beginControlFlow("if (builder.refreshes())")
            .addStatement("sb.append('R')")
        .endControlFlow();
    return this;
  }

  private LocalCacheSelectorCode selector(Set<String> classNames) {
    CodeBlock.Builder switchBuilder = CodeBlock.builder();
    switchBuilder.beginControlFlow("switch (sb.toString())");
    for (String className : classNames) {
      switchBuilder.addStatement(
          "case $S: return new $N<>(builder, cacheLoader, async)", className, className);
    }
    switchBuilder.addStatement("default: throw new $T(sb.toString())", IllegalStateException.class);
    switchBuilder.endControlFlow();
    block.add(switchBuilder.build());
    return this;
  }

  private CodeBlock build() {
    return block.build();
  }

  public static CodeBlock get(Set<String> classNames) {
    return new LocalCacheSelectorCode()
        .keys()
        .values()
        .removalListener()
        .stats()
        .maximum()
        .expires()
        .selector(classNames)
        .build();
  }
}

@@ -653,16 +653,54 @@
 cache;%0A%0A
+import java.lang.reflect.Constructor;%0A
 import j
@@ -2998,38 +2998,16 @@
 elector(
-Set%3CString%3E classNames
 ) %7B%0A    
@@ -3024,22 +3024,23 @@
 Builder 
-switch
+reflect
 Builder 
@@ -3066,30 +3066,31 @@
 ();%0A    
-switch
+reflect
 Builder.
 beginCon
@@ -3085,34 +3085,189 @@
 der.
-beginControlFlow(%22switch (
+add(%22try %7B%5Cn%22%0A            + %22  Class%3C?%3E cls = LocalCacheFactory.class.getClassLoader()%5Cn%22%0A            + %22    .loadClass(%5C%22com.github.benmanes.caffeine.cache.LocalCacheFactory$$%5C%22 + 
 sb.t
@@ -3280,318 +3280,410 @@
 g())
-%22);
+;%5Cn%22
 %0A    
-for (String className : classNames) %7B%0A      switchBuilder.addStatement(%0A          %22case $S: return new $N%3C%3E(builder, cacheLoader, async)%22, className, className);%0A    %7D%0A    switchBuilder.addStatement(%22default: throw new $T(sb.toString())%22, IllegalStateException.class);%0A    switchBuilder.end
+        + %22  $T%3C?%3E ctor = cls.getDeclaredConstructor(Caffeine.class, CacheLoader.class, boolean.class);%5Cn%22%0A            + %22  return (BoundedLocalCache%3CK, V%3E) ctor.newInstance(builder, cacheLoader, async);%5Cn%22%0A            + %22%7D catch (ReflectiveOperationException e) %7B%5Cn%22%0A            + %22  throw new IllegalStateException(sb.toString());%5Cn%22%0A            + %22%7D%5Cn%22%0A            + %22%5Cn%22, 
 Con
+s
 tr
-olFlow(
+uctor.class
 );%0A 
@@ -3699,14 +3699,15 @@
 add(
-switch
+reflect
 Buil
@@ -4011,24 +4011,24 @@
  .expires()%0A
+
         .sel
@@ -4033,26 +4033,16 @@
 elector(
-classNames
 )%0A      

Replace switch with reflection in generated code