/*
 * Copyright 2011 Marc Grue.
 *
 * 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 org.qi4j.sample.dcicargo.sample_a.context.shipping.booking;

import java.util.Date;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.qi4j.api.unitofwork.UnitOfWork;
import org.qi4j.sample.dcicargo.sample_a.bootstrap.test.TestApplication;
import org.qi4j.sample.dcicargo.sample_a.context.support.FoundNoRoutesException;
import org.qi4j.sample.dcicargo.sample_a.data.entity.CargosEntity;
import org.qi4j.sample.dcicargo.sample_a.data.shipping.cargo.Cargo;
import org.qi4j.sample.dcicargo.sample_a.data.shipping.cargo.Cargos;
import org.qi4j.sample.dcicargo.sample_a.data.shipping.cargo.TrackingId;
import org.qi4j.sample.dcicargo.sample_a.data.shipping.delivery.Delivery;
import org.qi4j.sample.dcicargo.sample_a.data.shipping.delivery.RoutingStatus;
import org.qi4j.sample.dcicargo.sample_a.data.shipping.delivery.TransportStatus;
import org.qi4j.sample.dcicargo.sample_a.data.shipping.handling.HandlingEventType;
import org.qi4j.sample.dcicargo.sample_a.data.shipping.itinerary.Itinerary;
import org.qi4j.sample.dcicargo.sample_a.data.shipping.location.Location;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;

/**
 * Test of Book New Cargo use case.
 *
 * This is a test suite where all steps and deviations in the use case are tested.
 * Some data will carry over from one test to another (all tests run within the same UnitOfWork).
 *
 * Test method names describe the test purpose. The prefix refers to the step in the use case.
 */
