/*
 * 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.maven.model;

import java.util.stream.Collectors;

/**
 * Class InputLocation.
 *
 * @version $Revision$ $Date$
 */
@SuppressWarnings("all")
public final class InputLocation implements java.io.Serializable, Cloneable, InputLocationTracker {

    // --------------------------/
    // - Class/Member Variables -/
    // --------------------------/

    /**
     * The one-based line number. The value will be non-positive if
     * unknown.
     */
    private int lineNumber = -1;

    /**
     * The one-based column number. The value will be non-positive
     * if unknown.
     */
    private int columnNumber = -1;

    /**
     * Field source.
     */
    private InputSource source;

    /**
     * Field locations.
     */
    private java.util.Map<Object, InputLocation> locations;

    /**
     * Field location.
     */
    private InputLocation location;

    /**
     * Field importedFrom.
     */
    private InputLocation importedFrom;

    // ----------------/
    // - Constructors -/
    // ----------------/

    public InputLocation(org.apache.maven.api.model.InputLocation location) {
        this.lineNumber = location.getLineNumber();
        this.columnNumber = location.getColumnNumber();
        this.source = location.getSource() != null ? new InputSource(location.getSource()) : null;
        this.locations = location.getLocations().isEmpty()
                ? null
                : location.getLocations().entrySet().stream()
                        .collect(Collectors.toMap(
                                e -> e.getKey(),
                                e -> e.getValue() == location ? this : new InputLocation(e.getValue())));
        this.importedFrom = location.getImportedFrom() != null ? new InputLocation(location.getImportedFrom()) : null;
    }

    public InputLocation(int lineNumber, int columnNumber) {
        this.lineNumber = lineNumber;
        this.columnNumber = columnNumber;
    } // -- org.apache.maven.model.InputLocation(int, int)

    public InputLocation(int lineNumber, int columnNumber, InputSource source) {
        this.lineNumber = lineNumber;
        this.columnNumber = columnNumber;
        this.source = source;
    } // -- org.apache.maven.model.InputLocation(int, int, InputSource)

    // -----------/
    // - Methods -/
    // -----------/

    /**
     * Method clone.
     *
     * @return InputLocation
     */
    public InputLocation clone() {
        try {
            InputLocation copy = (InputLocation) super.clone();

            if (copy.locations != null) {
                copy.locations = new java.util.LinkedHashMap(copy.locations);
            }

            return copy;
        } catch (Exception ex) {
            throw (RuntimeException)
                    new UnsupportedOperationException(getClass().getName() + " does not support clone()").initCause(ex);
        }
    } // -- InputLocation clone()

    /**
     * Get the one-based column number. The value will be
     * non-positive if unknown.
     *
     * @return int
     */
    public int getColumnNumber() {
        return this.columnNumber;
    } // -- int getColumnNumber()

    /**
     * Get the one-based line number. The value will be
     * non-positive if unknown.
     *
     * @return int
     */
    public int getLineNumber() {
        return this.lineNumber;
    } // -- int getLineNumber()

    /**
     *
     *
     * @param key
     * @return InputLocation
     */
    @Override
    public InputLocation getLocation(Object key) {
        if (key instanceof String string) {
            switch (string) {
                case "": {
                    return this.location;
                }
                default: {
                    return getOtherLocation(key);
                }
            }
        } else {
            return getOtherLocation(key);
        }
    } // -- InputLocation getLocation( Object )

    /**
     *
     *
     * @return Map
     */
    public java.util.Map<Object, InputLocation> getLocations() {
        return locations;
    } // -- java.util.Map<Object, InputLocation> getLocations()

    /**
     *
     *
     * @param key
     * @param location
     */
    @Override
    public void setLocation(Object key, InputLocation location) {
        if (key instanceof String string) {
            switch (string) {
                case "": {
                    this.location = location;
                    return;
                }
                default: {
                    setOtherLocation(key, location);
                    return;
                }
            }
        } else {
            setOtherLocation(key, location);
        }
    } // -- void setLocation( Object, InputLocation )

    /**
     *
     *
     * @param key
     * @param location
     */
    public void setOtherLocation(Object key, InputLocation location) {
        if (location != null) {
            if (this.locations == null) {
                this.locations = new java.util.LinkedHashMap<>();
            }
            this.locations.put(key, location);
        }
    } // -- void setOtherLocation( Object, InputLocation )

    /**
     *
     *
     * @param key
     * @return InputLocation
     */
    private InputLocation getOtherLocation(Object key) {
        return (locations != null) ? locations.get(key) : null;
    } // -- InputLocation getOtherLocation( Object )

