/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.atlas.type;


import org.apache.atlas.exception.AtlasBaseException;
import org.apache.atlas.model.typedef.AtlasBaseTypeDef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;


/**
 * class that implements behaviour of a map-type.
 */
public class AtlasMapType extends AtlasType {
    private static final Logger LOG = LoggerFactory.getLogger(AtlasEntityType.class);

    private final String keyTypeName;
    private final String valueTypeName;

    private AtlasType keyType;
    private AtlasType valueType;

    public AtlasMapType(String keyTypeName, String valueTypeName) {
        super(AtlasBaseTypeDef.getMapTypeName(keyTypeName, valueTypeName));

        this.keyTypeName   = keyTypeName;
        this.valueTypeName = valueTypeName;
    }

    public AtlasMapType(AtlasType keyType, AtlasType valueType) {
        super(AtlasBaseTypeDef.getMapTypeName(keyType.getTypeName(), valueType.getTypeName()));

        this.keyTypeName   = keyType.getTypeName();
        this.valueTypeName = valueType.getTypeName();
        this.keyType       = keyType;
        this.valueType     = valueType;
    }

    public AtlasMapType(String keyTypeName, String valueTypeName, AtlasTypeRegistry typeRegistry)
        throws AtlasBaseException {
        super(AtlasBaseTypeDef.getMapTypeName(keyTypeName, valueTypeName));

        this.keyTypeName   = keyTypeName;
        this.valueTypeName = valueTypeName;

        resolveReferences(typeRegistry);
    }

    @Override
    public void resolveReferences(AtlasTypeRegistry typeRegistry) throws AtlasBaseException {
        this.keyType   = typeRegistry.getType(keyTypeName);
        this.valueType = typeRegistry.getType(valueTypeName);

        if (keyType == null) {
            String msg = keyTypeName + ": unknown key-type for map";

            LOG.error(msg);

            throw new AtlasBaseException(msg);
        }

        if (valueType == null) {
            String msg = valueTypeName + ": unknown value-type for map";

            LOG.error(msg);

            throw new AtlasBaseException(msg);
        }

        keyType.resolveReferences(typeRegistry);
        valueType.resolveReferences(typeRegistry);
    }

    @Override
    public Map<Object, Object>  createDefaultValue() {
        Map<Object, Object> ret = new HashMap<Object, Object>();

        ret.put(keyType.createDefaultValue(), valueType.createDefaultValue());

        return ret;
    }

    @Override
    public boolean isValidValue(Object obj) {
        if (obj != null) {
            if (obj instanceof Map) {
                Map<Object, Objects> map = (Map<Object, Objects>) obj;

                for (Map.Entry e : map.entrySet()) {
                    if (!keyType.isValidValue(e.getKey()) || !valueType.isValidValue(e.getValue())) {
                        return false; // invalid key/value
                    }
                }
            } else {
                return false; // invalid type
            }
        }

        return true;
    }

    @Override
    public Map<Object, Object> getNormalizedValue(Object obj) {
        if (obj == null) {
            return null;
        }

        if (obj instanceof Map) {
            Map<Object, Object> ret = new HashMap<Object, Object>();

            Map<Object, Objects> map = (Map<Object, Objects>) obj;

            for (Map.Entry e : map.entrySet()) {
                Object normalizedKey = keyType.getNormalizedValue(e.getKey());

                if (normalizedKey != null) {
                    Object value = e.getValue();

                    if (value != null) {
                        Object normalizedValue = valueType.getNormalizedValue(e.getValue());

                        if (normalizedValue != null) {
                            ret.put(normalizedKey, normalizedValue);
                        } else {
                            return null; // invalid value
                        }
                    } else {
                        ret.put(normalizedKey, value);
                    }
                } else {
                    return null; // invalid key
                }
            }

            return ret;
        }

        return null;
    }

    @Override
    public boolean validateValue(Object obj, String objName, List<String> messages) {
        boolean ret = true;

        if (obj != null) {
            if (obj instanceof Map) {
                Map<Object, Objects> map = (Map<Object, Objects>) obj;

                for (Map.Entry e : map.entrySet()) {
                    Object key = e.getKey();

                    if (!keyType.isValidValue(key)) {
                        ret = false;

                        messages.add(objName + "." + key + ": invalid key for type " + getTypeName());
                    } else {
                        Object value = e.getValue();

                        ret = valueType.validateValue(value, objName + "." + key, messages) && ret;
                    }
                }
            } else {
                ret = false;

                messages.add(objName + "=" + obj + ": invalid value for type " + getTypeName());
            }
        }

        return ret;
    }
}
