package com.fasterxml.jackson.failing;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static org.hamcrest.CoreMatchers.instanceOf;

import static org.junit.Assert.assertThat;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.testutil.NoCheckSubTypeValidator;

// see [https://github.com/FasterXML/jackson-core/issues/384]: most likely
// can not be fixed, but could we improve error message to indicate issue
// with non-static type of `Car` and `Truck`, which prevent instantiation?
public class InnerClassNonStaticCore384Test extends BaseMapTest
{
    static class Fleet {
        private List<Vehicle> vehicles;

        public List<Vehicle> getVehicles() {
            return vehicles;
        }

        public void setVehicles(List<Vehicle> vehicles) {
            this.vehicles = vehicles;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Fleet fleet = (Fleet) o;
            return Objects.equals(vehicles, fleet.vehicles);
        }

        @Override
        public int hashCode() {
            return Objects.hash(vehicles);
        }
    }

    static abstract class Vehicle {
        private String make;
        private String model;

        protected Vehicle(String make, String model) {
            this.make = make;
            this.model = model;
        }

        public Vehicle() {
        }

        public String getMake() {
            return make;
        }

        public void setMake(String make) {
            this.make = make;
        }

        public String getModel() {
            return model;
        }

        public void setModel(String model) {
            this.model = model;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Vehicle)) return false;
            Vehicle vehicle = (Vehicle) o;
            return Objects.equals(make, vehicle.make) &&
                    Objects.equals(model, vehicle.model);
        }

        @Override
        public int hashCode() {
            return Objects.hash(make, model);
        }
    }

    class Car extends Vehicle {
        private int seatingCapacity;
        private double topSpeed;

        public Car(String make, String model, int seatingCapacity, double topSpeed) {
            super(make, model);
            this.seatingCapacity = seatingCapacity;
            this.topSpeed = topSpeed;
        }

        public Car() {
        }

        public int getSeatingCapacity() {
            return seatingCapacity;
        }

        public void setSeatingCapacity(int seatingCapacity) {
            this.seatingCapacity = seatingCapacity;
        }

        public double getTopSpeed() {
            return topSpeed;
        }

        public void setTopSpeed(double topSpeed) {
            this.topSpeed = topSpeed;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            if (!super.equals(o)) return false;
            Car car = (Car) o;
            return seatingCapacity == car.seatingCapacity &&
                    Double.compare(car.topSpeed, topSpeed) == 0;
        }

        @Override
        public int hashCode() {
            return Objects.hash(super.hashCode(), seatingCapacity, topSpeed);
        }
    }

    class Truck extends Vehicle {
        private double payloadCapacity;

        public Truck(String make, String model, double payloadCapacity) {
            super(make, model);
            this.payloadCapacity = payloadCapacity;
        }

        public Truck() {
        }

        public double getPayloadCapacity() {
            return payloadCapacity;
        }

        public void setPayloadCapacity(double payloadCapacity) {
            this.payloadCapacity = payloadCapacity;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            if (!super.equals(o)) return false;
            Truck truck = (Truck) o;
            return Double.compare(truck.payloadCapacity, payloadCapacity) == 0;
        }

        @Override
        public int hashCode() {
            return Objects.hash(super.hashCode(), payloadCapacity);
        }
    }

    /*
    /**********************************************************************
    /* Test methods
    /**********************************************************************
     */

    public void testHierarchy() throws IOException {
        ObjectMapper mapper = jsonMapperBuilder()
                .activateDefaultTyping(NoCheckSubTypeValidator.instance)
                .build();

        Fleet fleet = initVehicle();

        /*
for (Vehicle v : fleet.vehicles) {
    System.out.println("Vehicle, type: "+v.getClass());
}
*/
        String serializedFleet = mapper
                .writerWithDefaultPrettyPrinter()
                .writeValueAsString(fleet);

//System.out.println(serializedFleet);

        Fleet deserializedFleet = mapper.readValue(serializedFleet, Fleet.class);

        assertThat(deserializedFleet.getVehicles().get(0), instanceOf(Car.class));
        assertThat(deserializedFleet.getVehicles().get(1), instanceOf(Truck.class));

        assertEquals(fleet, deserializedFleet);
    }

    private Fleet initVehicle() {
        Car car = new Car("Mercedes-Benz", "S500", 5, 250.0);
        Truck truck = new Truck("Isuzu", "NQR", 7500.0);

        List<Vehicle> vehicles = new ArrayList<>();
        vehicles.add(car);
        vehicles.add(truck);

        Fleet serializedFleet = new Fleet();
        serializedFleet.setVehicles(vehicles);
        return serializedFleet;
    }
}
