/*
 * Copyright 2006-2021 Prowide
 *
 * 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.prowidesoftware.swift.model;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;

import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Class for identification of MX messages.
 *
 * <p>It is composed by the business process (business area), functionality (message type), variant and version.
 * For a better understanding of ISO 20022 variants check https://www.iso20022.org/variants.page
 *
 * @author miguel
 * @since 7.7
 */
public class MxId {
    private static final Pattern pattern = Pattern.compile(".*([a-zA-Z]{4}).(\\d{3}).(\\d{3}).(\\d{2}).*");
    private static final Pattern pattern1 = Pattern.compile(".*([a-zA-Z]{4})(\\d{3})(\\d{3})(\\d{2}).*");
    private MxBusinessProcess businessProcess;
    private String functionality;
    private String variant;
    private String version;

    public MxId() {
        this.businessProcess = null;
        this.functionality = StringUtils.EMPTY;
        this.variant = StringUtils.EMPTY;
        this.version = StringUtils.EMPTY;
    }

    /**
     * Creates a new object getting data from an MX message namespace.
     * <p>The implementation parses the namespace using a regex to detect the message type part.
     *
     * @param namespace a complete or partial namespace such as "urn:iso:std:iso:20022:tech:xsd:pain.001.001.03" or just "pain.001.001.03"
     * @throws IllegalArgumentException if namespace parameter cannot be parsed as MX identification
     */
    public MxId(final String namespace) {
        Validate.notNull(namespace);
        Matcher matcher = pattern.matcher(namespace);
        if (!matcher.matches()) {
            matcher = pattern1.matcher(namespace);
        }
        if (matcher.matches()) {
            final String bpStr = matcher.group(1);
            try {
                this.businessProcess = MxBusinessProcess.valueOf(bpStr);
            } catch (final Exception e) {
                throw new IllegalArgumentException("Illegal value for business process: '" + bpStr + "' see enum values in " + MxBusinessProcess.class.getName() + " for valid options", e);
            }
            this.functionality = matcher.group(2);
            this.variant = matcher.group(3);
            this.version = matcher.group(4);
        } else {
            throw new IllegalArgumentException("Could not parse namespace '" + namespace + "'");
        }
    }

    public MxId(final MxBusinessProcess businessProcess, final String funString, final String varString, final String verString) {
        this.businessProcess = businessProcess;
        this.functionality = funString;
        this.variant = varString;
        this.version = verString;
    }

    public MxId(final String bpString, final String funString, final String varString, final String verString) {
        this(MxBusinessProcess.valueOf(bpString), funString, varString, verString);
    }

    /**
     * Gets the business process (a.k.a. business area)
     *
     * @return the business process set
     */
    public MxBusinessProcess getBusinessProcess() {
        return businessProcess;
    }

    public MxId setBusinessProcess(final MxBusinessProcess businessProcess) {
        this.businessProcess = businessProcess;
        return this;
    }

    /**
     * Gets the functionality (a.k.a. message type)
     *
     * @return the functionality set
     */
    public String getFunctionality() {
        return functionality;
    }

    public MxId setFunctionality(final String functionality) {
        this.functionality = functionality;
        return this;
    }

    public String getVariant() {
        return variant;
    }

    public MxId setVariant(final String variant) {
        this.variant = variant;
        return this;
    }

    public String getVersion() {
        return version;
    }

    public MxId setVersion(final String version) {
        this.version = version;
        return this;
    }

    public String camelized() {
        final StringBuilder sb = new StringBuilder();
        if (businessProcess != null) {
            sb.append(Character.toUpperCase(businessProcess.name().charAt(0)));
            sb.append(businessProcess.name().substring(1));
        }
        if (functionality != null) {
            sb.append(functionality);
        }
        if (variant != null) {
            sb.append(variant);
        }
        if (version != null) {
            sb.append(version);
        }

        return sb.toString();
    }

    public int getVersionInt() {
        return Integer.valueOf(getVersion());
    }

    public int getVariantInt() {
        return Integer.valueOf(getVariant());
    }

    public int getFunctionalityInt() {
        return Integer.valueOf(getFunctionality());
    }

    /**
     * Creates the corresponding ISO 20022 namespace URI for this MX, for example: urn:swift:xsd:camt.003.001.04
     * All id attributes should be properly filled.
     *
     * @return a string representing the namespace URI for the MX or null if any of the attributes is not set
     */
    public String namespaceURI() {
        return new StringBuilder("urn:swift:xsd:").append(id()).toString();
    }

    /**
     * Get a string in the form of businessprocess.functionality.variant.version
     *
     * @return a string with the MX message type identification or null if any of the properties is null
     * @since 7.7
     */
    public String id() {
        final StringBuilder sb = new StringBuilder();
        if (businessProcess != null) {
            sb.append(businessProcess.name());
        } else {
            return null;
        }
        if (functionality != null) {
            sb.append("." + functionality);
        } else {
            return null;
        }
        if (variant != null) {
            sb.append("." + variant);
        } else {
            return null;
        }
        if (version != null) {
            sb.append("." + version);
        } else {
            return null;
        }
        return sb.toString();
    }

    @Override
    public String toString() {
        return id();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MxId mxId = (MxId) o;
        return businessProcess == mxId.businessProcess &&
                Objects.equals(functionality, mxId.functionality) &&
                Objects.equals(variant, mxId.variant) &&
                Objects.equals(version, mxId.version);
    }

    @Override
    public int hashCode() {
        return Objects.hash(businessProcess, functionality, variant, version);
    }

    /**
     * Check if this identification matches the given namespace.
     *
     * <p>This is particularly useful if this identifier is not completely filled, for example: if the business process
     * is set to "pain" and the functionality is set to "002" but the variant and version are left null, then this
     * identifier will match any namespace containing pain.002.*.* where the wildcard could be any number.
     * <br>new MxId("pain", "002", null, null).matches("pain.002.001.03") will be true.
     *
     * @param namespace a complete or partial namespace such as "urn:iso:std:iso:20022:tech:xsd:pain.001.001.03" or just "pain.001.001.03"
     * @return true if this id matches the parameter
     * @throws IllegalArgumentException if namespace parameter cannot be parsed as MX identification
     * @since 7.10.7
     */
    public boolean matches(String namespace) {
        return matches(new MxId(namespace));
    }

    /**
     * Check if this identification matches another one.
     *
     * <p>This is particularly useful if this identifier is not completely filled, for example: if the business process
     * is set to "pain" and the functionality is set to "002" but the variant and version are left null, then this
     * identifier will match for example both pain.002.001.03 and pain.002.002.04.
     *
     * <p>The difference between this implementation and {@link #equals(Object)} is that here null and empty properties
     * are treated as equals. Meaning it is not sensible to null versus blank properties, thus pain.001.001.null will
     * match pain.001.001.empty. </p>
     *
     * @param other an identification to compare
     * @return true if this id matches the parameter
     * @throws IllegalArgumentException if namespace parameter cannot be parsed as MX identification
     * @since 7.10.7
     */
    public boolean matches(MxId other) {
        return this.businessProcess == other.getBusinessProcess() &&
                (StringUtils.isBlank(this.functionality) || StringUtils.isBlank(other.getFunctionality()) || StringUtils.equals(this.functionality, other.getFunctionality())) &&
                (StringUtils.isBlank(this.variant) || StringUtils.isBlank(other.getVariant()) || StringUtils.equals(this.variant, other.getVariant())) &&
                (StringUtils.isBlank(this.version) || StringUtils.isBlank(other.getVersion()) || StringUtils.equals(this.version, other.getVersion()));
    }

}