/**
 * Copyright 2011-2015 John Ericksen
 *
 * 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 org.parceler.internal.generator;

import com.google.common.collect.UnmodifiableIterator;
import com.sun.codemodel.*;
import org.androidtransfuse.adapter.ASTType;
import org.androidtransfuse.adapter.classes.ASTClassFactory;
import org.androidtransfuse.gen.ClassGenerationUtil;
import org.androidtransfuse.gen.UniqueVariableNamer;
import org.parceler.MapsUtil;
import org.parceler.internal.Generators;

import javax.inject.Inject;
import java.util.Map;

/**
* @author John Ericksen
*/
public class MapReadWriteGenerator extends ReadWriteGeneratorBase {

    private final ClassGenerationUtil generationUtil;
    private final UniqueVariableNamer namer;
    private final Generators generators;
    private final ASTClassFactory astClassFactory;
    private final JCodeModel codeModel;
    private final ASTType mapType;
    private final boolean mapInitialCapacityArgument;
    private final boolean initialCapacityLoadFactor;

    @Inject
    public MapReadWriteGenerator(ClassGenerationUtil generationUtil, UniqueVariableNamer namer, Generators generators, ASTClassFactory astClassFactory, JCodeModel codeModel, Class<? extends Map> mapType, boolean mapInitialCapacityArgument, boolean initialCapacityLoadFactor) {
        this(generationUtil, namer, generators, astClassFactory, codeModel, astClassFactory.getType(mapType), mapInitialCapacityArgument, initialCapacityLoadFactor);
    }

    public MapReadWriteGenerator(ClassGenerationUtil generationUtil, UniqueVariableNamer namer, Generators generators, ASTClassFactory astClassFactory, JCodeModel codeModel, ASTType mapType, boolean mapInitialCapacityArgument, boolean initialCapacityLoadFactor) {
        super("readHashMap", new Class[]{ClassLoader.class}, "writeMap", new Class[]{Map.class});
        this.generationUtil = generationUtil;
        this.generators = generators;
        this.namer = namer;
        this.astClassFactory = astClassFactory;
        this.codeModel = codeModel;
        this.mapType = mapType;
        this.mapInitialCapacityArgument = mapInitialCapacityArgument;
        this.initialCapacityLoadFactor = initialCapacityLoadFactor;
    }

    @Override
    public JExpression generateReader(JBlock body, JVar parcelParam, ASTType type, JClass returnJClassRef, JDefinedClass parcelableClass, JVar identity, JVar readIdentityMap) {

        JClass mapImplType = generationUtil.ref(mapType);

        ASTType keyComponentType = astClassFactory.getType(Object.class);
        ASTType valueComponentType = astClassFactory.getType(Object.class);
        JClass keyType = generationUtil.ref(Object.class);
        JClass valueType = generationUtil.ref(Object.class);

        if(type.getGenericArgumentTypes().size() == 2){
            UnmodifiableIterator<ASTType> iterator = type.getGenericArgumentTypes().iterator();
            keyComponentType = iterator.next();
            valueComponentType = iterator.next();
            keyType = generationUtil.narrowRef(keyComponentType);
            valueType = generationUtil.narrowRef(valueComponentType);
            mapImplType = mapImplType.narrow(keyType, valueType);
        }

        JVar sizeVar = body.decl(codeModel.INT, namer.generateName(codeModel.INT), parcelParam.invoke("readInt"));

        JVar outputVar = body.decl(mapImplType, namer.generateName(Map.class));

        JConditional nullInputConditional = body._if(sizeVar.lt(JExpr.lit(0)));

        JBlock nullBody = nullInputConditional._then();

        nullBody.assign(outputVar, JExpr._null());

        JBlock nonNullBody = nullInputConditional._else();

        JInvocation mapConstruction = JExpr._new(mapImplType);
        if(mapInitialCapacityArgument) {
            JExpression initialCapacityExpression;
            if(initialCapacityLoadFactor) {
                initialCapacityExpression = generationUtil.ref(MapsUtil.class).staticInvoke(MapsUtil.INITIAL_HASH_MAP_CAPACITY_METHOD).arg(sizeVar);
            }
            else{
                initialCapacityExpression = sizeVar;
            }
            mapConstruction = mapConstruction.arg(initialCapacityExpression);
        }

        nonNullBody.assign(outputVar, mapConstruction);

        JForLoop forLoop = nonNullBody._for();
        JVar nVar = forLoop.init(codeModel.INT, namer.generateName(codeModel.INT), JExpr.lit(0));
        forLoop.test(nVar.lt(sizeVar));
        forLoop.update(nVar.incr());
        JBlock readLoopBody = forLoop.body();

        ReadWriteGenerator keyGenerator = generators.getGenerator(keyComponentType);
        ReadWriteGenerator valueGenerator = generators.getGenerator(valueComponentType);

        JExpression readKeyExpression = keyGenerator.generateReader(readLoopBody, parcelParam, keyComponentType, generationUtil.ref(keyComponentType), parcelableClass, identity, readIdentityMap);
        JVar keyVar = readLoopBody.decl(keyType, namer.generateName(keyComponentType), readKeyExpression);

        JExpression readValueExpression = valueGenerator.generateReader(readLoopBody, parcelParam, valueComponentType, generationUtil.ref(valueComponentType), parcelableClass, identity, readIdentityMap);
        JVar valueVar = readLoopBody.decl(valueType, namer.generateName(valueComponentType), readValueExpression);

        readLoopBody.invoke(outputVar, "put").arg(keyVar).arg(valueVar);

        return outputVar;
    }

    @Override
    public void generateWriter(JBlock body, JExpression parcel, JVar flags, ASTType type, JExpression getExpression, JDefinedClass parcelableClass, JVar writeIdentitySet) {

        ASTType keyComponentType = astClassFactory.getType(Object.class);
        ASTType valueComponentType = astClassFactory.getType(Object.class);

        if(type.getGenericArgumentTypes().size() == 2){
            UnmodifiableIterator<ASTType> iterator = type.getGenericArgumentTypes().iterator();
            keyComponentType = iterator.next();
            valueComponentType = iterator.next();
        }
        JClass keyType = generationUtil.narrowRef(keyComponentType);
        JClass valueType = generationUtil.narrowRef(valueComponentType);

        JClass inputType = generationUtil.ref(Map.Entry.class).narrow(keyType, valueType);


        JConditional nullConditional = body._if(getExpression.eq(JExpr._null()));
        nullConditional._then().invoke(parcel, "writeInt").arg(JExpr.lit(-1));

        JBlock writeBody = nullConditional._else();

        writeBody.invoke(parcel, "writeInt").arg(getExpression.invoke("size"));

        JForEach forEach = writeBody.forEach(inputType, namer.generateName(inputType), getExpression.invoke("entrySet"));

        ReadWriteGenerator keyGenerator = generators.getGenerator(keyComponentType);
        ReadWriteGenerator valueGenerator = generators.getGenerator(valueComponentType);

        keyGenerator.generateWriter(forEach.body(), parcel, flags, keyComponentType, forEach.var().invoke("getKey"), parcelableClass, writeIdentitySet);
        valueGenerator.generateWriter(forEach.body(), parcel, flags, valueComponentType, forEach.var().invoke("getValue"), parcelableClass, writeIdentitySet);
    }
}