/*!
 * Copyright 2010 - 2017 Hitachi Vantara.  All rights reserved.
 *
 * 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.pentaho.di.ui.repository.pur.controller;

import java.util.Enumeration;
import java.util.List;
import java.util.ResourceBundle;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Shell;
import org.pentaho.di.core.EngineMetaInterface;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.gui.SpoonFactory;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.job.JobMeta;
import org.pentaho.di.repository.Repository;
import org.pentaho.di.repository.pur.PurRepository;
import org.pentaho.di.repository.pur.UnifiedRepositoryLockService;
import org.pentaho.di.repository.pur.model.RepositoryLock;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.ui.core.dialog.ErrorDialog;
import org.pentaho.di.ui.core.gui.GUIResource;
import org.pentaho.di.ui.repository.pur.PurRepositoryDialog;
import org.pentaho.di.ui.repository.pur.services.IAbsSecurityProvider;
import org.pentaho.di.ui.repository.pur.services.ILockService;
import org.pentaho.di.ui.spoon.Spoon;
import org.pentaho.di.ui.spoon.job.JobGraph;
import org.pentaho.di.ui.spoon.trans.TransGraph;
import org.pentaho.ui.xul.XulComponent;
import org.pentaho.ui.xul.XulDomContainer;
import org.pentaho.ui.xul.XulException;
import org.pentaho.ui.xul.binding.Binding.Type;
import org.pentaho.ui.xul.binding.BindingConvertor;
import org.pentaho.ui.xul.binding.BindingFactory;
import org.pentaho.ui.xul.binding.DefaultBindingFactory;
import org.pentaho.ui.xul.components.XulMenuitem;
import org.pentaho.ui.xul.components.XulMessageBox;
import org.pentaho.ui.xul.components.XulPromptBox;
import org.pentaho.ui.xul.impl.AbstractXulEventHandler;
import org.pentaho.ui.xul.swt.custom.DialogConstant;
import org.pentaho.ui.xul.util.XulDialogCallback;

public class SpoonLockController extends AbstractXulEventHandler implements java.io.Serializable {

  private static final long serialVersionUID = -8466323408581425803L; /* EESOURCE: UPDATE SERIALVERUID */

  private static final Class<?> PKG = PurRepositoryDialog.class;

  private ILockService service;

  private EngineMetaInterface workingMeta = null;

  private BindingFactory bindingFactory = null;

  private boolean tabBound = false;

  private boolean isCreateAllowed = false;

  private boolean isLockingAllowed = false;

  private Shell shell;

  private static Log log = LogFactory.getLog( SpoonLockController.class );

  protected ResourceBundle messages = new ResourceBundle() {

    @Override
    public Enumeration<String> getKeys() {
      return null;
    }

    @Override
    protected Object handleGetObject( String key ) {
      return BaseMessages.getString( PKG, key );
    }
  };

  public String getName() {
    return "spoonLockController"; //$NON-NLS-1$
  }

  public void lockContent() throws Exception {
    try {
      if ( workingMeta != null && workingMeta.getObjectId() != null
          && supportsLocking( Spoon.getInstance().getRepository() ) ) {
        // Bind the tab icon if it is not already bound (cannot be done in init because TransGraph must exist to create
        // the tab)
        // Look in the SpoonTransformationDelegate for details on the TabItem creation
        if ( !tabBound ) {
          bindingFactory
              .createBinding(
                  this,
                  "activeMetaUnlocked", Spoon.getInstance().delegates.tabs.findTabMapEntry( workingMeta ).getTabItem(), "image", new BindingConvertor<Boolean, Image>() { //$NON-NLS-1$ //$NON-NLS-2$
                    @Override
                    public Image sourceToTarget( Boolean activeMetaUnlocked ) {
                      if ( activeMetaUnlocked ) {
                        if ( workingMeta instanceof TransMeta ) {
                          return GUIResource.getInstance().getImageTransGraph();
                        } else if ( workingMeta instanceof JobMeta ) {
                          return GUIResource.getInstance().getImageJobGraph();
                        }
                      } else {
                        return GUIResource.getInstance().getImageLocked();
                      }
                      return null;
                    }

                    @Override
                    public Boolean targetToSource( Image arg0 ) {
                      return false;
                    }
                  } );
          tabBound = true;
        }

        // Decide whether to lock or unlock the object
        if ( fetchRepositoryLock( workingMeta ) == null ) {
          // Lock the object (it currently is NOT locked)

          XulPromptBox lockNotePrompt = promptLockMessage( document, messages, null );
          lockNotePrompt.addDialogCallback( new XulDialogCallback<String>() {
            public void onClose( XulComponent component, Status status, String value ) {

              if ( !status.equals( Status.CANCEL ) ) {
                try {
                  if ( workingMeta instanceof TransMeta ) {
                    getService( Spoon.getInstance().getRepository() ).lockTransformation( workingMeta.getObjectId(),
                        value );
                  } else if ( workingMeta instanceof JobMeta ) {
                    getService( Spoon.getInstance().getRepository() ).lockJob( workingMeta.getObjectId(), value );
                  }

                  // Execute binding. Notify listeners that the object is now locked
                  firePropertyChange( "activeMetaUnlocked", true, false ); //$NON-NLS-1$ //$NON-NLS-2$

                  // this keeps the menu item and the state in sync
                  // could a binding be used instead?
                  XulDomContainer container = getXulDomContainer();
                  XulMenuitem lockMenuItem =
                      (XulMenuitem) container.getDocumentRoot().getElementById( "lock-context-lock" ); //$NON-NLS-1$
                  lockMenuItem.setSelected( true );
                } catch ( Exception e ) {
                  // convert to runtime exception so it bubbles up through the UI
                  throw new RuntimeException( e );
                }
              } else {
                // this keeps the menu item and the state in sync
                // could a binding be used instead?
                XulDomContainer container = getXulDomContainer();
                XulMenuitem lockMenuItem =
                    (XulMenuitem) container.getDocumentRoot().getElementById( "lock-context-lock" ); //$NON-NLS-1$
                lockMenuItem.setSelected( false );
              }
            }

            public void onError( XulComponent component, Throwable err ) {
              throw new RuntimeException( err );
            }
          } );

          lockNotePrompt.open();
        } else {
          // Unlock the object (it currently IS locked)
          if ( workingMeta instanceof TransMeta ) {
            getService( Spoon.getInstance().getRepository() ).unlockTransformation( workingMeta.getObjectId() );
          } else if ( workingMeta instanceof JobMeta ) {
            getService( Spoon.getInstance().getRepository() ).unlockJob( workingMeta.getObjectId() );
          }
          // Execute binding. Notify listeners that the object is now unlocked
          firePropertyChange( "activeMetaUnlocked", false, true ); //$NON-NLS-1$ //$NON-NLS-2$
        }

      } else if ( workingMeta != null && workingMeta.getObjectId() == null
          && supportsLocking( Spoon.getInstance().getRepository() ) ) {
        XulDomContainer container = getXulDomContainer();
        XulMenuitem lockMenuItem = (XulMenuitem) container.getDocumentRoot().getElementById( "lock-context-lock" ); //$NON-NLS-1$
        lockMenuItem.setSelected( false );

        XulMessageBox msgBox = (XulMessageBox) document.createElement( "messagebox" ); //$NON-NLS-1$
        msgBox.setTitle( BaseMessages.getString( PKG, "Dialog.Error" ) ); //$NON-NLS-1$
        msgBox.setMessage( BaseMessages.getString( PKG, "LockController.SaveBeforeLock" ) );//$NON-NLS-1$
        msgBox.setModalParent( shell );

        msgBox.open();
      } else {
        XulDomContainer container = getXulDomContainer();
        XulMenuitem lockMenuItem = (XulMenuitem) container.getDocumentRoot().getElementById( "lock-context-lock" ); //$NON-NLS-1$
        lockMenuItem.setSelected( false );

        XulMessageBox msgBox = (XulMessageBox) document.createElement( "messagebox" ); //$NON-NLS-1$
        msgBox.setTitle( BaseMessages.getString( PKG, "Dialog.Error" ) ); //$NON-NLS-1$
        msgBox.setMessage( BaseMessages.getString( PKG, "LockController.NoLockingSupport" ) );//$NON-NLS-1$
        msgBox.setModalParent( shell );
        msgBox.open();
      }
    } catch ( Throwable th ) {
      log.error( BaseMessages.getString( PKG, "LockController.NoLockingSupport" ), th );//$NON-NLS-1$
      new ErrorDialog(
          ( (Spoon) SpoonFactory.getInstance() ).getShell(),
          BaseMessages.getString( PKG, "Dialog.Error" ), BaseMessages.getString( PKG, "LockController.NoLockingSupport" ), th ); //$NON-NLS-1$ //$NON-NLS-2$
    }
  }

  public void viewLockNote() throws Exception {
    if ( workingMeta != null && supportsLocking( Spoon.getInstance().getRepository() ) ) {
      try {
        RepositoryLock repoLock = fetchRepositoryLock( workingMeta );
        if ( repoLock != null ) {
          XulMessageBox msgBox = (XulMessageBox) document.createElement( "messagebox" ); //$NON-NLS-1$
          msgBox.setTitle( BaseMessages.getString( PKG, "PurRepository.LockNote.Title" ) ); //$NON-NLS-1$
          msgBox.setMessage( repoLock.getMessage() );
          msgBox.setModalParent( shell );

          msgBox.open();
        }
      } catch ( Throwable th ) {
        log.error( BaseMessages.getString( PKG, "LockController.NoLockingSupport" ), th );//$NON-NLS-1$
        new ErrorDialog( ( (Spoon) SpoonFactory.getInstance() ).getShell(), BaseMessages
            .getString( PKG, "Dialog.Error" ), BaseMessages.getString( PKG, "LockController.NoLockingSupport" ), th ); //$NON-NLS-1$ //$NON-NLS-2$
      }
    } else {
      XulMessageBox msgBox = (XulMessageBox) document.createElement( "messagebox" ); //$NON-NLS-1$
      msgBox.setTitle( BaseMessages.getString( PKG, "Dialog.Error" ) ); //$NON-NLS-1$
      msgBox.setMessage( BaseMessages.getString( PKG, "LockController.NoLockingSupport" ) );//$NON-NLS-1$
      msgBox.setModalParent( shell );
      msgBox.open();
    }
  }

  @Override
  public void setXulDomContainer( XulDomContainer xulDomContainer ) {
    super.setXulDomContainer( xulDomContainer );
    init();
  }

  public boolean isActiveMetaUnlocked() {
    try {
      if ( fetchRepositoryLock( workingMeta ) != null ) {
        return false;
      } else {
        return true;
      }
    } catch ( KettleException e ) {
      throw new RuntimeException( e );
    }
  }

  protected void init() {
    try {
      if ( ( Spoon.getInstance().getRepository() != null )
          && ( Spoon.getInstance().getRepository().hasService( IAbsSecurityProvider.class ) ) ) {
        IAbsSecurityProvider securityService =
            (IAbsSecurityProvider) Spoon.getInstance().getRepository().getService( IAbsSecurityProvider.class );

        setCreateAllowed( allowedActionsContains( securityService, IAbsSecurityProvider.CREATE_CONTENT_ACTION ) );
      }

      shell = ( ( (Spoon) SpoonFactory.getInstance() ).getShell() );
      XulDomContainer container = getXulDomContainer();

      bindingFactory = new DefaultBindingFactory();
      bindingFactory.setDocument( container.getDocumentRoot() );

      bindingFactory.setBindingType( Type.ONE_WAY );

      bindingFactory.createBinding( this, "activeMetaUnlocked", "lock-context-locknotes", "disabled" ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      bindingFactory.createBinding( this, "lockingNotAllowed", "lock-context-lock", "disabled" ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

      // Get trans* object to gain access to the *Meta object to determine if we are initially locked or not
      // Try transformation
      if ( container.getEventHandlers().containsKey( "transgraph" ) ) { //$NON-NLS-1$
        workingMeta = ( (TransGraph) container.getEventHandler( "transgraph" ) ).getMeta(); //$NON-NLS-1$
      } else if ( container.getEventHandlers().containsKey( "jobgraph" ) ) { //$NON-NLS-1$
        workingMeta = ( (JobGraph) container.getEventHandler( "jobgraph" ) ).getMeta(); //$NON-NLS-1$
      }

      RepositoryLock repoLock = fetchRepositoryLock( workingMeta );
      if ( repoLock != null ) {
        XulMenuitem lockMenuItem = (XulMenuitem) container.getDocumentRoot().getElementById( "lock-context-lock" ); //$NON-NLS-1$
        lockMenuItem.setSelected( true );
        // Permit locking/unlocking if the user owns the lock
        if ( Spoon.getInstance().getRepository() instanceof PurRepository ) {
          setLockingAllowed( new UnifiedRepositoryLockService(
              ( (PurRepository) Spoon.getInstance().getRepository() ).getUnderlyingRepository() )
                  .canUnlockFileById( workingMeta.getObjectId() ) );
        } else {
          setLockingAllowed( repoLock.getLogin().equalsIgnoreCase(
              Spoon.getInstance().getRepository().getUserInfo().getLogin() ) );
        }
      } else {
        setLockingAllowed( true );
      }

      firePropertyChange( "activeMetaUnlocked", null, repoLock == null ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    } catch ( Exception e ) {
      log.error( BaseMessages.getString( PKG, "LockController.NoLockingSupport" ), e );//$NON-NLS-1$
      new ErrorDialog( ( (Spoon) SpoonFactory.getInstance() ).getShell(),
          BaseMessages.getString( PKG, "Dialog.Error" ), e.getMessage(), e ); //$NON-NLS-1$

    }
  }

  public boolean isCreateAllowed() {
    return isCreateAllowed;
  }

  public void setCreateAllowed( boolean isCreateAllowed ) {
    this.isCreateAllowed = isCreateAllowed;
    this.firePropertyChange( "createAllowed", null, isCreateAllowed ); //$NON-NLS-1$
  }

  public boolean isLockingAllowed() {
    return isLockingAllowed;
  }

  public boolean isLockingNotAllowed() {
    return !isLockingAllowed;
  }

  public void setLockingAllowed( boolean isLockingAllowed ) {
    this.isLockingAllowed = isLockingAllowed;
    this.firePropertyChange( "lockingNotAllowed", null, !isLockingAllowed ); //$NON-NLS-1$
  }

  private boolean allowedActionsContains( IAbsSecurityProvider service, String action ) throws KettleException {
    List<String> allowedActions = service.getAllowedActions( IAbsSecurityProvider.NAMESPACE );
    for ( String actionName : allowedActions ) {
      if ( action != null && action.equals( actionName ) ) {
        return true;
      }
    }
    return false;
  }

  protected RepositoryLock fetchRepositoryLock( EngineMetaInterface meta ) throws KettleException {
    RepositoryLock result = null;
    if ( meta != null ) {
      if ( meta.getObjectId() != null ) {
        if ( meta instanceof TransMeta ) {
          result = getService( Spoon.getInstance().getRepository() ).getTransformationLock( meta.getObjectId() );
        } else if ( meta instanceof JobMeta ) {
          result = getService( Spoon.getInstance().getRepository() ).getJobLock( meta.getObjectId() );
        }
      }
    }
    return result;
  }

  private XulPromptBox promptLockMessage( org.pentaho.ui.xul.dom.Document document, ResourceBundle messages,
      String defaultMessage ) throws XulException {
    XulPromptBox prompt = (XulPromptBox) document.createElement( "promptbox" ); //$NON-NLS-1$
    prompt.setModalParent( shell );

    prompt.setTitle( BaseMessages.getString( PKG, "RepositoryExplorer.LockMessage.Title" ) );//$NON-NLS-1$
    prompt.setButtons( new DialogConstant[] { DialogConstant.OK, DialogConstant.CANCEL } );

    prompt.setMessage( BaseMessages.getString( PKG, "RepositoryExplorer.LockMessage.Label" ) );//$NON-NLS-1$
    prompt.setValue( defaultMessage == null
        ? BaseMessages.getString( PKG, "RepositoryExplorer.DefaultLockMessage" ) : defaultMessage ); //$NON-NLS-1$
    return prompt;
  }

  private ILockService getService( Repository repository ) throws KettleException {
    if ( service == null ) {
      if ( repository != null && repository.hasService( ILockService.class ) ) {
        return (ILockService) repository.getService( ILockService.class );
      } else {
        throw new IllegalStateException();
      }
    } else {
      return service;
    }
  }

  private boolean supportsLocking( Repository repository ) throws KettleException {
    return repository.hasService( ILockService.class );
  }

}
