//
// Copyright (c) Microsoft Corporation.   All rights reserved.
//

// Some sort of threads/locking test.
// See usage for parameter.
// DebubPrintSpinLock was in the Marmot runtime.

using System;
using System.Threading;
using Microsoft.Contracts;
using Microsoft.SingSharp.Reflection;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Applications;
using Microsoft.Singularity.Io;
using Microsoft.Singularity.Configuration;
[assembly: Transform(typeof(ApplicationResourceTransform))]

namespace MarmotBugs
{
    [ConsoleCategory(HelpMessage="Show attributes associated with a file", DefaultAction=true)]
    internal class Parameters {
    
        [InputEndpoint("data")]
        public readonly TRef<UnicodePipeContract.Exp:READY> Stdin;

        [OutputEndpoint("data")]
        public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;

        [LongParameter( "t", Default=1,  HelpMessage="threads")]
        internal long threads;

        reflective internal Parameters();

        internal int AppMain() {
            Bug00013.AppMain(this);
            return 0;
        }
    }

  class DebugPrintSpin
  {
      private static Object locker = new Object();
      public static void Lock() {
          //    Class_java_lang_Thread *currentThread = CurrentThread();
          //    currentThread->f_isAwaitingDebugPrintSpinLock = true;
          System.Threading.Monitor.Enter(locker);
          //    DebugPrintCriticalSectionHolder = currentThread;
          //    currentThread->f_isAwaitingDebugPrintSpinLock = false;
      }
      public static void Unlock() {
          //    DebugPrintCriticalSectionHolder = NULL;
          System.Threading.Monitor.Exit(locker);
      }
  }

  class Bug00013
  { //: Runnable {

      static int totalThreads = Int32.MaxValue;

      const bool    wasteSpace  = true;
      const bool    showPended  = false;

      const int        maxLinks    =   10;
      const int        maxThreads  =   10;
      const int        maxActive   =    4;
      const int        nodeTraverse=   50;
      const int        linkTraverse=  500;

      const int        newThreads  =    2; //10;
      const int        newNodes    =  100;
      const int        newLinks    =  700;
      const int        newRejects  = 1000;

      // conversion filler
      public static void DebugDumpThreadAnchorTable(int i) {}
      private static void DebugBreak() {
          Assert(false, "DebugBreak");
      }
      private static void Assert(bool b) {
          Assert(b, "Assertion failed");
      }
      private static void Assert(bool b, String s) {
          if (!b) {
              DebugPrintSpin.Lock();
              Console.WriteLine(s);
              Console.WriteLine("Failed Bug00013");
              DebugPrintSpin.Unlock();
              Environment.Exit(1);
          }
      }
      // end conversion

      void nonsyncv() {
      lock (this) {
          DebugPrintSpin.Lock();
          Console.WriteLine("within nonsyncv()");
          DebugDumpThreadAnchorTable(4);
          DebugPrintSpin.Unlock();
      }
      }

