/*
 * Copyright 2009 the original author or authors.
 *
 * 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.gradle.openapi.wrappers.ui;

import org.gradle.gradleplugin.foundation.GradlePluginLord;
import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
import org.gradle.gradleplugin.foundation.request.Request;
import org.gradle.gradleplugin.userinterface.swing.generic.BasicGradleUI;
import org.gradle.openapi.external.foundation.GradleInterfaceVersion1;
import org.gradle.openapi.external.foundation.favorites.FavoritesEditorVersion1;
import org.gradle.openapi.external.ui.*;
import org.gradle.openapi.wrappers.foundation.favorites.FavoritesEditorWrapper;

import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.util.HashMap;
import java.util.Map;

/**
* Implementation of BasicGradleUI meant to help shield external users from internal changes.
* This also provides the basics for the UI regardless of whether the output is in a separate
 * pane or not.
 @author mhunsicker
*/
public abstract class AbstractOpenAPIUIWrapper<U extends BasicGradleUI>
{
    private U basicGradleUI;
    private Map<GradleTabVersion1, GradleTabVersionWrapper> tabMap = new HashMap<GradleTabVersion1, GradleTabVersionWrapper>();

    protected SettingsNodeVersionWrapper settingsVersionWrapper;
    protected AlternateUIInteractionVersionWrapper alternateUIInteractionVersionWrapper;
    protected GradleInterfaceVersion1 gradleInterfaceWrapper;

    private OutputUILordWrapper outputUILordWrapper;

   public AbstractOpenAPIUIWrapper( SettingsNodeVersion1 settings, AlternateUIInteractionVersion1 alternateUIInteraction)
   {
      settingsVersionWrapper = new SettingsNodeVersionWrapper(settings);
      alternateUIInteractionVersionWrapper = new AlternateUIInteractionVersionWrapper(alternateUIInteraction, settingsVersionWrapper );
   }

   public void initialize( U basicGradleUI ) {
        this.basicGradleUI = basicGradleUI;
        basicGradleUI.getGradlePluginLord().addRequestObserver( new GradlePluginLord.RequestObserver()
        {
           /**
            Notification that a command is about to be executed. This is mostly useful
            for IDE's that may need to save their files.

            @param request the request that's about to be executed
            @author mhunsicker
            */
           public void aboutToExecuteRequest( Request request )
           {
               alternateUIInteractionVersionWrapper.aboutToExecuteCommand( request.getFullCommandLine() );
           }

           public void executionRequestAdded( ExecutionRequest request ) { }
           public void refreshRequestAdded( RefreshTaskListRequest request ) { }
           public void requestExecutionComplete( Request request, int result, String output ) { }
        }, false );

       outputUILordWrapper = new OutputUILordWrapper( basicGradleUI.getOutputUILord() );
       gradleInterfaceWrapper = instantiateGradleInterfaceWrapper();

    }

    /**
     * This instantiates our GradleInterfaceVersion object. Additions have been made
     * to it -- making new versions, so we have a choice of which one to load. We'll
     * try to load the latest one first, if that fails, we'll fall back on older
     * versions. The latest version would fail to load because it depends on classes
     * that are part of the Open API project and it can't find those classes. It
     * might not find them because this is loaded from the Open API and if its an
     * older version, those classes won't exist.
     * @return a version of GradleInterfaceVersionX
     */
    protected GradleInterfaceVersion1 instantiateGradleInterfaceWrapper()
    {
       try
       {
           //try to load the latest version. I'm explicitly using the full package name so any NoClassDefFoundErrors will only
           //occur within this try/catch block.
           return new org.gradle.openapi.wrappers.foundation.GradleInterfaceWrapperVersion2( basicGradleUI.getGradlePluginLord() );
       }
       catch( NoClassDefFoundError e )
       {  //if that's not found, fall back to version 1
          return new org.gradle.openapi.wrappers.foundation.GradleInterfaceWrapperVersion1( basicGradleUI.getGradlePluginLord() );
       }
    }

    public U getGradleUI() {
      return basicGradleUI;
   }

    /**
       Call this whenever you're about to show this panel. We'll do whatever
       initialization is necessary.
    */
    public void aboutToShow() {
        basicGradleUI.aboutToShow();
    }

    /**
     * Call this to deteremine if you can close this pane. if we're busy, we'll
     * ask the user if they want to close.
     *
     * @param closeInteraction allows us to interact with the user
     * @return true if we can close, false if not.
     */
    public boolean canClose(final BasicGradleUIVersion1.CloseInteraction closeInteraction) {
        return basicGradleUI.canClose(new BasicGradleUI.CloseInteraction() {
            public boolean promptUserToConfirmClosingWhileBusy() {
                return closeInteraction.promptUserToConfirmClosingWhileBusy();
            }
        });
    }

    /**
       Call this before you close the pane. This gives it an opportunity to do
       cleanup. You probably should call canClose before this. It gives the
       app a chance to cancel if its busy.
    */
    public void close() {
        basicGradleUI.close();
    }


   /**
      @return the root directory of your gradle project.
   */
   public File getCurrentDirectory() {
       return gradleInterfaceWrapper.getCurrentDirectory();
   }

   /**
      @param  currentDirectory the new root directory of your gradle project.
   */
   public void setCurrentDirectory(File currentDirectory) {
       gradleInterfaceWrapper.setCurrentDirectory(currentDirectory);
   }

   /**
    * @return the gradle home directory. Where gradle is installed.
    */
   public File getGradleHomeDirectory() {
       return gradleInterfaceWrapper.getGradleHomeDirectory();
   }

