/*
 * AssertStatementTransform.java
 *
 * Copyright (c) 2013 Mike Strobel
 *
 * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain;
 * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa.
 *
 * This source code is subject to terms and conditions of the Apache License, Version 2.0.
 * A copy of the license can be found in the License.html file at the root of this distribution.
 * By using this source code in any fashion, you are agreeing to be bound by the terms of the
 * Apache License, Version 2.0.
 *
 * You must not remove this notice, or any other, from this software.
 */

package com.strobel.decompiler.languages.java.ast.transforms;

import com.strobel.assembler.metadata.FieldDefinition;
import com.strobel.assembler.metadata.FieldReference;
import com.strobel.assembler.metadata.MemberReference;
import com.strobel.assembler.metadata.MetadataHelper;
import com.strobel.assembler.metadata.MetadataResolver;
import com.strobel.assembler.metadata.MethodDefinition;
import com.strobel.assembler.metadata.TypeReference;
import com.strobel.decompiler.DecompilerContext;
import com.strobel.decompiler.languages.java.ast.*;
import com.strobel.decompiler.patterns.*;

import static com.strobel.core.CollectionUtilities.*;

public class AssertStatementTransform extends ContextTrackingVisitor<Void> {
    public AssertStatementTransform(final DecompilerContext context) {
        super(context);
    }

    private final static IfElseStatement ASSERT_PATTERN;
    private final static AssignmentExpression ASSERTIONS_DISABLED_PATTERN;

    static {
        ASSERT_PATTERN = new IfElseStatement(
            new UnaryOperatorExpression(
                UnaryOperatorType.NOT,
                new Choice(
                    new BinaryOperatorExpression(
                        new LeftmostBinaryOperandNode(
                            new NamedNode(
                                "assertionsDisabledCheck",
                                new TypeReferenceExpression(new SimpleType(Pattern.ANY_STRING)).member("$assertionsDisabled")
                            ),
                            BinaryOperatorType.LOGICAL_OR,
                            true
                        ).toExpression(),
                        BinaryOperatorType.LOGICAL_OR,
                        new AnyNode("condition").toExpression()
                    ),
                    new TypeReferenceExpression(new SimpleType(Pattern.ANY_STRING)).member("$assertionsDisabled")
                ).toExpression()
            ),
            new BlockStatement(
                new ThrowStatement(
                    new ObjectCreationExpression(
                        new SimpleType("AssertionError"),
                        new OptionalNode(new AnyNode("message")).toExpression()
                    )
                )
            )
        );

        ASSERTIONS_DISABLED_PATTERN = new AssignmentExpression(
            new NamedNode(
                "$assertionsDisabled",
                new Choice(
                    new IdentifierExpression("$assertionsDisabled"),
                    new TypedNode(TypeReferenceExpression.class).toExpression().member("$assertionsDisabled")
                )
            ).toExpression(),
            new UnaryOperatorExpression(
                UnaryOperatorType.NOT,
                new InvocationExpression(
                    new MemberReferenceExpression(
                        new NamedNode("type", new ClassOfExpression(new SimpleType(Pattern.ANY_STRING))).toExpression(),
                        "desiredAssertionStatus"
                    )
                )
            )
        );
    }

    @Override
    public Void visitIfElseStatement(final IfElseStatement node, final Void data) {
        super.visitIfElseStatement(node, data);

        transformAssert(node);

        return null;
    }

    @Override
    public Void visitAssignmentExpression(final AssignmentExpression node, final Void data) {
        super.visitAssignmentExpression(node, data);

        removeAssertionsDisabledAssignment(node);

        return null;
    }

    private void removeAssertionsDisabledAssignment(final AssignmentExpression node) {
        if (context.getSettings().getShowSyntheticMembers()) {
            return;
        }

        final Match m = ASSERTIONS_DISABLED_PATTERN.match(node);

        if (!m.success()) {
            return;
        }

        final AstNode parent = node.getParent();

        if (!(parent instanceof ExpressionStatement &&
              parent.getParent() instanceof BlockStatement &&
              parent.getParent().getParent() instanceof MethodDeclaration)) {

            return;
        }

        final MethodDeclaration staticInitializer = (MethodDeclaration) parent.getParent().getParent();
        final MethodDefinition methodDefinition = staticInitializer.getUserData(Keys.METHOD_DEFINITION);

        if (methodDefinition == null || !methodDefinition.isTypeInitializer()) {
            return;
        }

        final Expression field = first(m.<IdentifierExpression>get("$assertionsDisabled"));
        final ClassOfExpression type = m.<ClassOfExpression>get("type").iterator().next();
        final MemberReference reference = field.getUserData(Keys.MEMBER_REFERENCE);

        if (!(reference instanceof FieldReference)) {
            return;
        }

        final FieldDefinition resolvedField = ((FieldReference) reference).resolve();

        if (resolvedField == null || !resolvedField.isSynthetic()) {
            return;
        }

        final TypeReference typeReference = type.getType().getUserData(Keys.TYPE_REFERENCE);

        if (typeReference != null &&
            (MetadataResolver.areEquivalent(context.getCurrentType(), typeReference) ||
             MetadataHelper.isEnclosedBy(context.getCurrentType(), typeReference))) {

            parent.remove();

            if (staticInitializer.getBody().getStatements().isEmpty()) {
                staticInitializer.remove();
            }
        }
    }

    private AssertStatement transformAssert(final IfElseStatement ifElse) {
        final Match m = ASSERT_PATTERN.match(ifElse);

        if (!m.success()) {
            return null;
        }

        final Expression assertionsDisabledCheck = firstOrDefault(m.<Expression>get("assertionsDisabledCheck"));

        Expression condition = firstOrDefault(m.<Expression>get("condition"));

        if (assertionsDisabledCheck != null && assertionsDisabledCheck.getParent() instanceof BinaryOperatorExpression) {
            final BinaryOperatorExpression logicalOr = (BinaryOperatorExpression) assertionsDisabledCheck.getParent();
            final Expression right = logicalOr.getRight();

            right.remove();
            logicalOr.replaceWith(right);
            condition = right;
        }

        final AssertStatement assertStatement = new AssertStatement();

        if (condition != null) {
            condition.remove();
            assertStatement.setCondition(condition);
        }
        else {
            assertStatement.setCondition(new PrimitiveExpression(false));
        }

        if (m.has("message")) {
            Expression message = firstOrDefault(m.<Expression>get("message"));

            while (message instanceof CastExpression) {
                message = ((CastExpression) message).getExpression();
            }

            message.remove();
            assertStatement.setMessage(message);
        }

        ifElse.replaceWith(assertStatement);

        return assertStatement;
    }
}

@@ -6655,16 +6655,29 @@
  null &&
+%0D%0A           
  asserti
@@ -6696,32 +6696,132 @@
 heck.getParent()
+ instanceof BinaryOperatorExpression &&%0D%0A            assertionsDisabledCheck.getParent().getParent()
  instanceof Bina
@@ -6837,32 +6837,34 @@
 rExpression) %7B%0D%0A
+%0D%0A
             fina
@@ -7070,25 +7070,39 @@
         
-logicalOr
+assertionsDisabledCheck
 .replace
@@ -7140,16 +7140,97 @@
 tion
- = right
+.remove();%0D%0A            logicalOr.setRight(condition);%0D%0A            condition = logicalOr
 ;%0D%0A 

Fix for complex assert conditions. Closes #136.