/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification
 *             of Java bytecode.
 *
 * Copyright (c) 2002-2018 GuardSquare NV
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 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 General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package proguard.shrink;

import proguard.classfile.*;
import proguard.classfile.visitor.*;


/**
 * This ClassVisitor and MemberVisitor recursively marks all classes
 * and class elements that are being used. For each element, it finds the
 * shortest chain of dependencies.
 *
 * @see ClassShrinker
 *
 * @author Eric Lafortune
 */
public class ShortestUsageMarker extends UsageMarker
{
    private static final ShortestUsageMark INITIAL_MARK =
        new ShortestUsageMark("is kept by a directive in the configuration.\n\n");


    // A field acting as a parameter to the visitor methods.
    private ShortestUsageMark currentUsageMark = INITIAL_MARK;

    // A utility object to check for recursive causes.
    private final MyRecursiveCauseChecker recursiveCauseChecker = new MyRecursiveCauseChecker();


    // Overriding implementations for UsageMarker.

    protected void markProgramClassBody(ProgramClass programClass)
    {
        ShortestUsageMark previousUsageMark = currentUsageMark;

        currentUsageMark = new ShortestUsageMark(getShortestUsageMark(programClass),
                                                 "is extended by   ",
                                                 10000,
                                                 programClass);

        super.markProgramClassBody(programClass);

        currentUsageMark = previousUsageMark;
    }


    protected void markProgramFieldBody(ProgramClass programClass, ProgramField programField)
    {
        ShortestUsageMark previousUsageMark = currentUsageMark;

        currentUsageMark = new ShortestUsageMark(getShortestUsageMark(programField),
                                                 "is referenced by ",
                                                 1,
                                                 programClass,
                                                 programField);

        super.markProgramFieldBody(programClass, programField);

        currentUsageMark = previousUsageMark;
    }


    protected void markProgramMethodBody(ProgramClass programClass, ProgramMethod programMethod)
    {
        ShortestUsageMark previousUsageMark = currentUsageMark;

        currentUsageMark = new ShortestUsageMark(getShortestUsageMark(programMethod),
                                                 "is invoked by    ",
                                                 1,
                                                 programClass,
                                                 programMethod);

        super.markProgramMethodBody(programClass, programMethod);

        currentUsageMark = previousUsageMark;
    }


    protected void markMethodHierarchy(Clazz clazz, Method method)
    {
        ShortestUsageMark previousUsageMark = currentUsageMark;

        currentUsageMark = new ShortestUsageMark(getShortestUsageMark(method),
                                                 "implements       ",
                                                 100,
                                                 clazz,
                                                 method);

        super.markMethodHierarchy(clazz, method);

        currentUsageMark = previousUsageMark;
    }


    // Small utility methods.

    protected void markAsUsed(VisitorAccepter visitorAccepter)
    {
        Object visitorInfo = visitorAccepter.getVisitorInfo();

        ShortestUsageMark shortestUsageMark =
            visitorInfo != null                           &&
            visitorInfo instanceof ShortestUsageMark      &&
            !((ShortestUsageMark)visitorInfo).isCertain() &&
            !currentUsageMark.isShorter((ShortestUsageMark)visitorInfo) ?
                new ShortestUsageMark((ShortestUsageMark)visitorInfo, true):
                currentUsageMark;

        visitorAccepter.setVisitorInfo(shortestUsageMark);
    }


    protected boolean shouldBeMarkedAsUsed(VisitorAccepter visitorAccepter)
    {
        Object visitorInfo = visitorAccepter.getVisitorInfo();

        return //!(visitorAccepter instanceof Clazz &&
               //  isCausedBy(currentUsageMark, (Clazz)visitorAccepter)) &&
               (visitorInfo == null                           ||
               !(visitorInfo instanceof ShortestUsageMark)   ||
               !((ShortestUsageMark)visitorInfo).isCertain() ||
               currentUsageMark.isShorter((ShortestUsageMark)visitorInfo));
    }


    protected boolean isUsed(VisitorAccepter visitorAccepter)
    {
        Object visitorInfo = visitorAccepter.getVisitorInfo();

        return visitorInfo != null                      &&
               visitorInfo instanceof ShortestUsageMark &&
               ((ShortestUsageMark)visitorInfo).isCertain();
    }


    protected void markAsPossiblyUsed(VisitorAccepter visitorAccepter)
    {
        visitorAccepter.setVisitorInfo(new ShortestUsageMark(currentUsageMark, false));
    }


    protected boolean shouldBeMarkedAsPossiblyUsed(VisitorAccepter visitorAccepter)
    {
        Object visitorInfo = visitorAccepter.getVisitorInfo();

        return visitorInfo == null                         ||
               !(visitorInfo instanceof ShortestUsageMark) ||
               (!((ShortestUsageMark)visitorInfo).isCertain() &&
                currentUsageMark.isShorter((ShortestUsageMark)visitorInfo));
    }


    protected boolean isPossiblyUsed(VisitorAccepter visitorAccepter)
    {
        Object visitorInfo = visitorAccepter.getVisitorInfo();

        return visitorInfo != null                      &&
               visitorInfo instanceof ShortestUsageMark &&
               !((ShortestUsageMark)visitorInfo).isCertain();
    }


    protected ShortestUsageMark getShortestUsageMark(VisitorAccepter visitorAccepter)
    {
        Object visitorInfo = visitorAccepter.getVisitorInfo();

        return (ShortestUsageMark)visitorInfo;
    }


    // Small utility methods.

    private boolean isCausedBy(ShortestUsageMark shortestUsageMark,
                               Clazz             clazz)
    {
        return recursiveCauseChecker.check(shortestUsageMark, clazz);
    }


    private class MyRecursiveCauseChecker implements ClassVisitor, MemberVisitor
    {
        private Clazz   checkClass;
        private boolean isRecursing;


        public boolean check(ShortestUsageMark shortestUsageMark,
                             Clazz             clazz)
        {
            checkClass  = clazz;
            isRecursing = false;

            shortestUsageMark.acceptClassVisitor(this);
            shortestUsageMark.acceptMemberVisitor(this);

            return isRecursing;
        }

        // Implementations for ClassVisitor.

        public void visitProgramClass(ProgramClass programClass)
        {
            checkCause(programClass);
        }


        public void visitLibraryClass(LibraryClass libraryClass)
        {
            checkCause(libraryClass);
        }


        // Implementations for MemberVisitor.

        public void visitProgramField(ProgramClass programClass, ProgramField programField)
        {
            checkCause(programField);
        }


        public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
        {
            checkCause(programMethod);
        }


        public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
        {
             checkCause(libraryField);
       }


        public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
        {
            checkCause(libraryMethod);
        }


        // Small utility methods.

        private void checkCause(VisitorAccepter visitorAccepter)
        {
            if (ShortestUsageMarker.this.isUsed(visitorAccepter))
            {
                ShortestUsageMark shortestUsageMark = ShortestUsageMarker.this.getShortestUsageMark(visitorAccepter);

                // Check the class of this mark, if any
                isRecursing = shortestUsageMark.isCausedBy(checkClass);

                // Check the causing class or method, if still necessary.
                if (!isRecursing)
                {
                    shortestUsageMark.acceptClassVisitor(this);
                    shortestUsageMark.acceptMemberVisitor(this);
                }
            }
        }
    }
}
