/**
 *
 * Copyright 2009-2010 Rickard Öberg AB
 *
 * 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.library.eventsourcing.domain.rest.server;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.qi4j.api.injection.scope.Service;
import org.qi4j.api.service.qualifier.Tagged;
import org.qi4j.functional.Iterables;
import org.qi4j.io.Outputs;
import org.qi4j.library.eventsourcing.domain.api.UnitOfWorkDomainEventsValue;
import org.qi4j.library.eventsourcing.domain.source.EventSource;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.data.CharacterSet;
import org.restlet.data.MediaType;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.ext.atom.*;
import org.restlet.representation.StringRepresentation;
import org.restlet.representation.WriterRepresentation;
import org.restlet.resource.ResourceException;

import static org.qi4j.functional.Iterables.iterable;

/**
 * Get events in various formats.
 * <p>
 * The feed is paged, with one
 * current set page, one working set page, and the rest being archive pages that never change. The links "next", "previous",
 * "first" and "last" are used as expected per the Atom spec.
 * </p>
 * <pre><code>
 * / = current set of most recent events (event range: count-pagesize to count)
 * /n,m = events from index n to index m. These are archive pages.
 * /n = working set page, where n is the first event index to be presented
 * </code></pre>
 */
public class DomainEventSourceResource
        extends Restlet
{
    EventSource source;

    public DomainEventSourceResource( @Service @Tagged("domain") EventSource source )
    {
        this.source = source;
    }

    @Override
    public void handle( Request request, Response response )
    {
        long eventCount = source.count();
        long pageSize = 10;
        long startEvent = -1;
        long endEvent = -1;
        long limit = pageSize;

        final List<UnitOfWorkDomainEventsValue> eventsValues = new ArrayList<UnitOfWorkDomainEventsValue>();

        final Feed feed = new Feed();
        feed.setBaseReference( request.getResourceRef().getParentRef() );
        List<Link> links = feed.getLinks();

        String remainingPart = request.getResourceRef().getRemainingPart();
        if (remainingPart.equals( "/" ))
        {
            // Current set - always contains the last "pageSize" events
            startEvent = Math.max( 0, eventCount - pageSize - 1 );

            feed.setTitle( new Text( "Current set" ) );

            if (startEvent > 0)
            {
                long previousStart = Math.max(0, startEvent-pageSize);
                long previousEnd = startEvent-1;

                Link link = new Link( new Reference( previousStart+","+previousEnd ), new Relation( "previous" ), MediaType.APPLICATION_ATOM );
                link.setTitle( "Previous page" );
                links.add( link );
            }

        } else
        {
            // Archive
            String[] indices = remainingPart.substring(1).split( "," );

            if (indices.length == 1)
            {
                // Working set
                startEvent = Long.parseLong( indices[0] );
                endEvent = startEvent + pageSize - 1;
                limit = pageSize;
                feed.setTitle( new Text("Working set") );
            } else if (indices.length == 2)
            {
                feed.setTitle( new Text("Archive page") );
                startEvent = Long.parseLong( indices[0] );
                endEvent = Long.parseLong( indices[1] );
                limit = 1+endEvent-startEvent;

            } else
                throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND );

            if (startEvent > 0)
            {
                long previousStart = Math.max(0, startEvent-pageSize);
                long previousEnd = startEvent-1;

                Link link = new Link( new Reference( previousStart+","+previousEnd ), new Relation( "previous" ), MediaType.APPLICATION_ATOM );
                link.setTitle( "Previous page" );
                links.add( link );
            }

            long nextStart = endEvent+1;
            long nextEnd = nextStart+pageSize-1;

            if (nextStart < eventCount)
                if (nextEnd >= eventCount)
                {
                    Link next = new Link( new Reference( nextStart+"" ), new Relation( "next" ), MediaType.APPLICATION_ATOM );
                    next.setTitle( "Working set" );
                    links.add( next );
                } else
                {
                    Link next = new Link( new Reference( nextStart+","+nextEnd ), new Relation( "next" ), MediaType.APPLICATION_ATOM );
                    next.setTitle( "Next page" );
                    links.add( next );
                }
        }

        try
        {
            source.events( startEvent, limit ).transferTo( Outputs.collection( eventsValues ) );
        } catch (Throwable throwable)
        {
            throw new ResourceException( Status.SERVER_ERROR_INTERNAL, throwable );
        }

        Link last = new Link( new Reference( "0,"+(pageSize-1) ), new Relation( "last" ), MediaType.APPLICATION_ATOM );
        last.setTitle( "Last archive page" );
        links.add( last );

        Link first = new Link( new Reference( "." ), new Relation( "first" ), MediaType.APPLICATION_ATOM );
        first.setTitle( "Current set" );
        links.add( first );

/*
        if (previousPage != -1)
        {
            Link link = new Link( new Reference( ""+previousPage ), new Relation( "prev-archive" ), MediaType.APPLICATION_ATOM );
            link.setTitle( "Previous archive page" );
            links.add( link );
        }
        if (nextPage != -1)
        {
            Link link = new Link( new Reference( "" + nextPage ), new Relation( "next-archive" ), MediaType.APPLICATION_ATOM );
            link.setTitle( "Next archive page" );
            links.add( link );
        }
        else if (startEvent != workingSetOffset)
        {
            Link next = new Link( new Reference( "" ), new Relation( "next" ), MediaType.APPLICATION_ATOM );
            next.setTitle( "Next page" );
            links.add( next );
        }
*/

        Date lastModified = null;
        for (UnitOfWorkDomainEventsValue eventsValue : eventsValues)
        {
            Entry entry = new Entry();
            entry.setTitle( new Text( eventsValue.usecase().get() + "(" + eventsValue.user().get() + ")" ) );
            entry.setPublished( new Date( eventsValue.timestamp().get() ) );
            entry.setModificationDate( lastModified = new Date( eventsValue.timestamp().get() ) );
            entry.setId( Long.toString( startEvent + 1 ) );
            startEvent++;
            Content content = new Content();
            content.setInlineContent( new StringRepresentation( eventsValue.toString(), MediaType.APPLICATION_JSON ) );
            entry.setContent( content );
            feed.getEntries().add( entry );
        }

        feed.setModificationDate( lastModified );

        MediaType mediaType = request.getClientInfo().getPreferredMediaType( Iterables.toList( iterable( MediaType.TEXT_HTML, MediaType.APPLICATION_ATOM ) ));

        if (MediaType.APPLICATION_ATOM.equals( mediaType ))
        {
            WriterRepresentation representation = new WriterRepresentation( MediaType.APPLICATION_ATOM )
            {
                @Override
                public void write( final Writer writer ) throws IOException
                {
                    feed.write( writer );
                }
            };
            representation.setCharacterSet( CharacterSet.UTF_8 );
            response.setEntity( representation );
        } else
        {
            WriterRepresentation representation = new WriterRepresentation(MediaType.TEXT_HTML)
            {
                @Override
                public void write( Writer writer ) throws IOException
                {
                    writer.append( "<html><head><title>Events</title></head><body>" );

                    for( Link link : feed.getLinks() )
                    {
                        writer.append( "<a href=\"").append( link.getHref().getPath()).append( "\">" );
                        writer.append( link.getTitle() );
                        writer.append( "</a><br/>" );
                    }

                    writer.append( "<ol>" );
                    for( Entry entry : feed.getEntries() )
                    {
                        writer.append( "<li>" ).append( entry.getTitle().toString() ).append( "</li>" );
                    }
                    writer.append( "</ol></body>" );
                }
            };
            representation.setCharacterSet( CharacterSet.UTF_8 );
            response.setEntity( representation );
        }

/*
        } else
        {
            throw new ResourceException( Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE );
        }
*/
    }
}
