/*
 * Copyright 2011 JBoss, by Red Hat, Inc
 *
 * 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.jboss.errai.bus.client.security;

import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import org.jboss.errai.bus.client.ErraiBus;
import org.jboss.errai.bus.client.api.Message;
import org.jboss.errai.bus.client.api.MessageCallback;
import org.jboss.errai.bus.client.api.base.MessageBuilder;
import org.jboss.errai.common.client.protocols.MessageParts;
import org.jboss.errai.bus.client.protocols.SecurityCommands;
import org.jboss.errai.bus.client.protocols.SecurityParts;
import org.jboss.errai.bus.client.security.impl.BasicAuthenticationContext;
import org.jboss.errai.bus.client.security.impl.NameCredential;
import org.jboss.errai.bus.client.security.impl.PasswordCredential;

import java.util.HashSet;

import static org.jboss.errai.bus.client.api.base.MessageBuilder.createMessage;
import static org.jboss.errai.bus.client.protocols.SecurityParts.CredentialsRequired;

public class SecurityService {
  private AuthenticationContext authenticationContext;
  private AuthenticationHandler authHandler;
  public static final String SUBJECT = "AuthenticationListener";

  // deferr notification of LoginClient in case
  // a workspace wants to override the authentication or authorization scheme
  private boolean deferredNotification;

  public SecurityService() {
    ErraiBus.get().subscribe(SUBJECT, new MessageCallback() {
      public void callback(Message msg) {

        switch (SecurityCommands.valueOf(msg.getCommandType())) {
          case AuthenticationScheme:
            if (authHandler == null) {
              //               msg.toSubject("LoginClient").sendNowWith(ErraiBus.get());
              return;
            }

            String credentialsRequired = msg.get(String.class, CredentialsRequired);
            String[] credentialNames = credentialsRequired.split(",");
            Credential[] credentials = new Credential[credentialNames.length];

            for (int i = 0; i < credentialNames.length; i++) {
              switch (CredentialTypes.valueOf(credentialNames[i])) {
                case Name:
                  credentials[i] = new NameCredential();
                  break;
                case Password:
                  credentials[i] = new PasswordCredential();
                  break;

                default:
                  //todo: throw a massive error here.
              }
            }

            // callback on externally provided login form
            // asking for the required credentials specified on the server side.
            authHandler.doLogin(credentials);

            // Create an authentication request and send the credentials
            Message challenge = createMessage()
                .toSubject("AuthenticationService")
                .command(SecurityCommands.AuthRequest)
                .with(MessageParts.ReplyTo, SUBJECT)
                .getMessage();

            for (int i = 0; i < credentialNames.length; i++) {
              switch (CredentialTypes.valueOf(credentialNames[i])) {
                case Name:
                  challenge.set(CredentialTypes.Name, credentials[i].getValue());
                  break;
                case Password:
                  challenge.set(CredentialTypes.Password, credentials[i].getValue());
                  break;
              }
            }

            challenge.sendNowWith(ErraiBus.get());

            break;
          case AuthenticationNotRequired:
            notifyLoginClient(msg);
            break;

          case FailedAuth:
            notifyLoginClient(msg);
            break;
          case SuccessfulAuth:
            if (authenticationContext != null && authenticationContext.isValid()) return;
            authenticationContext = createAuthContext(msg);
            notifyLoginClient(msg);

            break;
        }
      }
    });


    // listener for the authorization assignment
    ErraiBus.get().subscribe("AuthorizationListener",
        new MessageCallback() {
          public void callback(Message message) {

            authenticationContext = createAuthContext(message);

            if (!deferredNotification) {
              MessageBuilder.createMessage()
                  .toSubject("LoginClient")
                  .command(SecurityCommands.HandshakeComplete)
                  .noErrorHandling()
                  .sendNowWith(ErraiBus.get());
            }
          }
        });
  }

  private void notifyLoginClient(Message msg) {
    // forward this message on to the login client.
    msg.toSubject("LoginClient").sendNowWith(ErraiBus.get());
  }

  private static AuthenticationContext createAuthContext(Message msg) {
    String username = "";
    HashSet<Role> assingnedRoles = new HashSet<Role>();

    if (msg.hasPart(SecurityParts.Roles)) {
      username = msg.get(String.class, SecurityParts.Name);
      String rolesString = msg.get(String.class, SecurityParts.Roles);


      String[] roles = rolesString.split(",");

      for (final String role : roles) {
        assingnedRoles.add(new Role() {
          public String getRoleName() {
            return role;
          }
        });
      }
    }

    return new BasicAuthenticationContext(assingnedRoles, username);
  }

  /**
   * Send a
   *
   * @param handler
   */
  public void doAuthentication(final AuthenticationHandler handler) {
    authHandler = handler;

    DeferredCommand.addCommand(new Command() {
      public void execute() {
        MessageBuilder.createMessage()
            .toSubject("AuthenticationService")
            .command(SecurityCommands.AuthenticationScheme)
            .with(MessageParts.ReplyTo, SUBJECT)
            .noErrorHandling().sendNowWith(ErraiBus.get());
      }
    });
  }

  public AuthenticationContext getAuthenticationContext() {
    return authenticationContext;
  }

  public void setAuthenticationContext(AuthenticationContext authenticationContext) {
    this.authenticationContext = authenticationContext;
  }

  public void setDeferredNotification(boolean deferredNotification) {
    this.deferredNotification = deferredNotification;
  }
}
