
// external inputs:
// User data (name and list of groups)
// List of PublisherDid's as param to some app-function (multi-cutout, merge)
// Database connection (from Settings)

// For Non-authenticated requests two behaviours possible:
// A, setups with configured security: return only PUBLIC data
// B, setups wihtout need of security: access all data
// Currently B supported: Vlkb security filters will always set UsePrincipal.
// Security filters could reserve 'anonymous' user for non-authenticated requests, if needed.
// So missing UserPrincipal is interpreted as setup without security filters - full access allowed.


import java.util.logging.Logger;

import java.io.PrintWriter;
import java.security.Principal;

import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;
import java.util.ListIterator;



public class AuthPolicy
{
   private static final Logger LOGGER = Logger.getLogger(AuthPolicy.class.getName());

   enum Access { PUBLIC_ONLY, PUBLIC_AND_AUTHORIZED_PRIVATE };
   private Access access;

   private String   userName;
   private String[] userGroups;
   private boolean userGroupsValid;


   public AuthPolicy(Principal principal)
   {
      if(principal == null)
      {
         access = Access.PUBLIC_ONLY;
         userName = null;
         userGroups = null;
         userGroupsValid = false;
         LOGGER.finer("Non authenticated request (UserPrincipal null in HttpServletRequest)");
      }
      else
      {
         if(principal instanceof it.inaf.ia2.aa.data.User)
         {
            it.inaf.ia2.aa.data.User vlkbUser = (it.inaf.ia2.aa.data.User) principal;

            userName = vlkbUser.getName();
            List<String> groups = vlkbUser.getGroups();
            userGroups = groups.toArray(new String[0]);
            userGroupsValid = true;

            access = Access.PUBLIC_AND_AUTHORIZED_PRIVATE;

            LOGGER.finer("User [Groups]: " + userName + " [ " + String.join(" ", userGroups) + " ]" );
         }
         else
         {
            userName = principal.getName();
            LOGGER.finer("DBG principal '"+userName+"' is not instance of it.inaf.ia2.aa.data.User");
            userGroups = new String[]{""};//{"VLKB.groupA", "AllPrivate"}; // was for shiro
            userGroupsValid = true;
            access = Access.PUBLIC_AND_AUTHORIZED_PRIVATE;
            //throw new IllegalArgumentException("UserPrincipal is not of expected type");
         }
      }
   }


   public String[] removeNotAuthorized(String[] pubdidArr)
   {
      LOGGER.finer("trace");

      List<String> pubdidList = new ArrayList<String>(Arrays.asList(pubdidArr));

      switch(access)
      {
         case PUBLIC_ONLY :
            AuthPolicyDb adb;
            synchronized(AuthPolicyDb.class)
            {
               adb = new AuthPolicyDb();
            }
            pubdidList = adb.selectPublicOnly(pubdidArr);
            break;

         case PUBLIC_AND_AUTHORIZED_PRIVATE :
            List<AuthPolicyDb.PubdidGroups> privateGroups = dbQueryPrivateUniqGroups(pubdidList);
            List<String> notAuthorizedPubdids             = selectNotAuthorized(privateGroups, userGroups);
            removeNotAuthZd(pubdidList, notAuthorizedPubdids);
            break;

         default :
            assert false : "Unrecoginzed  access : " + access;
      }

      return pubdidList.toArray(new String[0]); 
   }


   private List<String> selectNotAuthorized(List<AuthPolicyDb.PubdidGroups> pubdidList, String[] userGroups)
   {
      LOGGER.fine("trace");

      List<String> pubdidsNotAuthorizedList = new LinkedList<String>();
      ListIterator<AuthPolicyDb.PubdidGroups> it = pubdidList.listIterator();

      while (it.hasNext())
      {
         AuthPolicyDb.PubdidGroups pubdidGroups = it.next();

         LOGGER.finest(pubdidGroups.pubdid + " : " + String.join(" ",pubdidGroups.groups));

         if( isIntersectionEmpty(pubdidGroups.groups, userGroups) )
         {
            pubdidsNotAuthorizedList.add(pubdidGroups.pubdid);
         }
      }

      return pubdidsNotAuthorizedList;
   }


   private boolean isIntersectionEmpty(String[] stringsA, String[] stringsB)
   {
      for(String strA : stringsA)
         for(String strB : stringsB)
         {
            if(strA.equals(strB))
            {
               return false;
            }
         }
      return true;
   }


   private void removeNotAuthZd(List<String> pubdids, List<String> notAuthorizedUniqPubdids)
   {
      ListIterator<String> itr = pubdids.listIterator();
      while (itr.hasNext())
      {
         String pubdid = itr.next();

         for(String notAuthPubdid : notAuthorizedUniqPubdids)
         {
            if (pubdid.equals(notAuthPubdid)) itr.remove();
         }
      }

      return;
   }


   private List<AuthPolicyDb.PubdidGroups> dbQueryPrivateUniqGroups(List<String> pubdids)
   {
      AuthPolicyDb adb;
      synchronized(AuthPolicyDb.class)
      {
         adb = new AuthPolicyDb();
      }

      Set<String> uniqPubdids = new HashSet<String>(pubdids);

      if(uniqPubdids.isEmpty())
      {
         List<AuthPolicyDb.PubdidGroups> privatePubdidGroups = Collections.emptyList();
         return privatePubdidGroups;
      }
      else
      {
         // FIXME handle DB-exceptions
         List<AuthPolicyDb.PubdidGroups> privatePubdidGroups = adb.queryGroupsPrivateOnly(uniqPubdids);
         return privatePubdidGroups;
      }
   }


   // API (XmlSerialize to legacy results-xml)

   public String   getAccessPolicy() { return access.name(); }
   public String   getUserName()     { return userName; }
   public String[] getUserGroups()   { return userGroups; }

   public String getUserGroupsAsString(String separator)
   {
      if( (userGroups != null) && (userGroups.length > 0) )
      {
         return String.join(separator, userGroups);
      }
      else
      {
         return null;
      }
   }

}

