/**
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.repository.impexp;

import org.apache.atlas.AtlasErrorCode;
import org.apache.atlas.exception.AtlasBaseException;
import org.apache.atlas.model.instance.AtlasClassification;
import org.apache.atlas.model.instance.AtlasEntity;
import org.apache.atlas.model.instance.AtlasObjectId;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public abstract class ImportTransformer {
    private static final String TRANSFORMER_PARAMETER_SEPARATOR = "~";

    private static final String TRANSFORMER_NAME_ADD                   = "add";
    private static final String TRANSFORMER_NAME_CLEAR_ATTR            = "clearAttrValue";
    private static final String TRANSFORMER_NAME_LOWERCASE             = "lowercase";
    private static final String TRANSFORMER_NAME_UPPERCASE             = "uppercase";
    private static final String TRANSFORMER_NAME_REMOVE_CLASSIFICATION = "removeClassification";
    private static final String TRANSFORMER_NAME_ADD_CLASSIFICATION    = "addClassification";
    private static final String TRANSFORMER_NAME_REPLACE               = "replace";
    private static final String TRANSFORMER_SET_DELETED                = "setDeleted";

    private final String transformType;

    protected ImportTransformer(String transformType) {
        this.transformType = transformType;
    }

    public static ImportTransformer getTransformer(String transformerSpec) throws AtlasBaseException {
        if (transformerSpec.contains(":") && !transformerSpec.contains(TRANSFORMER_PARAMETER_SEPARATOR)) {
            throw new AtlasBaseException(AtlasErrorCode.INVALID_VALUE,
                    "Invalid transformer specification. The parameter separator ':' is no longer supported. Use '~' instead. Transformer specification: " + transformerSpec);
        }

        String[] params = StringUtils.split(transformerSpec, TRANSFORMER_PARAMETER_SEPARATOR);
        String   key    = (params == null || params.length < 1) ? transformerSpec : params[0];

        final ImportTransformer ret;

        if (StringUtils.isEmpty(key)) {
            throw new AtlasBaseException(AtlasErrorCode.INVALID_VALUE, "Error creating ImportTransformer. Invalid transformer-specification: {}.", transformerSpec);
        } else if (key.equals(TRANSFORMER_NAME_REPLACE)) {
            String toFindStr  = (params == null || params.length < 2) ? "" : params[1];
            String replaceStr = (params == null || params.length < 3) ? "" : params[2];

            ret = new Replace(toFindStr, replaceStr);
        } else if (key.equals(TRANSFORMER_NAME_LOWERCASE)) {
            ret = new Lowercase();
        } else if (key.equals(TRANSFORMER_NAME_UPPERCASE)) {
            ret = new Uppercase();
        } else if (key.equals(TRANSFORMER_NAME_REMOVE_CLASSIFICATION)) {
            String name = (params == null || params.length < 1) ? "" : StringUtils.join(params, "~", 1, params.length);
            ret = new RemoveClassification(name);
        } else if (key.equals(TRANSFORMER_NAME_ADD)) {
            String name = (params == null || params.length < 1) ? "" : StringUtils.join(params, "~", 1, params.length);
            ret = new AddValueToAttribute(name);
        } else if (key.equals(TRANSFORMER_NAME_CLEAR_ATTR)) {
            String name = (params == null || params.length < 1) ? "" : StringUtils.join(params, "~", 1, params.length);
            ret = new ClearAttributes(name);
        } else if (key.equals(TRANSFORMER_SET_DELETED)) {
            ret = new SetDeleted();
        } else if (key.equals(TRANSFORMER_NAME_ADD_CLASSIFICATION)) {
            String name = (params == null || params.length < 1) ? "" : params[1];
            ret = new AddClassification(name, (params != null && params.length == 3) ? params[2] : "");
        } else {
            throw new AtlasBaseException(AtlasErrorCode.INVALID_VALUE, "Error creating ImportTransformer. Unknown transformer: {}.", transformerSpec);
        }

        return ret;
    }

    public String getTransformType() {
        return transformType;
    }

    public abstract Object apply(Object o) throws AtlasBaseException;

    static class Replace extends ImportTransformer {
        private final String toFindStr;
        private final String replaceStr;

        public Replace(String toFindStr, String replaceStr) {
            super(TRANSFORMER_NAME_REPLACE);

            this.toFindStr  = toFindStr;
            this.replaceStr = replaceStr;
        }

        public String getToFindStr() {
            return toFindStr;
        }

        public String getReplaceStr() {
            return replaceStr;
        }

        @Override
        public Object apply(Object o) {
            Object ret = o;

            if (o instanceof String) {
                ret = StringUtils.replace((String) o, toFindStr, replaceStr);
            }

            return ret;
        }
    }

    static class Lowercase extends ImportTransformer {
        public Lowercase() {
            super(TRANSFORMER_NAME_LOWERCASE);
        }

        @Override
        public Object apply(Object o) {
            Object ret = o;

            if (o instanceof String) {
                ret = StringUtils.lowerCase((String) o);
            }

            return ret;
        }
    }

    static class Uppercase extends ImportTransformer {
        public Uppercase() {
            super(TRANSFORMER_NAME_UPPERCASE);
        }

        @Override
        public Object apply(Object o) {
            Object ret = o;

            if (o instanceof String) {
                ret = StringUtils.upperCase((String) o);
            }

            return ret;
        }
    }

    static class AddClassification extends ImportTransformer {
        private static final String FILTER_SCOPE_TOP_LEVEL = "topLevel";

        private final String              scope;
        private final String              classificationName;
        private final List<AtlasObjectId> filters;

        public AddClassification(String name, String scope) {
            super(TRANSFORMER_NAME_REMOVE_CLASSIFICATION);

            this.classificationName = name;
            this.scope              = scope;
            filters                 = new ArrayList<>();
        }

        public void addFilter(AtlasObjectId objectId) {
            filters.add(objectId);
        }

        @Override
        public Object apply(Object o) {
            if (!(o instanceof AtlasEntity)) {
                return o;
            }

            AtlasEntity entity = (AtlasEntity) o;
            if (!passThruFilters(entity)) {
                return o;
            }

            if (entity.getClassifications() == null) {
                entity.setClassifications(new ArrayList<>());
            }

            for (AtlasClassification c : entity.getClassifications()) {
                if (c.getTypeName().equals(classificationName)) {
                    return entity;
                }
            }

            entity.getClassifications().add(new AtlasClassification(classificationName));

            return entity;
        }

        @Override
        public String toString() {
            return String.format("%s=%s", "AddClassification", classificationName);
        }

        public String getClassificationName() {
            return classificationName;
        }

        private boolean passThruFilters(AtlasEntity entity) {
            if (StringUtils.isEmpty(scope) || !scope.equals(FILTER_SCOPE_TOP_LEVEL)) {
                return true;
            }

            for (AtlasObjectId filter : filters) {
                if (isMatch(filter, entity)) {
                    return true;
                }
            }

            return false;
        }

        private boolean isMatch(AtlasObjectId objectId, AtlasEntity entity) {
            boolean ret;

            if (StringUtils.isEmpty(objectId.getGuid())) {
                ret = Objects.equals(objectId.getTypeName(), entity.getTypeName());

                if (ret) {
                    for (Map.Entry<String, Object> entry : objectId.getUniqueAttributes().entrySet()) {
                        ret = ret && Objects.equals(entity.getAttribute(entry.getKey()), entry.getValue());

                        if (!ret) {
                            break;
                        }
                    }
                }

                return ret;
            } else {
                return Objects.equals(objectId.getGuid(), entity.getGuid());
            }
        }
    }

    static class RemoveClassification extends ImportTransformer {
        private final String classificationToBeRemoved;

        public RemoveClassification(String name) {
            super(TRANSFORMER_NAME_REMOVE_CLASSIFICATION);

            this.classificationToBeRemoved = name;
        }

        @Override
        public Object apply(Object o) {
            if (!(o instanceof AtlasEntity)) {
                return o;
            }

            AtlasEntity entity = (AtlasEntity) o;

            if (entity.getClassifications().isEmpty()) {
                return o;
            }

            List<AtlasClassification> toRemove = null;

            for (AtlasClassification classification : entity.getClassifications()) {
                if (classification.getTypeName().equals(classificationToBeRemoved)) {
                    if (toRemove == null) {
                        toRemove = new ArrayList<>();
                    }

                    toRemove.add(classification);
                }
            }

            if (toRemove != null) {
                entity.getClassifications().removeAll(toRemove);
            }

            return entity;
        }

        @Override
        public String toString() {
            return String.format("%s=%s", "RemoveClassification", classificationToBeRemoved);
        }
    }

    static class AddValueToAttribute extends ImportTransformer {
        private final String nameValuePair;
        private       String attrName;
        private       String attrValueRaw;
        private       Object attrValue;

        protected AddValueToAttribute(String nameValuePair) {
            super(TRANSFORMER_NAME_ADD);

            this.nameValuePair = nameValuePair;

            setAttrNameValue(this.nameValuePair);
        }

        @Override
        public Object apply(Object o) {
            if (o == null) {
                return o;
            }

            if (!(o instanceof AtlasEntity)) {
                return o;
            }

            AtlasEntity entity            = (AtlasEntity) o;
            Object      attrExistingValue = entity.getAttribute(attrName);

            if (attrExistingValue == null) {
                entity.setAttribute(attrName, attrValue);
            } else if (attrExistingValue instanceof List) {
                List list = (List<?>) attrExistingValue;

                if (attrValue instanceof List) {
                    list.addAll((List<?>) attrValue);
                } else {
                    list.add(attrValue);
                }
            } else {
                entity.setAttribute(attrName, attrValueRaw);
            }

            return entity;
        }

        private void setAttrNameValue(String nameValuePair) {
            String separatorEquals = "=";

            if (!nameValuePair.contains(separatorEquals)) {
                return;
            }

            String[] splits = StringUtils.split(nameValuePair, separatorEquals);

            if (splits.length == 0) {
                return;
            }

            attrName = splits[0];

            if (splits.length >= 2) {
                attrValueRaw = splits[1];
            }

            setAttrValue(attrValueRaw);
        }

        private void setAttrValue(String attrValueRaw) {
            final String typePrefix = "list:";

            if (attrValueRaw.startsWith(typePrefix)) {
                final String item = StringUtils.remove(attrValueRaw, typePrefix);

                attrValue = new ArrayList<>(Collections.singletonList(item));
            } else {
                attrValue = attrValueRaw;
            }
        }
    }

    static class ClearAttributes extends ImportTransformer {
        private final String[] attrNames;

        protected ClearAttributes(String attrNames) {
            super(TRANSFORMER_NAME_CLEAR_ATTR);

            this.attrNames = StringUtils.split(attrNames, ",");
        }

        @Override
        public Object apply(Object o) {
            if (o == null) {
                return o;
            }

            if (!(o instanceof AtlasEntity)) {
                return o;
            }

            AtlasEntity entity = (AtlasEntity) o;

            for (String attrName : attrNames) {
                entity.setAttribute(attrName, null);
            }

            return entity;
        }
    }

    static class SetDeleted extends ImportTransformer {
        protected SetDeleted() {
            super(TRANSFORMER_SET_DELETED);
        }

        @Override
        public Object apply(Object o) {
            if (o == null) {
                return o;
            }

            if (!(o instanceof AtlasEntity)) {
                return o;
            }

            AtlasEntity entity = (AtlasEntity) o;

            entity.setStatus(AtlasEntity.Status.DELETED);

            return entity;
        }
    }
}
