/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * 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.android.dx.mockito.inline.tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import android.content.Intent;
import android.os.IBinder;
import android.print.PrintAttributes;
import android.printservice.PrintService;

import androidx.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
public class MockFinal {
    @Test
    public void mockFinalJavaMethod() throws Exception {
        ClassLoader fakeParent = mock(ClassLoader.class);
        ClassLoader mockClassLoader = mock(ClassLoader.class);

        assertNull(mockClassLoader.getParent());

        // ClassLoader#getParent is final
        when(mockClassLoader.getParent()).thenReturn(fakeParent);

        assertSame(fakeParent, mockClassLoader.getParent());
    }

    @Test
    public void mockFinalAndroidFrameworkClass() throws Exception {
        // PrintAttributes is final
        PrintAttributes mockAttributes = mock(PrintAttributes.class);

        assertEquals(0, mockAttributes.getColorMode());

        when(mockAttributes.getColorMode()).thenReturn(42);

        assertEquals(42, mockAttributes.getColorMode());
    }

    @Test
    public void mockFinalMethodOfAndroidFrameworkClass() throws Exception {
        IBinder fakeBinder = mock(IBinder.class);
        PrintService mockService = mock(PrintService.class);

        assertNull(mockService.onBind(new Intent()));

        // PrintService#onBind is final
        when(mockService.onBind(any(Intent.class))).thenReturn(fakeBinder);

        assertSame(fakeBinder, mockService.onBind(new Intent()));
    }

    private static final class FinalNonDefaultConstructorClass {
        public FinalNonDefaultConstructorClass(int i) {
        }

        String returnA() {
            return "A";
        }
    }

    @Test
    public void mockNonDefaultConstructorClass() throws Exception {
        FinalNonDefaultConstructorClass mock = mock(FinalNonDefaultConstructorClass.class);

        assertNull(mock.returnA());
        when(mock.returnA()).thenReturn("fakeA");

        assertEquals("fakeA", mock.returnA());
    }

    private interface NonDefaultConstructorInterface {
        String returnA();
    }

    @Test
    public void mockNonDefaultConstructorInterface() throws Exception {
        NonDefaultConstructorInterface mock = mock(NonDefaultConstructorInterface.class);

        assertNull(mock.returnA());
        when(mock.returnA()).thenReturn("fakeA");

        assertEquals("fakeA", mock.returnA());
    }

    private static class SuperClass {
        final String returnA() {
            return "superA";
        }

        String returnB() {
            return "superB";
        }

        String returnC() {
            return "superC";
        }
    }

    private static final class SubClass extends SuperClass {
        @Override
        String returnC() {
            return "subC";
        }
    }

    @Test
    public void mockSubClass() throws Exception {
        SubClass mocked = mock(SubClass.class);
        SuperClass mockedSuper = mock(SuperClass.class);
        SubClass nonMocked = new SubClass();
        SuperClass nonMockedSuper = new SuperClass();

        // Mock returns dummy value by default
        assertNull(mocked.returnA());
        assertNull(mocked.returnB());
        assertNull(mocked.returnC());
        assertNull(mockedSuper.returnA());
        assertNull(mockedSuper.returnB());
        assertNull(mockedSuper.returnC());

        // Set fake values for mockedSuper
        when(mockedSuper.returnA()).thenReturn("fakeA");
        when(mockedSuper.returnB()).thenReturn("fakeB");
        when(mockedSuper.returnC()).thenReturn("fakeC");

        // mocked is unaffected
        assertNull(mocked.returnA());
        assertNull(mocked.returnB());
        assertNull(mocked.returnC());

        // Verify fake values of mockedSuper
        assertEquals("fakeA", mockedSuper.returnA());
        assertEquals("fakeB", mockedSuper.returnB());
        assertEquals("fakeC", mockedSuper.returnC());

        // Set fake values for mocked
        when(mocked.returnA()).thenReturn("fake2A");
        when(mocked.returnB()).thenReturn("fake2B");
        when(mocked.returnC()).thenReturn("fake2C");

        // Verify fake values of mocked
        assertEquals("fake2A", mocked.returnA());
        assertEquals("fake2B", mocked.returnB());
        assertEquals("fake2C", mocked.returnC());

        // non mocked instances are unaffected
        assertEquals("superA", nonMocked.returnA());
        assertEquals("superB", nonMocked.returnB());
        assertEquals("subC", nonMocked.returnC());
        assertEquals("superA", nonMockedSuper.returnA());
        assertEquals("superB", nonMockedSuper.returnB());
        assertEquals("superC", nonMockedSuper.returnC());
    }

    @Test
    public void spySubClass() throws Exception {
        SubClass spied = spy(SubClass.class);
        SuperClass spiedSuper = spy(SuperClass.class);
        SubClass nonSpied = new SubClass();
        SuperClass nonSpiedSuper = new SuperClass();

        // Spies call real method by default
        assertEquals("superA", spied.returnA());
        assertEquals("superB", spied.returnB());
        assertEquals("subC", spied.returnC());
        assertEquals("superA", spiedSuper.returnA());
        assertEquals("superB", spiedSuper.returnB());
        assertEquals("superC", spiedSuper.returnC());

        // Set fake values for spiedSuper
        when(spiedSuper.returnA()).thenReturn("fakeA");
        when(spiedSuper.returnB()).thenReturn("fakeB");
        when(spiedSuper.returnC()).thenReturn("fakeC");

        // spied is unaffected
        assertEquals("superA", spied.returnA());
        assertEquals("superB", spied.returnB());
        assertEquals("subC", spied.returnC());

        // Verify fake values of spiedSuper
        assertEquals("fakeA", spiedSuper.returnA());
        assertEquals("fakeB", spiedSuper.returnB());
        assertEquals("fakeC", spiedSuper.returnC());

        // Set fake values for spied
        when(spied.returnA()).thenReturn("fake2A");
        when(spied.returnB()).thenReturn("fake2B");
        when(spied.returnC()).thenReturn("fake2C");

        // Verify fake values of spied
        assertEquals("fake2A", spied.returnA());
        assertEquals("fake2B", spied.returnB());
        assertEquals("fake2C", spied.returnC());

        // non spied instances are unaffected
        assertEquals("superA", nonSpied.returnA());
        assertEquals("superB", nonSpied.returnB());
        assertEquals("subC", nonSpied.returnC());
        assertEquals("superA", nonSpiedSuper.returnA());
        assertEquals("superB", nonSpiedSuper.returnB());
        assertEquals("superC", nonSpiedSuper.returnC());
    }
}
