/*
 * Copyright 2019 Scott Logic Ltd
 *
 * 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.scottlogic.datahelix.generator.profile.factories.constraint_factories;

import com.scottlogic.datahelix.generator.common.ValidationException;
import com.scottlogic.datahelix.generator.common.profile.Field;
import com.scottlogic.datahelix.generator.common.profile.Fields;
import com.scottlogic.datahelix.generator.common.profile.InSetRecord;
import com.scottlogic.datahelix.generator.core.fieldspecs.relations.InMapRelation;
import com.scottlogic.datahelix.generator.core.profile.constraints.atomic.*;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.atomic.*;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.atomic.integer.LongerThanConstraintDTO;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.atomic.integer.OfLengthConstraintDTO;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.atomic.integer.ShorterThanConstraintDTO;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.atomic.numeric.GreaterThanConstraintDTO;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.atomic.numeric.GreaterThanOrEqualToConstraintDTO;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.atomic.numeric.LessThanConstraintDTO;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.atomic.numeric.LessThanOrEqualToConstraintDTO;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.atomic.temporal.AfterConstraintDTO;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.atomic.temporal.AfterOrAtConstraintDTO;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.atomic.temporal.BeforeConstraintDTO;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.atomic.temporal.BeforeOrAtConstraintDTO;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.atomic.textual.ContainsRegexConstraintDTO;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.atomic.textual.MatchesRegexConstraintDTO;
import com.scottlogic.datahelix.generator.profile.dtos.constraints.relations.InMapConstraintDTO;

import java.util.List;
import java.util.stream.Collectors;

public abstract class AtomicConstraintFactory {
    public AtomicConstraint createAtomicConstraint(AtomicConstraintDTO dto, Fields fields) {
        Field field = fields.getByName(dto.field);
        switch (dto.getType()) {
            case EQUAL_TO:
                return new EqualToConstraint(field, parseValue(((EqualToConstraintDTO)dto).value));
            case IN_SET:
                return createInSetConstraint((InSetConstraintDTO) dto, field);
            case MATCHES_REGEX:
                return createMatchesRegexConstraint((MatchesRegexConstraintDTO) dto, field);
            case CONTAINS_REGEX:
                return createContainsRegexConstraint((ContainsRegexConstraintDTO) dto, field);
            case OF_LENGTH:
                return createOfLengthConstraint((OfLengthConstraintDTO) dto, field);
            case SHORTER_THAN:
                return createShorterThanConstraint((ShorterThanConstraintDTO) dto, field);
            case LONGER_THAN:
                return createLongerThanConstraint((LongerThanConstraintDTO) dto, field);
            case GREATER_THAN:
                return createGreaterThanConstraint((GreaterThanConstraintDTO) dto, field);
            case GREATER_THAN_OR_EQUAL_TO:
                return createGreaterThanOrEqualToConstraint((GreaterThanOrEqualToConstraintDTO) dto, field);
            case LESS_THAN:
                return createLessThanConstraint((LessThanConstraintDTO) dto, field);
            case LESS_THAN_OR_EQUAL_TO:
                return createLessThanOrEqualToConstraint((LessThanOrEqualToConstraintDTO) dto, field);
            case AFTER:
                return createAfterConstraint((AfterConstraintDTO) dto, field);
            case AFTER_OR_AT:
                return createAfterOrAtConstraint((AfterOrAtConstraintDTO) dto, field);
            case BEFORE:
                return createBeforeConstraint((BeforeConstraintDTO) dto, field);
            case BEFORE_OR_AT:
                return createBeforeOrAtConstraint((BeforeOrAtConstraintDTO) dto, field);
            case GRANULAR_TO:
                return createGranularToConstraint((GranularToConstraintDTO) dto, field);
            case IS_NULL:
                return createIsNullConstraint((IsNullConstraintDTO) dto, fields);
            default:
                throw new ValidationException("Atomic constraint type not found: " + dto);
        }
    }

    public InMapRelation createInMapRelation(InMapConstraintDTO dto, Fields fields)
    {
        Field main = fields.getByName(dto.field);
        Field other = fields.getByName(dto.otherField);
        List<Object> values = dto.values.stream().map(this::parseValue).collect(Collectors.toList());
        return new InMapRelation(main, other, values);
    }

    abstract Object parseValue(Object value);
    abstract MatchesRegexConstraint createMatchesRegexConstraint(MatchesRegexConstraintDTO dto, Field field);
    abstract ContainsRegexConstraint createContainsRegexConstraint(ContainsRegexConstraintDTO dto, Field field);
    abstract OfLengthConstraint createOfLengthConstraint(OfLengthConstraintDTO dto, Field field);
    abstract ShorterThanConstraint createShorterThanConstraint(ShorterThanConstraintDTO dto, Field field);
    abstract LongerThanConstraint createLongerThanConstraint(LongerThanConstraintDTO dto, Field field);
    abstract GreaterThanConstraint createGreaterThanConstraint(GreaterThanConstraintDTO dto, Field field);
    abstract GreaterThanOrEqualToConstraint createGreaterThanOrEqualToConstraint(GreaterThanOrEqualToConstraintDTO dto, Field field);
    abstract LessThanConstraint createLessThanConstraint(LessThanConstraintDTO dto, Field field);
    abstract LessThanOrEqualToConstraint createLessThanOrEqualToConstraint(LessThanOrEqualToConstraintDTO dto, Field field);
    abstract AtomicConstraint createAfterConstraint(AfterConstraintDTO dto, Field field);
    abstract AtomicConstraint createAfterOrAtConstraint(AfterOrAtConstraintDTO dto, Field field);
    abstract AtomicConstraint createBeforeConstraint(BeforeConstraintDTO dto, Field field);
    abstract AtomicConstraint createBeforeOrAtConstraint(BeforeOrAtConstraintDTO dto, Field field);
    abstract AtomicConstraint createGranularToConstraint(GranularToConstraintDTO dto, Field field);

    private InSetConstraint createInSetConstraint(InSetConstraintDTO dto, Field field)
    {
        final List<InSetRecord> values = dto.values.stream()
            .map(value -> value.mapValue(this::parseValue))
            .collect(Collectors.toList());

        return new InSetConstraint(field, values);
    }

    private AtomicConstraint createIsNullConstraint(IsNullConstraintDTO dto, Fields fields)
    {
        IsNullConstraint isNullConstraint = new IsNullConstraint(fields.getByName(dto.field));
        return dto.isNull ? isNullConstraint : isNullConstraint.negate();
    }
}
