/* 
    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.wiki.plugin;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.apache.wiki.WikiContext;
import org.apache.wiki.WikiEngine;
import org.apache.wiki.WikiPage;
import org.apache.wiki.api.exceptions.PluginException;
import org.apache.wiki.api.plugin.WikiPlugin;
import org.apache.wiki.attachment.Attachment;
import org.apache.wiki.i18n.InternationalizationManager;
import org.apache.wiki.preferences.Preferences;
import org.apache.wiki.preferences.Preferences.TimeFormat;
import org.apache.wiki.util.TextUtil;
import org.apache.wiki.util.XHTML;
import org.apache.wiki.util.XhtmlUtil;
import org.jdom2.Element;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.Map;


/**
 *  Returns the Recent Changes in the wiki being a date-sorted list of page names.
 *
 *  <p>Parameters: </p>
 *  <ul>
 *  <li><b>since</b> - show changes from the last n days, for example since=5 shows only the pages that were changed in the last five days</li>
 *  <li><b>format</b> - (full|compact) : if "full", then display a long version with all possible info. If "compact", then be as compact as possible.</li>
 *  <li><b>timeFormat</b> - the time format to use, the default is "HH:mm:ss"</li>
 *  <li><b>dateFormat</b> - the date format to use, the default is "dd.MM.yyyy"</li>
 *  </ul>
 */
public class RecentChangesPlugin extends AbstractReferralPlugin implements WikiPlugin {
	
    private static final Logger log = Logger.getLogger( RecentChangesPlugin.class );
    
    /** Parameter name for the separator format.  Value is <tt>{@value}</tt>. */
    public static final String PARAM_FORMAT      = "format";
    /** Parameter name for the separator timeFormat.  Value is <tt>{@value}</tt>. */
    public static final String PARAM_TIME_FORMAT = "timeFormat";
    /** Parameter name for the separator dateFormat.  Value is <tt>{@value}</tt>. */
    public static final String PARAM_DATE_FORMAT = "dateFormat";

    /** How many days we show by default. */
    private static final int   DEFAULT_DAYS = 100*365;
    public static final String DEFAULT_TIME_FORMAT ="HH:mm:ss";
    public static final String DEFAULT_DATE_FORMAT ="dd.MM.yyyy";


