/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 * 
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.modules.autoupdate;

import java.util.*;

import org.openide.util.NbBundle;
import javax.swing.SwingUtilities;
import org.openide.awt.StatusDisplayer;
import org.openide.util.RequestProcessor;

/** This class checks for updates in given period
 *
 * @author  Petr Hrebejk, Jiri Rechtacek
 */
public class AutoChecker extends Object
            implements Runnable, Wizard.Validator {
        
    /** Settings of autoupdate module */
    private Settings settings;

    /** Updates build by check */
    private Updates updates;
 
    /** pairs AutoupdateType, Updates */
    private HashMap allUpdates = new HashMap();
    
    // ==================================================================
    // This part is temporary

    static AutoChecker autoChecker;
    
    private RequestProcessor.Task regularlyCheck = null;    
    static final RequestProcessor REGULARLY_CHECK_TIMER = 
        new RequestProcessor("auto-checker-reqularly-timer", 1, true); // NOI18N
    
    private boolean canceled = false;
    
    static void doCheck() {
        autoChecker.run();
    }

    // ==================================================================


    /** Creates new AutoChecker */
    AutoChecker() {
        settings = Settings.getShared();
    }

    /** Installs this class into update support in NetBeans 3.0 implementation */
    void install() {
        Autoupdater.installUpdateChecker( this );

        // ==================================================================
        // This part is temporary
        autoChecker = this;
        // ==================================================================

    }
    
    // Implementation of UpdateSupport.UpdateChecker

    public void run() {
        canceled = false;
        
        // XXX needs to be in event thread, right?
        if (! SwingUtilities.isEventDispatchThread()) {
            SwingUtilities.invokeLater(this);
            return;
        }

        if ( !timeToCheck() ) {
            return;
        }
        
        if ( settings.isAskBefore() ) {
            AutoCheckInfo info = new AutoCheckInfo(
                                     NbBundle.getMessage( AutoChecker.class, "MSG_AutoCheck_Before" ),
                                     javax.swing.JOptionPane.INFORMATION_MESSAGE
                                 );
            if (!info.showDialog(true)) {
                return;
            }
        }
        
        RequestProcessor.getDefault ().post (new Runnable () {
            public void run () {
                runInner ();
            }
        });
    }
    
    void runInner () {
        
        //Autoupdater.setRunning( true );
        Wizard.resetErrorStore ();

        StatusDisplayer.getDefault().setStatusText( NbBundle.getMessage( AutoChecker.class, "CTL_Checking_StatusText" ) );
            
        Enumeration en = AutoupdateType.autoupdateTypes();
        int countOfServer = 0, countOfConnectedServer = 0;
        while (en.hasMoreElements()) {
            AutoupdateType at = (AutoupdateType)en.nextElement();
            if (at.isEnabled()) {
                countOfServer++;
                updates = at.connectForUpdates();
                updates.checkUpdates( this, at, true );
                int res = Wizard.checkConnect(updates, at);
                if (res == ConnectingDialog.OK && (updates.getTimeStamp () == null || at.getLastTimeStamp () == null ||
                           at.getLastTimeStamp ().before(updates.getTimeStamp ()))) {
                    allUpdates.put(at, updates);
                    countOfConnectedServer++;
                } else if (res == ConnectingDialog.SKIP && Wizard.getStoredErrorType () == Updates.NO_AVAILABLE_MODULES) {
                    countOfConnectedServer++;
                }
            }
        }
        
        boolean success = true;
        
        if ( countOfConnectedServer == 0 ) {
            //Autoupdater.setRunning( false );
            StatusDisplayer.getDefault().setStatusText( "" );
            canceled = true;
            if (Wizard.isErrorStored () && Updates.NO_NETWORK == Wizard.getStoredErrorType ()) {
                notifyError (Updates.NO_NETWORK);
                success = false;
            } else if (countOfServer == 0) {
                // we can ignore the NO_SERVER_ERROR, an user knows why disabled all servers => don't force to enable some
                //notifyError (Updates.NO_SERVER_ERROR);
            } else if (settings.isNegativeResults ()) {
                notifyNoUpdatesFound ();
            }
            return;
        }
        
        reportResults (success);
    }

    void reportResults (boolean success) {
        //Autoupdater.setRunning( false );
        
        StatusDisplayer.getDefault().setStatusText( "" );
        
        if (success) {
            settings.setLastCheck (new Date ());
        }
        
        if (canceled || ! success) {
            return;
        }

        // First af all check wether the XML has changed
        if ( allUpdates.size() == 0 ) {
            // Report it if necessary
            if ( settings.isNegativeResults() ) {
                notifyNoUpdatesFound ();
            }
            return;
        }


        if (getAllModules () != null && getAllModules ().size () > 0) {
            notifyUpdates ();
        } else if ( settings.isNegativeResults() ) {
            // No modules found and we have to report negative results
            notifyNoUpdatesFound ();
        }
        
    }
    
    private void notifyUpdates () {
        // Some modules found
        Runnable onMouseClick = new Runnable () {
            public void run () {
                Wizard.go( allUpdates );
            }
        };
        AvailableUpdateVisualizerProvider.UpdatesFlasher flasher = AvailableUpdateVisualizerProvider.getFlasher (onMouseClick);
        assert flasher != null : "Updates Flasher cannot be null.";
        flasher.setToolTipText (NbBundle.getMessage (AutoChecker.class, "MSG_AutoCheck_Found_ToolTip"));
        flasher.startFlashing();
    }
    
    private void notifyNoUpdatesFound () {
        // Some modules found
        Runnable onMouseClick = new Runnable () {
            public void run () {
                noUpdatesFound ();
            }
        };
        ProblemsVisualizerProvider.UpdatesFlasher flasher = ProblemsVisualizerProvider.getFlasher (onMouseClick);
        assert flasher != null : "Updates Flasher cannot be null.";
        flasher.setToolTipText (NbBundle.getMessage (AutoChecker.class, "MSG_AutoCheck_Problem"));
        flasher.startFlashing();
    }
    
    private void notifyError (final int errorType) {
        // Some modules found
        Runnable onMouseClick = new Runnable () {
            public void run () {
                ConnectingErrorDialog.showDialog (errorType, null, true);
            }
        };
        ProblemsVisualizerProvider.UpdatesFlasher flasher = ProblemsVisualizerProvider.getFlasher (onMouseClick);
        assert flasher != null : "Updates Flasher cannot be null.";
        flasher.setToolTipText (NbBundle.getMessage (AutoChecker.class, "MSG_AutoCheck_Problem"));
        flasher.startFlashing();
    }
    
    private void noUpdatesFound () {
        AutoCheckInfo info = new AutoCheckInfo(
                                 NbBundle.getMessage( AutoChecker.class, "MSG_AutoCheck_NotFound" ),
                                 javax.swing.JOptionPane.INFORMATION_MESSAGE
                             );
        info.showDialog(false);
    }

    private Collection getAllModules() {
        Set ret = new HashSet();
        Iterator it = allUpdates.values().iterator();
        while (it.hasNext()) {
            Collection c = ((Updates)it.next()).getModules();
            if ( c != null )
                ret.addAll( c );
        }
        return ret;
    }
    
    // Implementation of Wizard.Validator

    /** This method gets the notification that the updates is ready */

    public void setValid(boolean valid) {
    }

    // Utility methods --------------------------------------------------------

    /** This method decides whether to perform the check or not
    */
    private boolean timeToCheck() {
        if (getReqularlyTimerTask () != null) {
            // if time is off then is time to check
            if (getReqularlyTimerTask ().getDelay () <= 0 && getWaitPeriod () > 0) {
                // schedule next check
                getReqularlyTimerTask ().schedule (getWaitPeriod ());
                return true;
            }
        }
        
        // If this is the first time always check
        if ( settings.getLastCheck() == null ) {
            return true;
        }
        
        switch ( settings.getPeriod() ) {
        case Settings.EVERY_STARTUP:
            return true;
        case Settings.EVERY_NEVER:
            return false;
        default:
            Date lastCheck = settings.getLastCheck();
            GregorianCalendar calendar = new GregorianCalendar();
            calendar.setTime( lastCheck );

            calendar.set( Calendar.HOUR, 0 );
            calendar.set( Calendar.AM_PM, 0 );
            calendar.set( Calendar.MINUTE, 0 );
            calendar.set( Calendar.SECOND, 0 );
            calendar.set( Calendar.MILLISECOND, 0 );
            
            switch ( settings.getPeriod() ) {
            case Settings.EVERY_DAY:
                calendar.add( GregorianCalendar.DATE, 1 );
                break;
            case Settings.EVERY_WEEK:
                calendar.add( GregorianCalendar.WEEK_OF_YEAR, 1 );
                break;
            case Settings.EVERY_2WEEKS:
                calendar.add( GregorianCalendar.WEEK_OF_YEAR, 2 );
                break;
            case Settings.EVERY_MONTH:
                calendar.add( GregorianCalendar.MONTH, 1 );
                break;
            }
            
            return calendar.getTime().before( new Date() );

        }
    }
    
    private RequestProcessor.Task getReqularlyTimerTask () {
        if (regularlyCheck == null) {
            // only for ordinary periods
            if (getWaitPeriod () > 0) {
                int waitPeriod = getWaitPeriod ();
                int restTime = waitPeriod;
                // calculate rest time to check
                if (settings.getLastCheck () != null) {
                    restTime = waitPeriod - (int)(System.currentTimeMillis () - settings.getLastCheck ().getTime ());
                }
                
                // if restTime < 0 then schedule next round by given period
                if (restTime <= 0) {
                    restTime = waitPeriod;
                }
                
                regularlyCheck = REGULARLY_CHECK_TIMER.post (this, restTime, Thread.MIN_PRIORITY);
                
            }
        }
        return regularlyCheck;
    }

    private int getWaitPeriod () {
        switch (settings.getPeriod ()) {
            case Settings.EVERY_NEVER:
                return 0;
            case Settings.EVERY_STARTUP:
                return 0;
            case Settings.EVERY_DAY:
                return 1000 * 3600 * 24;
            case Settings.EVERY_WEEK:
                return 1000 * 3600 * 24 * 7;
            case Settings.EVERY_2WEEKS:
                return 1000 * 3600 * 24 * 14;
            case Settings.EVERY_MONTH:
                return Integer.MAX_VALUE; // 1000 * 3600 * 24 * 28 is close but too big 
            default:
                return 0;
        }
    }
    
}
