//------------------------------------------------------------------------------------------------//
//                                                                                                //
//                                      T u p l e t I n t e r                                     //
//                                                                                                //
//------------------------------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr">
//
//  Copyright © Audiveris 2018. All rights reserved.
//
//  This program is free software: you can redistribute it and/or modify it under the terms of the
//  GNU Affero General Public License as published by the Free Software Foundation, either version
//  3 of the License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
//  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//  See the GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License along with this
//  program.  If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------------------------//
// </editor-fold>
package org.audiveris.omr.sig.inter;

import org.audiveris.omr.constant.ConstantSet;
import org.audiveris.omr.glyph.Glyph;
import org.audiveris.omr.glyph.Shape;
import org.audiveris.omr.math.GeoOrder;
import org.audiveris.omr.math.Rational;
import org.audiveris.omr.sheet.DurationFactor;
import org.audiveris.omr.sheet.Scale;
import org.audiveris.omr.sheet.Staff;
import org.audiveris.omr.sheet.SystemInfo;
import org.audiveris.omr.sheet.rhythm.MeasureStack;
import org.audiveris.omr.sheet.rhythm.TupletsBuilder;
import org.audiveris.omr.sheet.rhythm.Voice;
import org.audiveris.omr.sig.relation.ChordTupletRelation;
import org.audiveris.omr.sig.relation.Link;
import org.audiveris.omr.sig.relation.Relation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;

import javax.xml.bind.annotation.XmlRootElement;

/**
 * Class {@code TupletInter} represents a tuplet sign (3 or 6).
 * <p>
 * A tuplet inter cannot be assigned a staff immediately, since it may be located between staves and
 * not related to the closest one.
 *
 * @author Hervé Bitteur
 */