    /**
     * {@inheritDoc}
     */
    public String execute( WikiContext context, Map<String, String> params ) throws PluginException {
        int since = TextUtil.parseIntParameter( params.get( "since" ), DEFAULT_DAYS );
        String   spacing  = "4";
        boolean  showAuthor = true;
        boolean  showChangenote = true;
        String   tablewidth = "4";
        
        WikiEngine engine = context.getEngine();

        //
        //  Which format we want to see?
        //
        if( "compact".equals( params.get(PARAM_FORMAT) ) ) {
            spacing  = "0";
            showAuthor = false;
            showChangenote = false;
            tablewidth = "2";
        }

        Calendar sincedate = new GregorianCalendar();
        sincedate.add( Calendar.DAY_OF_MONTH, -since );

        log.debug("Calculating recent changes from "+sincedate.getTime());

        // FIXME: Should really have a since date on the getRecentChanges method.
        Collection< WikiPage > changes = engine.getRecentChanges();
        super.initialize( context, params );
        changes = filterWikiPageCollection( changes );
        
        if ( changes != null ) {
            Date olddate = new Date( 0 );

            DateFormat fmt = getDateFormat( context, params );
            DateFormat tfmt = getTimeFormat( context, params );

            Element rt = XhtmlUtil.element( XHTML.table );
            rt.setAttribute( XHTML.ATTR_class, "recentchanges" );
            rt.setAttribute( XHTML.ATTR_cellpadding, spacing );
        
            for( Iterator< WikiPage > i = changes.iterator(); i.hasNext(); ) {
                WikiPage pageref = i.next();
                Date lastmod = pageref.getLastModified();

                if( lastmod.before( sincedate.getTime() ) ) {
                    break;
                }
                
                if( !isSameDay( lastmod, olddate ) ) {
                    Element row = XhtmlUtil.element( XHTML.tr );
                    Element col = XhtmlUtil.element( XHTML.td );
                    col.setAttribute( XHTML.ATTR_colspan, tablewidth );
                    col.setAttribute( XHTML.ATTR_class, "date" );
                    col.addContent( XhtmlUtil.element( XHTML.b, fmt.format( lastmod ) ) );

                    rt.addContent( row );
                    row.addContent( col );
                    olddate = lastmod;
                }

                String href = context.getURL( pageref instanceof Attachment ? WikiContext.ATTACH : WikiContext.VIEW, 
                                              pageref.getName() ) ;

                Element link = XhtmlUtil.link( href, engine.beautifyTitle( pageref.getName() ) );

                Element row = XhtmlUtil.element( XHTML.tr );
                Element col = XhtmlUtil.element( XHTML.td );
                col.setAttribute( XHTML.ATTR_width, "30%" );
                col.addContent( link );
                
                //
                //  Add the direct link to the attachment info.
                //
                if( pageref instanceof Attachment ) {
                    link = XhtmlUtil.link( context.getURL( WikiContext.INFO, pageref.getName() ), null );
                    link.setAttribute( XHTML.ATTR_class, "infolink" );

                    Element img = XhtmlUtil.img( context.getURL( WikiContext.NONE, "images/attachment_small.png" ), null );
                    link.addContent( img );

                    col.addContent( link );
                }

                row.addContent( col );
                rt.addContent( row );
                
                if( pageref instanceof Attachment ) {
                    Element td = XhtmlUtil.element( XHTML.td, tfmt.format( lastmod ) );
                    td.setAttribute( XHTML.ATTR_class, "lastchange" );
                    row.addContent( td );
                } else {
                    Element infocol = XhtmlUtil.element( XHTML.td );
                    infocol.setAttribute( XHTML.ATTR_class, "lastchange" );
                    infocol.addContent( XhtmlUtil.link( context.getURL( WikiContext.DIFF, pageref.getName(), "r1=-1" ), tfmt.format( lastmod ) ) );
                    row.addContent( infocol );
                }

                //
                //  Display author information.
                //

                if( showAuthor ) {
                    String author = pageref.getAuthor();

                    Element authorinfo = XhtmlUtil.element( XHTML.td );
                    authorinfo.setAttribute( XHTML.ATTR_class, "author" );
                    
                    if( author != null ) {
                        if( engine.pageExists( author ) ) {
                            authorinfo.addContent( XhtmlUtil.link( context.getURL( WikiContext.VIEW, author ), author ) );
                        } else {
                            authorinfo.addContent( author );
                        }
                    } else {
                        authorinfo.addContent( Preferences.getBundle( context, InternationalizationManager.CORE_BUNDLE )
                                                          .getString( "common.unknownauthor" ) );
                    }

                    row.addContent( authorinfo );
                }

                // Change note
                if( showChangenote ) {
                    String changenote = ( String )pageref.getAttribute( WikiPage.CHANGENOTE );
                    Element td_changenote = XhtmlUtil.element( XHTML.td, changenote );
                    td_changenote.setAttribute( XHTML.ATTR_class, "changenote" );
                    row.addContent( td_changenote );
                }
                
                //  Revert note
/*                
                if( context.hasAdminPermissions() )
                {
                    row.addElement( new td("Revert") );
                }
 */
            }
            return XhtmlUtil.serialize( rt, XhtmlUtil.EXPAND_EMPTY_NODES );
        }
        return "";
    }

    
    private boolean isSameDay( Date a, Date b ) {
        Calendar aa = Calendar.getInstance(); aa.setTime( a );
        Calendar bb = Calendar.getInstance(); bb.setTime( b );

        return aa.get( Calendar.YEAR ) == bb.get( Calendar.YEAR ) 
            && aa.get( Calendar.DAY_OF_YEAR ) == bb.get( Calendar.DAY_OF_YEAR );
    }
    

    // TODO: Ideally the default behavior should be to return the default format for the default
    // locale, but that is at odds with the 1st version of this plugin. We seek to preserve the
    // behaviour of that first version, so to get the default format, the user must explicitly do
    // something like: dateFormat='' timeformat='' which is a odd, but probably okay.
    private DateFormat getTimeFormat( WikiContext context, Map<String, String> params ) {
        String formatString = get( params, DEFAULT_TIME_FORMAT, PARAM_TIME_FORMAT );

        if( StringUtils.isBlank( formatString ) ) {
            return Preferences.getDateFormat( context, TimeFormat.TIME );
        }

        return new SimpleDateFormat( formatString );
    }

    private DateFormat getDateFormat( WikiContext context, Map< String, String > params ) {
        String formatString = get( params, DEFAULT_DATE_FORMAT, PARAM_DATE_FORMAT );
        
        if( StringUtils.isBlank( formatString ) ) {
            return Preferences.getDateFormat( context, TimeFormat.DATE );
        }

        return new SimpleDateFormat( formatString );
    }
    
    private String get( Map< String, String > params, String defaultValue, String paramName ) {
        String value = params.get( paramName );
        return value == null ? defaultValue : value;
    }
    
}