/*
 *  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.causeway.core.metamodel.object;

import java.util.List;
import java.util.stream.Collectors;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.apache.causeway.applib.domain.DomainObjectList;
import org.apache.causeway.commons.internal._Constants;
import org.apache.causeway.commons.internal.base._NullSafe;
import org.apache.causeway.core.metamodel.object.ManagedObject.Specialization;
import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
import org.apache.causeway.core.metamodel.valuesemantics.IntValueSemantics;
import org.apache.causeway.core.mmtestsupport.MetaModelContext_forTesting;

import lombok.SneakyThrows;

class ManagedObjectTest {

    private MetaModelContext_forTesting mmc;
    private SpecificationLoader specLoader;

    @BeforeEach
    public void setUp() throws Exception {
        mmc = MetaModelContext_forTesting.builder()
                        .valueSemantic(new IntValueSemantics())
                        .build();
        specLoader = mmc.getSpecificationLoader();
    }

    @ParameterizedTest
    @ValueSource(classes = {void.class, Void.class})
    void voidShouldMapToEmptyValue(final Class<?> cls) {
        var spec = specLoader.specForTypeElseFail(cls);
        assertTrue(spec.isVoid(), ()->"isVoid()");
        assertTrue(spec.isValue(), ()->"isValue()");
        assertFalse(spec.isAbstract(), ()->"isAbstract()");
        assertFalse(spec.isInjectable(), ()->"isInjectable()");
        assertFalse(spec.isEntityOrViewModel(), ()->"isEntityOrViewModel()");

        var emptySpez = Specialization.inferFrom(spec, null);
        assertEquals(Specialization.EMPTY, emptySpez);

        var emptyObject = ManagedObject.empty(spec);
        assertNotNull(emptyObject);
    }

    @ParameterizedTest
    @ValueSource(classes = {int.class, Integer.class})
    void intShouldMapToValue(final Class<?> cls) {
        var spec = specLoader.specForTypeElseFail(cls);
        assertFalse(spec.isVoid(), ()->"isVoid()");
        assertTrue(spec.isValue(), ()->"isValue()");
        assertFalse(spec.isAbstract(), ()->"isAbstract()");
        assertFalse(spec.isInjectable(), ()->"isInjectable()");
        assertFalse(spec.isEntityOrViewModel(), ()->"isEntityOrViewModel()");

        var emptySpez = Specialization.inferFrom(spec, null);
        assertEquals(Specialization.EMPTY, emptySpez);

        var emptyObject = ManagedObject.empty(spec);
        assertNotNull(emptyObject);

        var presentObject = ManagedObject.adaptSingular(specLoader, 3);
        assertEquals(Specialization.VALUE, presentObject.specialization());

        presentObject.specialization().assertCompliance(presentObject.objSpec(), 6);

        assertThrows(AssertionError.class, ()->{
            presentObject.specialization().assertCompliance(presentObject.objSpec(), "incompatible");
        });
    }

    @ParameterizedTest
    @ValueSource(classes = {DomainObjectList.class})
    @SneakyThrows
    void someTypesShouldMapToViewmodel(final Class<?> cls) {
        var spec = specLoader.specForTypeElseFail(cls);
        assertFalse(spec.isVoid(), ()->"isVoid()");
        assertTrue(spec.isViewModel(), ()->"isViewModel()");
        assertFalse(spec.isAbstract(), ()->"isAbstract()");
        assertFalse(spec.isInjectable(), ()->"isInjectable()");

        var emptySpez = Specialization.inferFrom(spec, null);
        assertEquals(Specialization.EMPTY, emptySpez);

        var emptyObject = ManagedObject.empty(spec);
        assertNotNull(emptyObject);

        var constructor = cls.getConstructor(_Constants.emptyClasses);
        var pojo = constructor.newInstance(_Constants.emptyObjects);

        var presentObject = ManagedObject.adaptSingular(specLoader, pojo);
        assertEquals(Specialization.VIEWMODEL, presentObject.specialization());

        presentObject.specialization().assertCompliance(presentObject.objSpec(), pojo);

        //TODO
//        assertThrows(AssertionError.class, ()->{
//            presentObject.assertCompliance("incompatible");
//        });
    }

    @Test
    void comparatorShouldNotViolateItsGeneralContract() {

        var stringSpec = specLoader.specForTypeElseFail(String.class);

        final String[] DATA = new String[] { "fn1.ln1@vendor3.example.com", "fn2.ln2@vendor4.example.com",
                "fn3.ln3@vendor6.example.com", "fn4.ln4@vendor10.example.com", "fn5.ln5@vendor6.example.com",
                "fn6.ln6@vendor4.example.com", "fn7.ln7@vendor3.example.com", "fn8.ln8@vendor4.example.com",
                "fn9.ln9@vendor9.example.com", "fn10.ln10@vendor9.example.com", "fn11.ln11@vendor8.example.com", null,
                "fn13.ln13@vendor5.example.com", "fn14.ln14@vendor8.example.com", "fn15.ln15@vendor6.example.com",
                "fn16.ln16@vendor6.example.com", "fn17.ln17@vendor4.example.com", "fn18.ln18@vendor5.example.com",
                "fn19.ln19@vendor3.example.com", "fn20.ln20@vendor3.example.com", "fn21.ln21@vendor8.example.com",
                "fn22.ln22@vendor8.example.com", "fn23.ln23@vendor4.example.com", null, "fn25.ln25@vendor9.example.com",
                "fn26.ln26@vendor10.example.com", "fn27.ln27@vendor4.example.com", "fn28.ln28@vendor9.example.com",
                "fn29.ln29@vendor8.example.com", "fn30.ln30@vendor3.example.com", "fn31.ln31@vendor4.example.com",
                "fn32.ln32@vendor4.example.com", "fn33.ln33@vendor7.example.com", "fn34.ln34@vendor10.example.com",
                "fn35.ln35@vendor7.example.com", null, "fn37.ln37@vendor7.example.com", "fn38.ln38@vendor1.example.com",
                "fn39.ln39@vendor10.example.com", "fn40.ln40@vendor5.example.com", "fn41.ln41@vendor9.example.com",
                "fn42.ln42@vendor9.example.com", "fn43.ln43@vendor5.example.com", "fn44.ln44@vendor6.example.com",
                "fn45.ln45@vendor1.example.com", "fn46.ln46@vendor6.example.com", "fn47.ln47@vendor5.example.com", null,
                "fn49.ln49@vendor4.example.com", "fn50.ln50@vendor1.example.com", "fn51.ln51@vendor8.example.com",
                "fn52.ln52@vendor3.example.com", "fn53.ln53@vendor10.example.com", "fn54.ln54@vendor5.example.com",
                "fn55.ln55@vendor4.example.com", "fn56.ln56@vendor7.example.com", "fn57.ln57@vendor4.example.com",
                "fn58.ln58@vendor5.example.com", "fn59.ln59@vendor5.example.com", null, "fn234.ln234@vendor2.example.com",
                "fn61.ln61@vendor6.example.com", "fn62.ln62@vendor2.example.com", "fn63.ln63@vendor3.example.com",
                "fn64.ln64@vendor5.example.com", "fn65.ln65@vendor6.example.com", "fn66.ln66@vendor6.example.com",
                "fn67.ln67@vendor2.example.com", "fn68.ln68@vendor4.example.com", "fn69.ln69@vendor7.example.com",
                "fn70.ln70@vendor5.example.com", "fn71.ln71@vendor8.example.com", null, "fn73.ln73@vendor3.example.com",
                "fn74.ln74@vendor4.example.com", "fn75.ln75@vendor6.example.com", "fn76.ln76@vendor4.example.com",
                "fn77.ln77@vendor2.example.com", "fn78.ln78@vendor5.example.com", "fn79.ln79@vendor5.example.com",
                "fn80.ln80@vendor9.example.com", "fn81.ln81@vendor2.example.com", "fn82.ln82@vendor10.example.com",
                "fn83.ln83@vendor3.example.com", null, "fn85.ln85@vendor9.example.com", "fn86.ln86@vendor8.example.com",
                "fn87.ln87@vendor10.example.com", "fn88.ln88@vendor7.example.com", "fn89.ln89@vendor2.example.com",
                "fn90.ln90@vendor6.example.com", "fn91.ln91@vendor10.example.com", "fn92.ln92@vendor2.example.com",
                "fn93.ln93@vendor4.example.com", "fn94.ln94@vendor7.example.com", "fn95.ln95@vendor4.example.com", null,
                "fn97.ln97@vendor7.example.com", "fn98.ln98@vendor10.example.com", "fn99.ln99@vendor2.example.com",
                "fn100.ln100@vendor5.example.com", "fn101.ln101@vendor8.example.com", "fn102.ln102@vendor4.example.com",
                "fn103.ln103@vendor8.example.com", "fn104.ln104@vendor2.example.com", "fn105.ln105@vendor3.example.com",
                "fn106.ln106@vendor10.example.com", "fn107.ln107@vendor9.example.com", null,
                "fn109.ln109@vendor7.example.com", "fn110.ln110@vendor10.example.com", null,
                "fn111.ln111@vendor1.example.com", "fn112.ln112@vendor10.example.com", "fn113.ln113@vendor8.example.com",
                "fn114.ln114@vendor9.example.com", "fn115.ln115@vendor3.example.com", "fn116.ln116@vendor6.example.com",
                "fn117.ln117@vendor2.example.com", "fn118.ln118@vendor9.example.com", "fn119.ln119@vendor9.example.com",
                null, "fn121.ln121@vendor8.example.com", "fn122.ln122@vendor3.example.com",
                "fn123.ln123@vendor8.example.com", "fn124.ln124@vendor4.example.com", "fn125.ln125@vendor5.example.com",
                "fn126.ln126@vendor2.example.com", "fn127.ln127@vendor4.example.com", "fn128.ln128@vendor1.example.com",
                "fn129.ln129@vendor4.example.com", "fn130.ln130@vendor8.example.com", "fn131.ln131@vendor10.example.com",
                null, "fn133.ln133@vendor3.example.com", "fn134.ln134@vendor8.example.com",
                "fn135.ln135@vendor4.example.com", "fn136.ln136@vendor7.example.com", "fn137.ln137@vendor1.example.com",
                "fn138.ln138@vendor3.example.com", "fn139.ln139@vendor2.example.com", "fn140.ln140@vendor5.example.com",
                "fn141.ln141@vendor9.example.com", "fn142.ln142@vendor8.example.com", "fn143.ln143@vendor10.example.com",
                null, "fn145.ln145@vendor8.example.com", "fn146.ln146@vendor10.example.com",
                "fn147.ln147@vendor3.example.com", "fn148.ln148@vendor9.example.com", "fn149.ln149@vendor2.example.com",
                "fn150.ln150@vendor3.example.com", "fn151.ln151@vendor5.example.com", "fn152.ln152@vendor5.example.com",
                "fn153.ln153@vendor6.example.com", "fn154.ln154@vendor9.example.com", "fn155.ln155@vendor4.example.com",
                null, "fn157.ln157@vendor3.example.com", "fn158.ln158@vendor9.example.com",
                "fn159.ln159@vendor8.example.com", "fn160.ln160@vendor9.example.com", "fn161.ln161@vendor5.example.com",
                "fn162.ln162@vendor10.example.com", "fn163.ln163@vendor8.example.com", "fn164.ln164@vendor6.example.com",
                "fn165.ln165@vendor2.example.com", "fn166.ln166@vendor3.example.com", "fn167.ln167@vendor7.example.com",
                "fn169.ln169@vendor8.example.com", "fn170.ln170@vendor5.example.com", "fn171.ln171@vendor4.example.com",
                "fn172.ln172@vendor5.example.com", "fn173.ln173@vendor3.example.com", "fn174.ln174@vendor8.example.com",
                "fn175.ln175@vendor2.example.com", "fn176.ln176@vendor8.example.com", "fn177.ln177@vendor7.example.com",
                "fn178.ln178@vendor9.example.com", "fn179.ln179@vendor10.example.com", null,
                "fn181.ln181@vendor6.example.com", "fn182.ln182@vendor4.example.com", "fn183.ln183@vendor2.example.com",
                "fn184.ln184@vendor3.example.com", "fn185.ln185@vendor9.example.com", "fn186.ln186@vendor3.example.com",
                "fn187.ln187@vendor9.example.com", "fn188.ln188@vendor8.example.com", "fn189.ln189@vendor2.example.com",
                "fn190.ln190@vendor10.example.com", "fn191.ln191@vendor3.example.com", null,
                "fn193.ln193@vendor9.example.com", "fn194.ln194@vendor1.example.com", "fn195.ln195@vendor7.example.com",
                "fn196.ln196@vendor3.example.com", "fn197.ln197@vendor5.example.com", "fn198.ln198@vendor2.example.com",
                "fn199.ln199@vendor4.example.com", "fn200.ln200@vendor6.example.com", "fn201.ln201@vendor4.example.com",
                "fn202.ln202@vendor1.example.com", "fn203.ln203@vendor2.example.com", null,
                "fn205.ln205@vendor10.example.com", "fn206.ln206@vendor3.example.com", "fn207.ln207@vendor5.example.com",
                "fn208.ln208@vendor6.example.com", "fn209.ln209@vendor9.example.com", "fn210.ln210@vendor5.example.com",
                "fn211.ln211@vendor9.example.com", "fn212.ln212@vendor1.example.com", "fn213.ln213@vendor1.example.com",
                "fn214.ln214@vendor6.example.com", "fn215.ln215@vendor10.example.com", null,
                "fn217.ln217@vendor6.example.com", "fn218.ln218@vendor5.example.com", "fn219.ln219@vendor8.example.com",
                "fn220.ln220@vendor4.example.com", "fn221.ln221@vendor7.example.com", "fn222.ln222@vendor2.example.com",
                "fn223.ln223@vendor8.example.com", "fn224.ln224@vendor10.example.com", "fn225.ln225@vendor10.example.com",
                "fn226.ln226@vendor6.example.com", "fn227.ln227@vendor6.example.com", null,
                "fn229.ln229@vendor1.example.com", "fn230.ln230@vendor6.example.com", "fn231.ln231@vendor5.example.com",
                "fn232.ln232@vendor3.example.com", "fn233.ln233@vendor10.example.com", "fn235.ln235@vendor7.example.com",
                "fn236.ln236@vendor4.example.com", "fn237.ln237@vendor5.example.com", "fn238.ln238@vendor7.example.com",
                "fn239.ln239@vendor5.example.com", null, "fn241.ln241@vendor5.example.com",
                "fn242.ln242@vendor1.example.com", "fn243.ln243@vendor4.example.com", "fn244.ln244@vendor10.example.com",
                "fn245.ln245@vendor3.example.com", "fn246.ln246@vendor3.example.com", "fn247.ln247@vendor3.example.com",
                "fn248.ln248@vendor3.example.com", "fn249.ln249@vendor4.example.com", "fn250.ln250@vendor8.example.com",
                "fn251.ln251@vendor10.example.com", null, "fn253.ln253@vendor8.example.com",
                "fn254.ln254@vendor6.example.com", "fn255.ln255@vendor5.example.com", "fn256.ln256@vendor2.example.com",
                "fn257.ln257@vendor1.example.com", "fn258.ln258@vendor9.example.com", "fn259.ln259@vendor6.example.com",
                "fn260.ln260@vendor3.example.com", "fn261.ln261@vendor7.example.com", "fn262.ln262@vendor10.example.com",
                "fn263.ln263@vendor1.example.com", null, "fn265.ln265@vendor10.example.com",
                "fn266.ln266@vendor3.example.com", "fn267.ln267@vendor8.example.com", "fn268.ln268@vendor9.example.com",
                "fn269.ln269@vendor8.example.com", "fn270.ln270@vendor7.example.com", "fn271.ln271@vendor1.example.com",
                "fn272.ln272@vendor3.example.com", "fn273.ln273@vendor10.example.com", "fn274.ln274@vendor7.example.com",
                "fn275.ln275@vendor8.example.com", null, "fn277.ln277@vendor4.example.com",
                "fn278.ln278@vendor5.example.com", "fn279.ln279@vendor10.example.com", "fn280.ln280@vendor2.example.com",
                "fn281.ln281@vendor2.example.com", "fn282.ln282@vendor4.example.com", "fn283.ln283@vendor2.example.com",
                "fn284.ln284@vendor3.example.com", "fn285.ln285@vendor3.example.com", "fn286.ln286@vendor5.example.com",
                "fn287.ln287@vendor7.example.com", null, "fn289.ln289@vendor8.example.com",
                "fn290.ln290@vendor1.example.com", "fn291.ln291@vendor8.example.com", "fn292.ln292@vendor1.example.com",
                "fn293.ln293@vendor3.example.com", "fn294.ln294@vendor8.example.com", "fn295.ln295@vendor7.example.com",
                "fn296.ln296@vendor4.example.com", "fn297.ln297@vendor1.example.com", "fn298.ln298@vendor7.example.com",
                "fn299.ln299@vendor9.example.com", null };

        // given
        final List<ManagedObject> managedObjects = _NullSafe.stream(DATA)
                .map(sampleString->
                    sampleString!=null
                        ? ManagedObject.value(stringSpec, sampleString)
                        : ManagedObject.empty(stringSpec))
                .collect(Collectors.toList());

        // when, then ... if broken throws java.lang.IllegalArgumentException: Comparison method violates its general contract!
        managedObjects.sort(MmSortUtils.NATURAL_NULL_FIRST);
    }

}
