/*
 * Orika - simpler, better and faster Java bean mapping
 *
 * Copyright (C) 2011-2013 Orika 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 ma.glasnost.orika.impl.generator.specification;

import static java.lang.String.format;
import static ma.glasnost.orika.impl.generator.SourceCodeContext.append;
import static ma.glasnost.orika.impl.generator.SourceCodeContext.statement;

import java.util.Map;

import ma.glasnost.orika.impl.generator.MapEntryRef;
import ma.glasnost.orika.impl.generator.MapEntryRef.EntryPart;
import ma.glasnost.orika.impl.generator.MultiOccurrenceVariableRef;
import ma.glasnost.orika.impl.generator.SourceCodeContext;
import ma.glasnost.orika.impl.generator.VariableRef;
import ma.glasnost.orika.impl.util.StringUtil;
import ma.glasnost.orika.metadata.FieldMap;
import ma.glasnost.orika.metadata.FieldMapBuilder;
import ma.glasnost.orika.metadata.TypeFactory;

/**
 * MapToMap handles conversion of a Map to another Map
 *
 */
public class MapToMap extends AbstractSpecification {

    public boolean appliesTo(FieldMap fieldMap) {
        return fieldMap.getSource().isMap() && fieldMap.getDestination().isMap();
    }

    public String generateMappingCode(FieldMap fieldMap, VariableRef source, VariableRef destination, SourceCodeContext code) {
        
        MultiOccurrenceVariableRef d = MultiOccurrenceVariableRef.from(destination);
        MultiOccurrenceVariableRef s = MultiOccurrenceVariableRef.from(source);
        
        if (code.isDebugEnabled()) {
            code.debugField(fieldMap, "mapping from Map<" + source.type().getNestedType(0) + ", " + 
            source.type().getNestedType(1) + "> to Map<" + destination.type().getNestedType(0) + ", " + 
            destination.type().getNestedType(1) + ">");
        }
        
        StringBuilder out = new StringBuilder();
        
        out.append(s.ifNotNull());
        out.append("{\n");
        
        MultiOccurrenceVariableRef newDest = new MultiOccurrenceVariableRef(destination.type(), "new_" + destination.name());
        if (d.isAssignable()) {
            out.append(statement(newDest.declare(d.newMap())));
        } else {
            out.append(statement(newDest.declare(d)));
            out.append(statement("%s.clear()", newDest));
        }
        
        
        VariableRef newKey = new VariableRef(d.mapKeyType(), "new" + StringUtil.capitalize(d.name()) + "Key");
        VariableRef newVal = new VariableRef(d.mapValueType(), "new" + StringUtil.capitalize(d.name()) + "Val");
        VariableRef entry = new VariableRef(TypeFactory.valueOf(Map.Entry.class), "source" + StringUtil.capitalize(d.name()) + "Entry");
        VariableRef sourceKey = new MapEntryRef(s.mapKeyType(), entry.name(), EntryPart.KEY);
        VariableRef sourceVal = new MapEntryRef(s.mapValueType(), entry.name(), EntryPart.VALUE);
        /*
         * Loop through the individual entries, map key/value and then put
         * them into the destination
         */
        append(out,
                format("for( java.util.Iterator entryIter = %s.entrySet().iterator(); entryIter.hasNext(); ) {\n", s),
                entry.declare("entryIter.next()"),
                newKey.declare(),
                newVal.declare(),
                code.mapFields(FieldMapBuilder.mapKeys(s.mapKeyType(), d.mapKeyType()), sourceKey, newKey),
                code.mapFields(FieldMapBuilder.mapValues(s.mapValueType(), d.mapValueType()), sourceVal, newVal),
                format("%s.put(%s, %s)", newDest, newKey, newVal),
                "\n",
                "}");
        
        if (d.isAssignable()) {
            out.append(statement(d.assign(newDest)));
        }
        
        String mapNull = shouldMapNulls(fieldMap, code) ? format(" else {\n %s;\n}", d.assignIfPossible("null")): "";
        append(out, "}" + mapNull);
        
        return out.toString();
    }
    
}

@@ -721,220 +721,8 @@
 n;%0A%0A
-import static java.lang.String.format;%0Aimport static ma.glasnost.orika.impl.generator.SourceCodeContext.append;%0Aimport static ma.glasnost.orika.impl.generator.SourceCodeContext.statement;%0A%0Aimport java.util.Map;%0A%0A
 impo
@@ -766,24 +766,24 @@
 apEntryRef;%0A
+
 import ma.gl
@@ -1203,16 +1203,228 @@
 ctory;%0A%0A
+import java.util.Map;%0A%0Aimport static java.lang.String.format;%0Aimport static ma.glasnost.orika.impl.generator.SourceCodeContext.append;%0Aimport static ma.glasnost.orika.impl.generator.SourceCodeContext.statement;%0A%0A
 /**%0A * M
@@ -2799,26 +2799,9 @@
   %7D%0A
-        %0A        
 %0A
+
     
@@ -3484,16 +3484,17 @@
      */%0A
+%0A
         
@@ -3538,95 +3538,65 @@
 or( 
-java.util.Iterator entryIter = %25s.entrySet().iterator(); entryIter.hasNext(); ) %7B%5Cn%22, s
+%25s; %25s; ) %7B %5Cn%22, s.declareIterator(), s.iteratorHasNext()
 ),%0A 
@@ -3628,26 +3628,23 @@
 are(
-%22entryIter.nex
+s.nextElemen
 t()
-%22
 ),%0A 

Use different iterator name for nested map mapping