public class BookNewCargoTest
      extends TestApplication
{

    private static final Date TODAY = new Date();

    @Before
    public void prepareTest()
        throws Exception
    {
        super.prepareTest();

    }

    @Test( expected = RouteException.class )
    public void deviation2a_OriginAndDestinationSame() throws Exception
    {
        UnitOfWork uow = module.currentUnitOfWork();
        Location HONGKONG = uow.get( Location.class, CNHKG.code().get() );
        Cargos CARGOS = uow.get( Cargos.class, CargosEntity.CARGOS_ID );
        new BookNewCargo( CARGOS, HONGKONG, HONGKONG, day( 17 ) ).book();
    }

    @Test( expected = RouteException.class )
    public void deviation_2b_1_DeadlineInThePastNotAccepted() throws Exception
    {
        UnitOfWork uow = module.currentUnitOfWork();
        Location HONGKONG = uow.get( Location.class, CNHKG.code().get() );
        Location STOCKHOLM = uow.get( Location.class, SESTO.code().get() );
        Cargos CARGOS = uow.get( Cargos.class, CargosEntity.CARGOS_ID );
        new BookNewCargo( CARGOS, HONGKONG, STOCKHOLM, day( -1 ) ).book();
    }

    @Test( expected = RouteException.class )
    public void deviation_2b_2_DeadlineTodayIsTooEarly() throws Exception
    {
        UnitOfWork uow = module.currentUnitOfWork();
        Location HONGKONG = uow.get( Location.class, CNHKG.code().get() );
        Location STOCKHOLM = uow.get( Location.class, SESTO.code().get() );
        Cargos CARGOS = uow.get( Cargos.class, CargosEntity.CARGOS_ID );
        new BookNewCargo( CARGOS, HONGKONG, STOCKHOLM, day( 0 ) ).book();
    }

    @Test
    public void deviation_2b_3_DeadlineTomorrowIsOkay() throws Exception
    {
        UnitOfWork uow = module.currentUnitOfWork();
        Location HONGKONG = uow.get( Location.class, CNHKG.code().get() );
        Location STOCKHOLM = uow.get( Location.class, SESTO.code().get() );
        Cargos CARGOS = uow.get( Cargos.class, CargosEntity.CARGOS_ID );
        new BookNewCargo( CARGOS, HONGKONG, STOCKHOLM, day( 1 ) ).book();
    }

    @Test
    public void step_2_CreateNewCargo() throws Exception
    {
        UnitOfWork uow = module.currentUnitOfWork();
        Location HONGKONG = uow.get( Location.class, CNHKG.code().get() );
        Location STOCKHOLM = uow.get( Location.class, SESTO.code().get() );
        Cargos CARGOS = uow.get( Cargos.class, CargosEntity.CARGOS_ID );
        // Create cargo with valid input from customer
        TrackingId trackingId = new BookNewCargo( CARGOS, HONGKONG, STOCKHOLM, day( 17 ) ).book();

        // Retrieve created cargo from store
        Cargo cargo = uow.get( Cargo.class, trackingId.id().get() );

        // Test cargo data
        assertThat( cargo.trackingId().get(), is( equalTo( trackingId ) ) );
        assertThat( cargo.origin().get(), is( equalTo( HONGKONG ) ) );

        // Test route specification
        assertThat( cargo.routeSpecification().get().destination().get(), is( equalTo( STOCKHOLM ) ) );
        // day(17) here is calculated a few milliseconds after initial day(17), so it will be later...
        assertThat( cargo.routeSpecification().get().arrivalDeadline().get(),  equalTo( day( 17 )  ));

        // (Itinerary is not assigned yet)

        // Test derived delivery snapshot
        Delivery delivery = cargo.delivery().get();
        assertThat( delivery.timestamp().get().after( TODAY ), is( equalTo( true ) ) ); // TODAY is set first
        assertThat( delivery.routingStatus().get(), is( equalTo( RoutingStatus.NOT_ROUTED ) ) );
        assertThat( delivery.transportStatus().get(), is( equalTo( TransportStatus.NOT_RECEIVED ) ) );
        assertThat( delivery.nextExpectedHandlingEvent().get().handlingEventType().get(), is( equalTo( HandlingEventType.RECEIVE ) ) );
        assertThat( delivery.nextExpectedHandlingEvent().get().location().get(), is( equalTo( HONGKONG ) ) );
        assertThat( delivery.nextExpectedHandlingEvent().get().voyage().get(), is( equalTo( null ) ) );
        assertThat( delivery.lastHandlingEvent().get(), is( equalTo( null ) ) );
        assertThat( delivery.lastKnownLocation().get(), is( equalTo( null ) ) );
        assertThat( delivery.currentVoyage().get(), is( equalTo( null ) ) );
        assertThat( delivery.eta().get(), is( equalTo( null ) ) ); // Is set when itinerary is assigned
        assertThat( delivery.isMisdirected().get(), is( equalTo( false ) ) );
        assertThat( delivery.isUnloadedAtDestination().get(), is( equalTo( false ) ) );
    }

    @Test( expected = FoundNoRoutesException.class )
    public void deviation_3a_NoRoutesCanBeThatFast() throws Exception
    {
        UnitOfWork uow = module.currentUnitOfWork();
        Location HONGKONG = uow.get( Location.class, CNHKG.code().get() );
        Location STOCKHOLM = uow.get( Location.class, SESTO.code().get() );
        Cargos CARGOS = uow.get( Cargos.class, CargosEntity.CARGOS_ID );
        TrackingId trackingId = new BookNewCargo( CARGOS, HONGKONG, STOCKHOLM, day( 1 ) ).book();
        Cargo cargo = uow.get( Cargo.class, trackingId.id().get() );

        // No routes will be found
        new BookNewCargo( cargo ).routeCandidates();
    }

    @Test
    public void step_3_CalculatePossibleRoutes() throws Exception
    {
        UnitOfWork uow = module.currentUnitOfWork();
        Location HONGKONG = uow.get( Location.class, CNHKG.code().get() );
        Location STOCKHOLM = uow.get( Location.class, SESTO.code().get() );
        Cargos CARGOS = uow.get( Cargos.class, CargosEntity.CARGOS_ID );

        // Create valid cargo
        TrackingId trackingId = new BookNewCargo( CARGOS, HONGKONG, STOCKHOLM, day( 30 ) ).book();
        Cargo cargo = uow.get( Cargo.class, trackingId.id().get() );

        // Step 3 - Find possible routes
        List<Itinerary> routeCandidates = new BookNewCargo( cargo ).routeCandidates();

        // Check possible routes
        for (Itinerary itinerary : routeCandidates)
        {
            assertThat( "First load location equals origin location.",
                        itinerary.firstLeg().loadLocation().get(),
                        is( equalTo( cargo.routeSpecification().get().origin().get() ) ) );
            assertThat( "Last unload location equals destination location.",
                        itinerary.lastLeg().unloadLocation().get(),
                        is( equalTo( cargo.routeSpecification().get().destination().get() ) ) );
            assertThat( "Cargo will be delivered in time.",
                        itinerary.finalArrivalDate().before( cargo.routeSpecification().get().arrivalDeadline().get() ),
                        is( equalTo( true ) ) );
        }
    }

    @Test
    public void step_5_AssignCargoToRoute() throws Exception
    {
        UnitOfWork uow = module.currentUnitOfWork();
        Location HONGKONG = uow.get( Location.class, CNHKG.code().get() );
        Location STOCKHOLM = uow.get( Location.class, SESTO.code().get() );
        Cargos CARGOS = uow.get( Cargos.class, CargosEntity.CARGOS_ID );

        // Create valid cargo
        Date deadline = day( 30 );
        TrackingId trackingId = new BookNewCargo( CARGOS, HONGKONG, STOCKHOLM, deadline ).book();
        Cargo cargo = uow.get( Cargo.class, trackingId.id().get() );

        List<Itinerary> routeCandidates = new BookNewCargo( cargo ).routeCandidates();

        // Get first route found
        // Would normally be found with an Itinerary id from customer selection
        Itinerary itinerary = routeCandidates.get( 0 );

        // Use case step 5 - System assigns cargo to route
        new BookNewCargo( cargo, itinerary ).assignCargoToRoute();

        assertThat( "Itinerary has been assigned to cargo.", itinerary, is( equalTo( cargo.itinerary().get() ) ) );

        // BuildDeliverySnapshot will check if itinerary is valid. No need to check it here.

        // Check values set in new delivery snapshot
        Delivery delivery = cargo.delivery().get();
        assertThat( delivery.routingStatus().get(), is( equalTo( RoutingStatus.ROUTED ) ) );

        // ETA (= Unload time of last Leg) is before Deadline (set in previous test)
        assertTrue( delivery.eta().get().before( deadline ) );
    }
}