    /**
     * Get the source field.
     *
     * @return InputSource
     */
    public InputSource getSource() {
        return this.source;
    } // -- InputSource getSource()

    /**
     * Gets the parent InputLocation where this InputLocation may have been imported from.
     * Can return {@code null}.
     *
     * @return InputLocation
     * @since 4.0.0
     */
    public InputLocation getImportedFrom() {
        return importedFrom;
    }

    /**
     * Set the imported from location.
     *
     * @param importedFrom
     */
    public void setImportedFrom(InputLocation importedFrom) {
        this.importedFrom = importedFrom;
    }

    /**
     * Method merge.
     *
     * @param target
     * @param sourceDominant
     * @param source
     * @return InputLocation
     */
    public static InputLocation merge(InputLocation target, InputLocation source, boolean sourceDominant) {
        if (source == null) {
            return target;
        } else if (target == null) {
            return source;
        }

        InputLocation result = new InputLocation(target.getLineNumber(), target.getColumnNumber(), target.getSource());

        java.util.Map<Object, InputLocation> locations;
        java.util.Map<Object, InputLocation> sourceLocations = source.getLocations();
        java.util.Map<Object, InputLocation> targetLocations = target.getLocations();
        if (sourceLocations == null) {
            locations = targetLocations;
        } else if (targetLocations == null) {
            locations = sourceLocations;
        } else {
            locations = new java.util.LinkedHashMap();
            locations.putAll(sourceDominant ? targetLocations : sourceLocations);
            locations.putAll(sourceDominant ? sourceLocations : targetLocations);
        }
        result.setLocations(locations);

        return result;
    } // -- InputLocation merge( InputLocation, InputLocation, boolean )

    /**
     * Method merge.
     *
     * @param target
     * @param indices
     * @param source
     * @return InputLocation
     */
    public static InputLocation merge(
            InputLocation target, InputLocation source, java.util.Collection<Integer> indices) {
        if (source == null) {
            return target;
        } else if (target == null) {
            return source;
        }

        InputLocation result = new InputLocation(target.getLineNumber(), target.getColumnNumber(), target.getSource());

        java.util.Map<Object, InputLocation> locations;
        java.util.Map<Object, InputLocation> sourceLocations = source.getLocations();
        java.util.Map<Object, InputLocation> targetLocations = target.getLocations();
        if (sourceLocations == null) {
            locations = targetLocations;
        } else if (targetLocations == null) {
            locations = sourceLocations;
        } else {
            locations = new java.util.LinkedHashMap<>();
            for (java.util.Iterator<Integer> it = indices.iterator(); it.hasNext(); ) {
                InputLocation location;
                Integer index = it.next();
                if (index.intValue() < 0) {
                    location = sourceLocations.get(Integer.valueOf(~index.intValue()));
                } else {
                    location = targetLocations.get(index);
                }
                locations.put(Integer.valueOf(locations.size()), location);
            }
        }
        result.setLocations(locations);

        return result;
    } // -- InputLocation merge( InputLocation, InputLocation, java.util.Collection )

    /**
     *
     *
     * @param locations
     */
    public void setLocations(java.util.Map<Object, InputLocation> locations) {
        this.locations = locations;
    } // -- void setLocations( java.util.Map )

    public org.apache.maven.api.model.InputLocation toApiLocation() {
        if (locations != null && locations.values().contains(this)) {
            if (locations.size() == 1 && locations.values().iterator().next() == this) {
                return org.apache.maven.api.model.InputLocation.of(
                        lineNumber,
                        columnNumber,
                        source != null ? source.toApiSource() : null,
                        locations.keySet().iterator().next());
            } else {
                return org.apache.maven.api.model.InputLocation.of(
                        lineNumber, columnNumber, source != null ? source.toApiSource() : null);
            }
        } else {
            return org.apache.maven.api.model.InputLocation.of(
                    lineNumber,
                    columnNumber,
                    source != null ? source.toApiSource() : null,
                    locations != null
                            ? locations.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()
                                    .toApiLocation()))
                            : null);
        }
    }

    // -----------------/
    // - Inner Classes -/
    // -----------------/

    /**
     * Class StringFormatter.
     *
     * @version $Revision$ $Date$
     */
    public abstract static class StringFormatter {

        // -----------/
        // - Methods -/
        // -----------/

        /**
         * Method toString.
         *
         * @param location
         * @return String
         */
        public abstract String toString(InputLocation location);
    }

    @Override
    public String toString() {
        return getLineNumber() + " : " + getColumnNumber() + ", " + getSource();
    }
}
