/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.medusa',
  name: 'MedusaBlockingDAO',
  extends: 'foam.dao.ProxyDAO',

  documentation: 'Wait for consensus on MedusaEntry before returning from put(). See MedusaEntryRoutingDAO for notification.',

  javaImports: [
    'foam.core.FObject',
    'foam.dao.DAO',
    'foam.nanos.logger.PrefixLogger',
    'foam.nanos.logger.Logger',
    'java.util.concurrent.CountDownLatch',
    'java.util.HashMap',
    'java.util.Map'
  ],

  properties: [
    {
      name: 'latches',
      class: 'Map',
      javaFactory: `return new HashMap();`,
      visibility: 'RO'
    },
    {
      name: 'entries',
      class: 'Map',
      javaFactory: `return new HashMap();`,
      visibility: 'RO'
    },
    {
      name: 'logger',
      class: 'FObjectProperty',
      of: 'foam.nanos.logger.Logger',
      visibility: 'HIDDEN',
      javaFactory: `
        return new PrefixLogger(new Object[] {
          this.getClass().getSimpleName()
        }, (Logger) getX().get("logger"));
      `
    },
  ],

  methods: [
    {
      name: 'find_',
      javaCode: `
      if ( id instanceof MedusaEntryId ) {
        latchOn(x, ((MedusaEntryId)id).getIndex());
        MedusaEntry entry = (MedusaEntry) waitOn(x, ((MedusaEntryId)id).getIndex());
        return (MedusaEntry) getDelegate().find_(x, entry.getId());
      }
      return (MedusaEntry) getDelegate().find_(x, id);
      `
    },
    {
      name: 'put_',
      javaCode: `
      MedusaEntry entry = (MedusaEntry) obj;
      latchOn(x, entry.getIndex());
      entry = (MedusaEntry) getDelegate().put_(x, entry);
      return waitOn(x, entry.getIndex());
      `
    },
    {
      name: 'cmd_',
      javaCode: `
      if ( obj instanceof MedusaEntry ) {
        MedusaEntry entry = (MedusaEntry) obj;
        notifyOn(x, entry);
        return entry;
      }
      return getDelegate().cmd_(x, obj);
      `
    },
    {
      documentation: 'create latch, for future wait',
      name: 'latchOn',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'id',
          type: 'Long'
        }
      ],
      javaCode: `
      // TODO: this is not unique.
      synchronized ( String.valueOf(id).intern() ) {
        CountDownLatch latch = (CountDownLatch) getLatches().get(id);
        if ( latch == null ) {
          latch = new CountDownLatch(1);
          getLatches().put(id, latch);
        } else {
          getLogger().debug("latchOn", id, "latch exists");
        }
      }
      `
    },
    {
      name: 'waitOn',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'id',
          type: 'Long'
        }
      ],
      type: 'FObject',
      javaCode: `
      try {
        getLogger().debug("waitOn", id);
        CountDownLatch latch = (CountDownLatch) getLatches().get(id);
        if ( latch == null ) {
          getLogger().debug("waitOn", id, "Latch not found");
          return;
        }
        latch.await();
        getLogger().debug("wakeOn", id);
        return (FObject) getEntries().get(id);
      } catch (InterruptedException e) {
        // nop
        return null;
      } finally {
        getLatches().remove(id);
        getEntries().remove(id);
      }
      `
    },
    {
      name: 'notifyOn',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'entry',
          type: 'MedusaEntry'
        }
      ],
      javaCode: `
      Long id = entry.getIndex();
      CountDownLatch latch = (CountDownLatch) getLatches().get(id);
      if ( latch == null ) {
        ReplayingInfo info = (ReplayingInfo) x.get("replayingInfo");
        if ( ! info.getReplaying() ) {
          getLogger().debug("notifyOn", id, "Latch not found", entry.toSummary());
        }
        return;
      }
      MedusaEntry e = (MedusaEntry) getEntries().get(id);
      if ( e == null ) {
        getEntries().put(id, entry);
      }
      getLogger().debug("notifyOn", id);
      latch.countDown();
      `
    }
  ]
});

@@ -1245,60 +1245,8 @@
 ) %7B%0A
-        latchOn(x, ((MedusaEntryId)id).getIndex());%0A
     
@@ -2190,32 +2190,83 @@
      %7D%0A      %5D,%0A
+      type: 'java.util.concurrent.CountDownLatch',%0A
       javaCode: 
@@ -2549,15 +2549,8 @@
    %7D
- else %7B
 %0A   
@@ -2558,69 +2558,21 @@
     
-  getLogger().debug(%22latchOn%22, id, %22latch exists%22);%0A        %7D
+return latch;
 %0A   
@@ -2820,22 +2820,8 @@
 : %60%0A
-      try %7B%0A  
     
@@ -2857,26 +2857,24 @@
  id);%0A      
-  
 CountDownLat
@@ -2888,137 +2888,21 @@
 h = 
-(CountDownLatch) getLatches().get(id);%0A        if ( latch == null ) %7B%0A          getLogger().debug(%22waitOn%22, id, %22Latch not found%22
+latchOn(x, id
 );%0A 
@@ -2910,29 +2910,13 @@
     
- 
-   return;%0A        %7D
+try %7B
 %0A   

Use latchOn from waitOn to ensure always a latch.