      [System.Runtime.CompilerServices.MethodImpl
      (System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
      void syncv() {
          DebugPrintSpin.Lock();
          Console.WriteLine("within syncv()");
          DebugDumpThreadAnchorTable(4);
          DebugPrintSpin.Unlock();
      }

      static void nonsyncs() {
      lock (ClassBug00013) {
          DebugPrintSpin.Lock();
          Console.WriteLine("within nonsyncs()");
          DebugDumpThreadAnchorTable(4);
          DebugPrintSpin.Unlock();
      }
      }

      [System.Runtime.CompilerServices.MethodImpl
      (System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
      static void syncs() {
          DebugPrintSpin.Lock();
          Console.WriteLine("within syncs()");
          DebugDumpThreadAnchorTable(4);
          DebugPrintSpin.Unlock();
      }

      static void usage() {
          Console.WriteLine("usage:  Bug00013 [total_threads]");
          Environment.Exit(1);
      }

      internal static void AppMain(Parameters! config) 
      {
          totalThreads = (int)config.threads;

          undeadThreadids = new int[2*maxThreads];

          DebugPrintSpin.Lock();
          Console.WriteLine("Bug00013.Main()");
          DebugDumpThreadAnchorTable(4);
          DebugPrintSpin.Unlock();

          Object Lock = new Object();
          Bug00013.Lock = Lock;
          DebugPrintSpin.Lock();
          Console.Write("Bug00013.Lock = ");
          Console.WriteLine(Lock.GetHashCode());
          DebugPrintSpin.Unlock();

          anchor = new Bug00013();
          DebugPrintSpin.Lock();
          Console.Write("Bug00013.Type = ");
          Console.WriteLine(anchor.GetType());
          DebugPrintSpin.Unlock();

          ClassBug00013 = anchor.GetType();
          DebugPrintSpin.Lock();
          Console.Write("Bug00013.class = ");
          Console.WriteLine(ClassBug00013.GetHashCode());
          DebugPrintSpin.Unlock();

          bug13Type = anchor.GetType();
          linksType = anchor.links.GetType();

          //==================================================================

          DebugPrintSpin.Lock();
          Console.WriteLine();
          Console.WriteLine("//======================================" +
                                  "=======================================");
          Console.WriteLine();
          Console.WriteLine("before nonsyncv()");
          DebugPrintSpin.Unlock();

          anchor.nonsyncv();

          DebugPrintSpin.Lock();
          Console.WriteLine("after  nonsyncv()");
          DebugPrintSpin.Unlock();

          //==================================================================

          DebugPrintSpin.Lock();
          Console.WriteLine();
          Console.WriteLine("//======================================" +
                                  "=======================================");
          Console.WriteLine();
          Console.WriteLine("before syncv()");
          DebugPrintSpin.Unlock();

          anchor.syncv();

          DebugPrintSpin.Lock();
          Console.WriteLine("after  syncv()");
          DebugPrintSpin.Unlock();

          //==================================================================

          DebugPrintSpin.Lock();
          Console.WriteLine();
          Console.WriteLine("//======================================" +
                                  "=======================================");
          Console.WriteLine();
          Console.WriteLine("before nonsyncs()");
          DebugPrintSpin.Unlock();

          nonsyncs();

          DebugPrintSpin.Lock();
          Console.WriteLine("after  nonsyncs()");
          DebugPrintSpin.Unlock();

          //==================================================================

          DebugPrintSpin.Lock();
          Console.WriteLine();
          Console.WriteLine("//======================================" +
                                  "=======================================");
          Console.WriteLine();
          Console.WriteLine("before syncs()");
          DebugPrintSpin.Unlock();

          syncs();

          DebugPrintSpin.Lock();
          Console.WriteLine("after  syncs()");
          DebugPrintSpin.Unlock();

          //==================================================================

          Thread newThread = new Thread(new ThreadStart(anchor.run));
          DebugPrintSpin.Lock();
          Console.WriteLine();
          Console.WriteLine("//======================================" +
                                  "=======================================");
          Console.WriteLine();
          Console.Write("newThread = ");
          Console.WriteLine(newThread.GetHashCode());
          DebugPrintSpin.Unlock();

          //
          //new WatchDog().start();
          //try {
          //  Thread.sleep(1000);
          //}
          //catch (InterruptedException e) {
          //}
          ///*  

          newThread.Start();

          deadman = System.DateTime.Now;
          for (;;) {
              //try {
                  Thread.Sleep(25000);
                  //} catch(ThreadInterruptedException) {
                  //DebugPrintSpin.Lock();
                  //Console.WriteLine("!");
                  //DebugPrintSpin.Unlock();
                  // }
              lock (Lock) {
                  DebugPrintSpin.Lock();
                  long deadms = (System.DateTime.Now - deadman).Milliseconds;
                  if (deadms > 100000) {
                      Console.WriteLine();
                      Console.WriteLine("Bug00013.Main()"
                                              + "  deadman expired");
                      DebugDumpThreadAnchorTable(4);
                      DebugBreak();
                  }
                  else {
                      Console.WriteLine();
                      Console.Write(" ?<0,");
                      Console.Write(numThreadsAlive);
                      Console.Write(",");
                      Console.Write(numThreadsActive);
                      DebugPrintUndead();
                      Console.Write("> ");
                  }
                  DebugPrintSpin.Unlock();
                  if (numThreadsAlive == 0) break;
              }
          }
          Console.WriteLine("Passed Bug00013");
      }

      Bug00013() {
          lock (Lock) {
              nodeid = nodeidGenerator++;
          }
          nodename = ("Node" + nodeid);
          links = new Bug00013[maxLinks];
      }

      //
      //public override String ToString() {
      //  if (nodename != null) return(nodename);
      //  return(base.ToString());
      //}
      //

      public void run() {
          int myNumThreadsAlive;
          int myNumThreadsActive;
          lock (Lock) {
              Assert(numThreadsAlive >= 0,
                     "run()  numThreadsAlive should be >= 0");
              Assert(numThreadsActive >= 0,
                     "run()  numThreadsActive should be >= 0");
              numThreadsAlive++;
              myNumThreadsAlive = numThreadsAlive;
              numThreadsActive++;
              myNumThreadsActive = numThreadsActive;
              threadid = ++threadidGenerator;
              recordUndead();
          }
          DebugPrintSpin.Lock();
          Console.WriteLine();
          Console.Write("T+<");
          Console.Write(threadid);
          Console.Write(",");
          Console.Write(myNumThreadsAlive);
          Console.Write(",");
          Console.Write(myNumThreadsActive);
          Console.Write("> ");
          DebugPrintSpin.Unlock();
          if ((myNumThreadsAlive <= maxThreads)
               && (threadid < totalThreads)) {
              bool conflict = false;
              lock (this) {
                  if (random == null) {
                      random = new Random();
                  }
                  else {
                      conflict = true;
                  }
              }
              if (!conflict) {
                  traversalLimit = nodeTraverse;
                  Bug00013 last = this;
                  Bug00013 next = this;
                  Bug00013 node = new Bug00013();
                  anchor.addLink(this, node);

                  for (int i = 0; i < newThreads; i++) {
                      traversalLimit = nodeTraverse;
                      int strides = 0;
                      for (int j = 0; j < newNodes; j++) {
                          recordUndead();
                          node = new Bug00013();
                          if (wasteSpace) {
                              while (newNodes
                                     < newRejects*random.NextDouble()) {
                                  Object ooo = new int[1057];
                                  node = new Bug00013();
                              }
                          }
                          strides = 100 + (int)(1000*random.NextDouble());
                          last = traverse(last, strides);
                          while (!addLink(last, node)) {
                              strides = 100 + (int)(1000*random.NextDouble());
                              last = traverse(last, strides);
                          }
                          strides = 100 + (int)(1000*random.NextDouble());
                          next = traverse(next, strides);
                          while (!addLink(next, node)) {
                              strides = 100 + (int)(1000*random.NextDouble());
                              next = traverse(next, strides);
                          }
                      }
                      DebugPrintSpin.Lock();
                      Console.Write("n");
                      Console.Write(threadid);
                      DebugPrintSpin.Unlock();
                      traversalLimit = linkTraverse;
                      for (int j = 4*newNodes; j < newLinks; j += 2) {
                          last = traverse(last, strides);
                          next = traverse(next, strides);
                          while (!addLink(last, next)) {
                              strides = 100 + (int)(1000*random.NextDouble());
                              last = traverse(last, strides);
                              strides = 100 + (int)(1000*random.NextDouble());
                              next = traverse(next, strides);
                          }
                      }
                      DebugPrintSpin.Lock();
                      Console.Write("l");
                      Console.Write(threadid);
                      DebugPrintSpin.Unlock();

                      Thread newThread = new Thread(new ThreadStart(last.run));
                      DebugPrintSpin.Lock();
                      Console.Write("t");
                      Console.Write(threadid);
                      DebugPrintSpin.Unlock();
                      newThread.Start();
                  }
              }
          }
          lock (Lock) {
              recordUndead();
              Assert(numThreadsAlive > 0,
                     "run()  numThreadsAlive should be > 0");
              Assert(numThreadsActive > 0,
                     "run()  numThreadsActive should be > 0");
              numThreadsAlive--;
              myNumThreadsAlive = numThreadsAlive;
              numThreadsActive--;
              myNumThreadsActive = numThreadsActive;
          }
          DebugPrintSpin.Lock();
          Console.WriteLine();
          Console.Write("T-<");
          Console.Write(threadid);
          Console.Write(",");
          Console.Write(myNumThreadsAlive);
          Console.Write(",");
          Console.Write(myNumThreadsActive);
          Console.Write("> ");
          DebugPrintSpin.Unlock();
          notifyPending();
      }

      Bug00013 traverse(Bug00013 node, int numStrides) {
          lock (Lock) {
              recordUndead();
          }
          for (int i = 0; i < numStrides; i++) {
              Assert(traversalsLeft >= 0);
              if (traversalsLeft > traversalLimit) {
                  traversalsLeft = traversalLimit;
              }
              if (traversalsLeft == 0) {
                  bool pended = false;
                  int myNumThreadsAlive;
                  int myNumThreadsActive;
                  lock (Lock) {
                      Assert(numThreadsActive > 0);
                      Assert(nextPending == null);
                      if ((numThreadsActive > maxActive)
                          || (pendingQueue != null)) {
                          pended = showPended;
                          isPending = true;
                          if (pendingQueue != null) {
                              Bug00013 pend = pendingQueue;
                              for (;;) {
                                  Bug00013 curr = pend;
                                  pend = curr.nextPending;
                                  if (pend == null) {
                                      curr.nextPending = this;
                                      break;
                                  }
                              }
                          }
                          else {
                              pendingQueue = this;
                          }
                      }
                      numThreadsActive--;
                      myNumThreadsAlive = numThreadsAlive;
                      myNumThreadsActive = numThreadsActive;
                  }
                  if (pended) {
                      DebugPrintSpin.Lock();
                      Console.WriteLine();
                      Console.Write("T_<");
                      Console.Write(threadid);
                      Console.Write(",");
                      Console.Write(myNumThreadsAlive);
                      Console.Write(",");
                      Console.Write(myNumThreadsActive);
                      Console.Write("> ");
                      DebugPrintSpin.Unlock();
                  }
                  notifyPending();
                  lock (this) {
                      while (isPending) {
                          //try {
                              Monitor.Wait(this);
                              //} catch(ThreadInterruptedException) {}
                      }
                      Assert(nextPending == null);
                  }
                  lock (Lock) {
                      Assert(numThreadsAlive > 0,
                             "run()  numThreadsAlive should be > 0");
                      Assert(numThreadsActive >= 0,
                             "run()  numThreadsActive should be >= 0");
                      myNumThreadsAlive = numThreadsAlive;
                      numThreadsActive++;
                      myNumThreadsActive = numThreadsActive;
                      recordUndead();
                  }
                  if (pended) {
                      DebugPrintSpin.Lock();
                      Console.WriteLine();
                      Console.Write("T=<");
                      Console.Write(threadid);
                      Console.Write(",");
                      Console.Write(myNumThreadsAlive);
                      Console.Write(",");
                      Console.Write(myNumThreadsActive);
                      Console.Write("< ");
                      DebugPrintSpin.Unlock();
                  }
                  traversalsLeft = traversalLimit;
              }
              traversalsLeft--;
              lock (node) {
                  if (node.numLinks > 0) {
                      int link = (int)(node.numLinks*random.NextDouble());
                      Assert(link >= 0,
                             "traverse()  selected link should be nonnegative");
                      Assert(link < node.numLinks,
                             "traverse()  selected link should be < numLinks");
                      if (node.links[link] == null) {
                          DebugPrintSpin.Lock();
                          Console.WriteLine("BUG at link "+link);
                          for (int ii = 0; ii < node.numLinks; ii++) {
                              Console.WriteLine("link["+ii+"] is "
                                                      +node.links[ii]);
                          }
                          Console.WriteLine("link["+link+"] is "
                                                  +node.links[link]);
                          DebugPrintSpin.Unlock();
                      }
                      Assert(node.links[link] != null,
                             "traverse()  selected link should not be null");
                      node = node.links[link];
                  }
              }
          }
          return(node);
      }

      bool addLink(Bug00013 last, Bug00013 next) {
          Assert(last != null, "addlink()  last should not be null");
          Assert(next != null, "addlink()  next should not be null");
          if (last == next) return(false);
          bool lastLinked = false;
          int lastLinks;
          bool nextLinked = false;
          int nextLinks;
          lock (last) {
              checkConsistency();
              if (last.numLinks < maxLinks) {
                  lastLinked = true;
                  for (int i = 0; i < last.numLinks; i++) {
                      if (last.links[i] == next) {
                          lastLinked = false;
                          break;
                      }
                  }
                  if (lastLinked) {
                      last.links[last.numLinks++] = next;
                      lastLinks = last.numLinks;
                  }
              }
              checkConsistency();
          }
          if (!lastLinked) {
              //  last can't take another link
              //  or these two nodes are already linked
              return(false);
          }
          lock (next) {
              checkConsistency();
              if (next.numLinks < maxLinks) {
                  nextLinked = true;
                  for (int i = 0; i < next.numLinks; i++) {
                      Assert(next.links[i] != last,
                             "addlink()  should not already be linked to last");
                  }
                  next.links[next.numLinks++] = last;
                  nextLinks = next.numLinks;
              }
              checkConsistency();
          }
          if (nextLinked) {
              return(true);
          }
          lock (last) {
              checkConsistency();
              Assert(last.numLinks>0, "addlink()  last.numLinks should be > 0");
              last.numLinks--;
              for (int i = 0; i < last.numLinks; i++) {
                  if (last.links[i] == next) {
                      last.links[i] = last.links[last.numLinks];
                      break;
                  }
              }
              last.links[last.numLinks] = null;
              checkConsistency();
          }
          return(false);
      }

      void notifyPending() {
          lock (Lock) {
              Bug00013 firstPending = pendingQueue;
              if ((firstPending != null) && (numThreadsActive < maxActive)) {
                  pendingQueue = firstPending.nextPending;
                  firstPending.nextPending = null;
                  lock (firstPending) {
                      firstPending.isPending = false;
                      Monitor.PulseAll(firstPending);
                  }
              }
          }
      }

      void recordUndead() {
          if (threadid == 0) DebugBreak();
          deadman = System.DateTime.Now;
          for (int i = 0; i < numUndeadThreadids; i++) {
              if (undeadThreadids[i] == threadid) {
                  return;
              }
              if (undeadThreadids[i] > threadid) {
                  if (numUndeadThreadids == undeadThreadids.Length) {
                      numUndeadThreadids--;
                  }
                  for (int j = numUndeadThreadids; j > i; j--) {
                      undeadThreadids[j] = undeadThreadids[j-1];
                  }
                  undeadThreadids[i] = threadid;
                  numUndeadThreadids++;
                  return;
              }
          }
          if (numUndeadThreadids == undeadThreadids.Length) return;
          undeadThreadids[numUndeadThreadids++] = threadid;
      }

      static void DebugPrintUndead() {
          char sep = ':';
          for (int i = 0; i < numUndeadThreadids; i++) {
              Console.Write(sep);
              Console.Write(undeadThreadids[i]);
              sep = ',';
          }
          numUndeadThreadids = 0;
      }

      private void checkConsistency() {
          bool locked = false;
          if (this.GetType() != bug13Type) {
              if (!locked) {
                  DebugPrintSpin.Lock();
                  Console.WriteLine();
                  Console.Write("Bug00013 ");
                  Console.Write(this);
                  Console.WriteLine(":");
                  locked = true;
              }
              Console.Write("    vtable ");
              Console.Write(this.GetType());
              Console.Write(" s/b ");
              Console.Write(bug13Type);
              Console.WriteLine();
          }
          if (links.GetType() != linksType) {
              if (!locked) {
                  DebugPrintSpin.Lock();
                  Console.WriteLine();
                  Console.Write("Bug00013 ");
                  Console.Write(this);
                  Console.WriteLine(":");
                  locked = true;
              }
              Console.Write("    links.vtable ");
              Console.Write(links.GetType());
              Console.Write(" s/b ");
              Console.Write(linksType);
              Console.WriteLine();
          }
          if (links.Length != maxLinks) {
              if (!locked) {
                  DebugPrintSpin.Lock();
                  Console.WriteLine();
                  Console.Write("Bug00013 ");
                  Console.Write(this);
                  Console.WriteLine(":");
                  locked = true;
              }
              Console.Write("    links.Length ");
              Console.Write(links.Length);
              Console.Write(" s/b ");
              Console.Write(maxLinks);
              Console.WriteLine();
          }
          for (int i = 0; i < links.Length; i++) {
              if (links[i] == null) continue;
              if (links[i].GetType() != bug13Type) {
                  if (!locked) {
                      DebugPrintSpin.Lock();
                      Console.WriteLine();
                      Console.Write("Bug00013 ");
                      Console.Write(this);
                      Console.WriteLine(":");
                      locked = true;
                  }
                  Console.Write("    links[");
                  Console.Write(i);
                  Console.Write("].vtable ");
                  Console.Write(this.GetType());
                  Console.Write(" s/b ");
                  Console.Write(bug13Type);
                  Console.WriteLine();
              }
          }
          if (locked) {
              DebugBreak();
              DebugPrintSpin.Unlock();
          }
      }

      //  node fields

      int                     nodeid;
      String                  nodename;
      int                     numLinks;
      Bug00013[]              links;

      //  thread/runnable fields

      int                     threadid;
      Random                  random;
      int                     traversalLimit;
      int                     traversalsLeft;
      bool                 isPending;
      Bug00013                nextPending;

      //  global/statics

      static Bug00013         anchor;

      static Object           Lock;
      static int              nodeidGenerator;
      static int              threadidGenerator;
      static int              numThreadsAlive;
      static int              numThreadsActive;
      static DateTime         deadman;
      static Type             ClassBug00013;
      static Bug00013         pendingQueue;
      static int              numUndeadThreadids;
      static int[]            undeadThreadids;

      static Type             bug13Type;
      static Type             linksType;
  }

  class WatchDog
  { // : Thread {
      WatchDog() {} //: base("WatchDog") {}

      public void run() {
          DebugPrintSpin.Lock();
          Console.WriteLine("WatchDog sleeping");
          DebugPrintSpin.Unlock();
          //try {
              Thread.Sleep(200000);
              //} catch(ThreadInterruptedException) {
              //DebugPrintSpin.Lock();
              //Console.WriteLine("!");
              //DebugPrintSpin.Unlock();
              //}
          DebugPrintSpin.Lock();
          Console.WriteLine("WatchDog awake");
          Bug00013.DebugDumpThreadAnchorTable(4);
          Console.WriteLine("WatchDog exitting");
          DebugPrintSpin.Unlock();
      }
  }
}