@XmlRootElement(name = "tuplet")
public class TupletInter
        extends AbstractInter
{

    private static final Constants constants = new Constants();

    private static final Logger logger = LoggerFactory.getLogger(TupletInter.class);

    // Factor lazily computed
    private DurationFactor durationFactor;

    /** Base duration. Lazily computed. */
    private Rational baseDuration;

    /**
     * Creates a new {@code TupletInter} object.
     *
     * @param glyph the tuplet glyph
     * @param shape TUPLET_THREE or TUPLET_SIX
     * @param grade the inter quality
     */
    public TupletInter (Glyph glyph,
                        Shape shape,
                        double grade)
    {
        super(glyph, (glyph != null) ? glyph.getBounds() : null, shape, grade);
    }

    /**
     * No-arg constructor meant for JAXB.
     */
    private TupletInter ()
    {
    }

    //--------//
    // accept //
    //--------//
    @Override
    public void accept (InterVisitor visitor)
    {
        visitor.visit(this);
    }

    //-------//
    // added //
    //-------//
    /**
     * Add it from containing stack and/or measure.
     *
     * @see #remove(boolean)
     */
    @Override
    public void added ()
    {
        super.added();

        MeasureStack stack = sig.getSystem().getStackAt(getCenter());

        if (stack != null) {
            stack.addInter(this);
        }

        setAbnormal(true); // No chord linked yet
    }

    //---------------//
    // checkAbnormal //
    //---------------//
    @Override
    public boolean checkAbnormal ()
    {
        SortedSet<AbstractChordInter> embraced = TupletsBuilder.getEmbracedChords(
                this,
                getChords());
        setAbnormal(embraced == null);

        return isAbnormal();
    }

    //-----------------//
    // getBaseDuration //
    //-----------------//
    /**
     * Report the chord duration (without dot) on which tuplet modification applies.
     *
     * @return base duration
     */
    public Rational getBaseDuration ()
    {
        if (baseDuration == null) {
            for (Relation rel : sig.getRelations(this, ChordTupletRelation.class)) {
                AbstractChordInter chord = (AbstractChordInter) sig.getOppositeInter(this, rel);
                Rational rawDur = chord.getDurationSansDotOrTuplet();

                if ((baseDuration == null) || (baseDuration.compareTo(rawDur) > 0)) {
                    baseDuration = rawDur;
                }
            }
        }

        return baseDuration;
    }

    //-----------//
    // getChords //
    //-----------//
    /**
     * Report the sequence of chords embraced by this tuplet.
     *
     * @return the left to right sequence of chords
     */
    public List<AbstractChordInter> getChords ()
    {
        List<AbstractChordInter> list = new ArrayList<>();

        for (Relation tcRel : sig.getRelations(this, ChordTupletRelation.class)) {
            list.add((AbstractChordInter) sig.getOppositeInter(this, tcRel));
        }

        Collections.sort(list, Inters.byAbscissa);

        return Collections.unmodifiableList(list);
    }

    //-------------------//
    // getDurationFactor //
    //-------------------//
    /**
     * @return the durationFactor
     */
    public DurationFactor getDurationFactor ()
    {
        if (durationFactor == null) {
            durationFactor = getFactor(shape);
        }

        return durationFactor;
    }

    //----------//
    // getStaff //
    //----------//
    @Override
    public Staff getStaff ()
    {
        if (staff == null) {
            // Chord -> Tuplet
            for (Relation ctRel : sig.getRelations(this, ChordTupletRelation.class)) {
                AbstractChordInter chord = (AbstractChordInter) sig.getOppositeInter(this, ctRel);

                if (chord.getStaff() != null) {
                    return staff = chord.getStaff();
                }
            }
        }

        return staff;
    }

    //----------//
    // getVoice //
    //----------//
    @Override
    public Voice getVoice ()
    {
        // Use the voice of the first chord embraced by the tuplet
        for (Relation rel : sig.getRelations(this, ChordTupletRelation.class)) {
            return sig.getOppositeInter(this, rel).getVoice();
        }

        return null;
    }

    //--------//
    // remove //
    //--------//
    /**
     * Remove it from containing stack and/or measure.
     *
     * @param extensive true for non-manual removals only
     * @see #added()
     */
    @Override
    public void remove (boolean extensive)
    {
        MeasureStack stack = sig.getSystem().getStackAt(getCenter());

        if (stack != null) {
            stack.removeInter(this);
        }

        super.remove(extensive);
    }

    //-------------//
    // searchLinks //
    //-------------//
    /**
     * {@inheritDoc}
     * <p>
     * Specifically, look for chords to link with this tuplet.
     *
     * @return chords link, perhaps empty
     */
    @Override
    public Collection<Link> searchLinks (SystemInfo system,
                                         boolean doit)
    {
        MeasureStack stack = system.getStackAt(getCenter());
        Collection<Link> links = new TupletsBuilder(stack).lookupLinks(this);

        if (doit) {
            for (Link link : links) {
                link.applyTo(this);
            }
        }

        return links;
    }

    //-----------//
    // internals //
    //-----------//
    @Override
    protected String internals ()
    {
        return super.internals() + " " + shape;
    }

    //-------------//
    // createValid //
    //-------------//
    /**
     * (Try to) create a tuplet inter, checking that there is at least one (head) chord
     * nearby.
     *
     * @param glyph        the candidate tuplet glyph
     * @param shape        TUPLET_THREE or TUPLET_SIX
     * @param grade        the interpretation quality
     * @param system       the related system
     * @param systemChords abscissa-ordered list of chords in this system
     * @return the create TupletInter or null
     */
    public static TupletInter createValid (Glyph glyph,
                                           Shape shape,
                                           double grade,
                                           SystemInfo system,
                                           List<Inter> systemChords)
    {
        Rectangle luBox = glyph.getBounds();
        Scale scale = system.getSheet().getScale();
        luBox.grow(
                scale.toPixels(constants.maxTupletChordDx),
                scale.toPixels(constants.maxTupletChordDy));

        List<Inter> nearby = Inters.intersectedInters(systemChords, GeoOrder.BY_ABSCISSA, luBox);

        if (nearby.isEmpty()) {
            logger.debug("Discarding isolated tuplet candidate glyph#{}", glyph.getId());

            return null;
        }

        return new TupletInter(glyph, shape, grade);
    }

    //-----------//
    // getFactor //
    //-----------//
    /**
     * Report the tuplet factor that corresponds to the tuplet sign
     *
     * @param glyph the tuplet sign
     * @return the related factor
     */
    private static DurationFactor getFactor (Shape shape)
    {
        switch (shape) {
        case TUPLET_THREE:
            return new DurationFactor(2, 3);

        case TUPLET_SIX:
            return new DurationFactor(4, 6);

        default:
            logger.error("Incorrect tuplet shape " + shape);

            return null;
        }
    }

    //-----------//
    // Constants //
    //-----------//
    private static class Constants
            extends ConstantSet
    {

        private final Scale.Fraction maxTupletChordDx = new Scale.Fraction(
                3,
                "Maximum abscissa gap between tuplet and closest chord");

        private final Scale.Fraction maxTupletChordDy = new Scale.Fraction(
                2.5,
                "Maximum ordinate gap between tuplet and closest chord");
    }
}