   /**
    * This is called to get a custom gradle executable file. If you don't run
    * gradle.bat or gradle shell script to run gradle, use this to specify
    * what you do run. Note: we're going to pass it the arguments that we would
    * pass to gradle so if you don't like that, see alterCommandLineArguments.
    * Normally, this should return null.
    *
    * @return the Executable to run gradle command or null to use the default
    */
   public File getCustomGradleExecutable() {
       return gradleInterfaceWrapper.getCustomGradleExecutable();
   }

    /**
       Call this to add an additional tab to the gradle UI. You can call this
       at any time.
       @param  index             the index of where to add the tab.
       @param  gradleTabVersion1 the tab to add.
    */
    public void addTab(int index, GradleTabVersion1 gradleTabVersion1) {
        GradleTabVersionWrapper gradleVersionWrapper = new GradleTabVersionWrapper(gradleTabVersion1);

        //we have to store our wrapper so you can call remove tab using your passed-in object
        tabMap.put(gradleTabVersion1, gradleVersionWrapper);

        basicGradleUI.addGradleTab(index, gradleVersionWrapper);
    }

    /**
       Call this to remove one of your own tabs from this.

       @param  gradleTabVersion1 the tab to remove
    */
    public void removeTab(GradleTabVersion1 gradleTabVersion1) {
        GradleTabVersionWrapper gradleTabVersionWrapper = tabMap.remove(gradleTabVersion1);
        if (gradleTabVersionWrapper != null) {
           basicGradleUI.removeGradleTab(gradleTabVersionWrapper);
        }
    }

    public int getGradleTabCount() {
       return basicGradleUI.getGradleTabCount();
    }

    /**
       @param  index      the index of the tab
       @return the name of the tab at the specified index.
    */
    public String getGradleTabName(int index) {
        return basicGradleUI.getGradleTabName(index);
    }

     /**
      * Returns the index of the gradle tab with the specified name.
      * @param name the name of the tab
      * @return the index of the tab or -1 if not found
      */
     public int getGradleTabIndex( String name ) {
         return basicGradleUI.getGradleTabIndex( name );
     }

    /**
     * @return the currently selected tab
     */
    public int getCurrentGradleTab() {
        return basicGradleUI.getCurrentGradleTab();
    }

    /**
     * Makes the specified tab the current tab.
     * @param index the index of the tab.
     */
    public void setCurrentGradleTab( int index ) {
        basicGradleUI.setCurrentGradleTab( index );
    }

    /**
     * This allows you to add a listener that can add additional command line
     * arguments whenever gradle is executed. This is useful if you've customized
     * your gradle build and need to specify, for example, an init script.
     *
     * @param listener the listener that modifies the command line arguments.
     */
    public void addCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListenerVersion1 listener) {
        gradleInterfaceWrapper.addCommandLineArgumentAlteringListener(listener);
    }

    public void removeCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListenerVersion1 listener) {
        gradleInterfaceWrapper.removeCommandLineArgumentAlteringListener(listener);
    }

    public OutputUILordVersion1 getOutputLord()
    {
        return new OutputUILordWrapper( basicGradleUI.getOutputUILord() );
    }


    public void addOutputObserver( OutputObserverVersion1 observer )
    {
        outputUILordWrapper.addOutputObserver( observer );
    }

    public void removeOutputObserver( OutputObserverVersion1 observer )
    {
       outputUILordWrapper.removeOutputObserver( observer );
    }

    /**
    Call this to execute the given gradle command.

    @param commandLineArguments  the command line arguments to pass to gradle.
    @param displayName           the name displayed in the UI for this command
    */
    public void executeCommand(String commandLineArguments, String displayName) {
        //we go through the Swing version because it allows you to specify a display name
        //for the command.
        basicGradleUI.executeCommand( commandLineArguments, displayName );
    }


   /**
    This refreshes the task tree. Useful if you know you've changed something behind
    gradle's back or when first displaying this UI.
    */
   public void refreshTaskTree()
   {
      basicGradleUI.refreshTaskTree();
   }

   /**
    Determines if commands are currently being executed or not.

    @return true if we're busy, false if not.
    */
   public boolean isBusy()
   {
      return getGradleUI().isBusy();
   }

   /**
    Determines whether output is shown only when errors occur or always
    @return true to only show output if errors occur, false to show it always.
    */
   public boolean getOnlyShowOutputOnErrors()
   {
      return getGradleUI().getOutputUILord().getOnlyShowOutputOnErrors();
   }

   /**
    This adds the specified component to the setup panel. It is added below the last
    'default' item. You can only add 1 component here, so if you need to add multiple
    things, you'll have to handle adding that to yourself to the one component.
    @param component the component to add.
    */
   public void setCustomPanelToSetupTab( JComponent component )
   {
      getGradleUI().setCustomPanelToSetupTab( component );
   }

   /**
    Sets the font for the output text
    @param font the new font
    */
   public void setOutputTextFont( Font font )
   {
      getGradleUI().setOutputTextFont( font );
   }

    /**
     * @return an object that works with lower level gradle and contains the current projects and tasks.
     */
   public GradleInterfaceVersion1 getGradleInterfaceVersion1()
   {
      return gradleInterfaceWrapper;
   }

   /**
     * Returns a FavoritesEditor. This is useful for getting a list of all favorites or
     * modifying them.
     * @return a FavoritesEditorVersion1. Use this to interact with the favorites.
     */
   public FavoritesEditorVersion1 getFavoritesEditor() {
      return new FavoritesEditorWrapper( basicGradleUI.getGradlePluginLord().getFavoritesEditor() );
   }
}
