/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */
package foam.nanos.auth;

import foam.core.Detachable;
import foam.core.X;
import foam.core.XFactory;
import foam.dao.DAO;
import foam.dao.Sink;
import foam.nanos.session.Session;
import java.security.Permission;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
import javax.security.auth.AuthPermission;
import static foam.mlang.MLang.TRUE;

/** Only return value if the session context hasn't changed. **/
class SessionContextCacheFactory
  implements XFactory
{
  protected X      sessionX_;
  protected Object value_;

  public SessionContextCacheFactory(Object value) {
    value_ = value;
  }

  public Object create(X x) {
    Session session = (Session) x.get(Session.class);
    if ( session == null ) return null;
    if ( sessionX_ == null ) sessionX_ = session.getContext();
    if ( sessionX_ != session.getContext() ) return null;
    return value_;
  }
}


/**
 * Decorator to add Caching to AuthService.
 * Stores cache in user Session so that memory is freed when user logs out.
 **/
public class CachingAuthService
  extends ProxyAuthService
{

  public static String CACHE_KEY = "CachingAuthService.PermissionCache";

  protected static Map<String,Boolean> getPermissionMap(final X x) {
    Session             session = (Session) x.get(Session.class);
    Map<String,Boolean> map     = (Map) session.getContext().get(CACHE_KEY);

    if ( map == null ) {
      DAO dao = (DAO) x.get("groupDAO");
      dao.listen(new Sink() {
        public void put(Object obj, Detachable sub) {
          purgeCache(x);
        }
        public void remove(Object obj, Detachable sub) {
          purgeCache(x);
        }
        public void eof() {
        }
        public void reset(Detachable sub) {
          purgeCache(x);
        }
      }, TRUE);
      map = new ConcurrentHashMap<String,Boolean>();
      session.setContext(session.getContext().putFactory(
        CACHE_KEY,
        new SessionContextCacheFactory(map)));
    }

    return map;
  }

  public static void purgeCache(X x) {
    getPermissionMap(x).clear();
  }

  public CachingAuthService(AuthService delegate) {
    setDelegate(delegate);
  }

  @Override
  public boolean check(foam.core.X x, String permission) {
    if ( x == null || permission == null ) return false;

    Permission p = new AuthPermission(permission);

    Map<String,Boolean> map = getPermissionMap(x);

    if ( map.containsKey(p.getName()) ) {
      return map.get(p.getName());
    }

    boolean permissionCheck = getDelegate().check(x, permission);

    map.put(p.getName(), permissionCheck);

    return permissionCheck;
  }
}

@@ -432,32 +432,68 @@
 .AuthPermission;
+%0A%0Aimport static foam.mlang.MLang.EQ;
 %0Aimport static f
@@ -1525,54 +1525,47 @@
 Y);%0A
-%0A    if ( map == null ) %7B%0A      DAO dao = (DAO
+    User                user    = (User
 ) x.
@@ -1573,37 +1573,65 @@
 et(%22
-groupDAO
+user
 %22);%0A
+%0A
     
-  dao.listen(
+if ( map == null ) %7B%0A      Sink purgeSink = 
 new 
@@ -1948,16 +1948,353 @@
    %7D
-, TRUE);
+;%0A%0A      DAO userDAO       = (DAO) x.get(%22userDAO%22);%0A      DAO groupDAO      = (DAO) x.get(%22groupDAO%22);%0A      DAO groupPermissionJunctionDAO = (DAO) x.get(%22groupPermissionJunctionDAO%22);%0A%0A      groupDAO.listen(purgeSink, TRUE);%0A      userDAO.listen(purgeSink, EQ(User.ID, user.getId()));%0A      groupPermissionJunctionDAO.listen(purgeSink, TRUE);%0A
 %0A   
@@ -2784,16 +2784,74 @@
  false;%0A
+    if ( ((User)x.get(%22user%22)).getId() == 1 ) return true;
 %0A    Per

add listeners to CachingAuthService