!7

sS background = #1009931;
static double minLoadScreenShowingTime = 4.0;
static JDesktopPane desktop;
static ReliableSingleThread rst = new(f updateModules);
static volatile long updateCycles;
static int systemErrorsToKeep = 10;
static L systemErrors = synchroList(new CircularArrayList);
static new Flag stopRequested;
static SimpleLiveValue<S> systemStatus = stringLiveValue("loading");

p {
  _handleException_addHandler(voidfunc(Throwable e) {
    addToListWithMaxSize(systemErrors, e, systemErrorsToKeep);
    infoBox("Error: " + exceptionToStringShort(e));
    LastErrors lastErrors = first(staticModulesOfType(LastErrors));
    if (lastErrors != null) lastErrors.addError(e);
  });
  
  if (swic(activateFramesOf(programID()), "OK")) cleanKill();
  framesBot();
  
  //substance();
  final Component loadingAnim = stefansOS_loadingAnimation(r {
    stopRequested.raise()
  });
  
  try {
    long start = sysNow();
    useDBOf(#1015871);
    db();
    desktop = jDesktopPaneWithSkyPicture_autoUnload(background, Color.black);
    autoFixDesktopPane(desktop);
    waitUntilSysTime(start+toMS(minLoadScreenShowingTime));
  } catch e {
    _handleException(e);
    stopRequested.raise();
  }
  
  bool flag = stopRequested.isUp(); // get before closing animation
  disposeWindow(loadingAnim);
  if (flag)
    ret with showFrame("Stop screen",
      centerAndSouthWithMargins(
        jcenteredlabel("Troubleshooting options will go here."),
        jcenteredbuttons(
          "Start Stefan's OS normally", r restart,
          "Delete session", disableButtonWhileCalcing(func -> bool {
            if (!fileExists(conceptsFile()))
              ret false with infoBox("Session already empty.");
            if (!confirmOKCancel("Really delete your current session?")) false;
            renameFileToSomeBackupName(conceptsFile());
            infoBox("Session deleted. Starting again.");
            disableAllButtonsInWindow(heldInstance(JButton));
            sleepSeconds(3);
            restart();
            false;
          }))));

  autoRestart();
  substance();
  showDesktop();
    
  initAfterDBLoad();
  hideConsole();
}

svoid showDesktop {
  doNothingOnClose(showMaximizedFrame(desktop));
  frameIcon(#1101272, desktop);
  cleanExitOnFrameClose_ifStillInSameFrame(desktop);
  titlePopupMenu_top(desktop, voidfunc(JPopupMenu menu) {
    addMenuItems(menu,
      "Update One Cycle", rst,
      "New Session", rThreadPcallMessageBox(r deleteAllModules),
      "Restore Initial Modules", r initialModules,
    );
  });
}

svoid initAfterDBLoad {
  initialModules();
  
  UpdateCycles uc = conceptWhere(UpdateCycles);
  if (uc != null) updateCycles = uc.value;
  
  for (Module m : onModules()) startModule(m);
  
  addConceptIndex(simpleConceptIndex(rst));
  rst.trigger();
  
  systemStatus.set("running");
}

svoid initialModules {
  if (empty(onModules()))
    showModule(new DynamicModule(#1016067, 'main$WelcomeScreen));
  makeOrShowStaticModuleOfType(ModuleClasses);
}

svoid triggerUpdate { rst.trigger(); }

svoid updateModules {
  ++updateCycles;
  for (Module m : onModules())
    updateModule(m);
}

svoid updateModule(Module m) {
  if (m == null) ret;
  temp m.enter();
  pcall { m.update(); }
}

svoid cleanMeUp {
  systemStatus.set("shutting down");
  for (Module m) if (m.vis != null) pcall {
    m.unvisualize();
  }
  
  for (Module m) cleanUp(m);
}

static L<Module> onModules() { ret conceptsWhere(Module, on := true); }

sbool hasModuleWithFields(Class<? extends Module> c, O... params) {
  ret hasConcept(c, concatArrays(new O[] {on := true}, params));
}

svoid startModule(Module m) { ping(); pcall {
  //addIfNotThere(modules, m);
  lock m.lock;
  if (m.started) ret;
  m.started = true;
  print("Starting module " + m);
  try {
    m.start();
  } catch e {
    m.setError(e);
    _handleException(e);
  }
  rst.trigger();
  if (m.visible) showModule(m);
}}

static Module showModule(final Module m) {
  if (m == null) ret m;
  startModule(m);
  lock m.lock;
  if (m.vis != null) {
    activateInternalFrame(m.vis);
    ret m;
  }
  cset(m, visible := true);
  visualizeModule(m);
  if (m.vis != null) swing {
    Rect r = m.frameRect;
    if (r == null) r = randomRect(desktop.getWidth(), desktop.getHeight(), 10, 150, 100);
    if (r == null) r = Rect(10, 10, 200, 100);
    print("Showing frame at " + r);
    S frameTitle = m.moduleName();
    final JInternalFrame f = showInternalFrame(desktop, frameTitle, r.x, r.y, r.w, r.h, m.vis);
    f.setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
    
    internalFrameTitlePopupMenuItem(f, "Module source", r-thread { pcall {
      S src = m.sourceCode();
      if (src != null) showText(internalFrameTitle(f) + " [Source]", src); else infoBox("No source code found");
    }});
    onInternalFrameIconified(f, r { hideModule(m) });
    onInternalFrameClosing(f, r { deleteModule(m) });
    internalFrameIcon(f, m.iconID);
    m.enhanceFrame(f);
  }
  ret m;
}

svoid showModules(L<? extends Module> l) {
  for (Module m : unnull(l)) showModule(m);
}

sbool deleteModule(Module m) {
  pcall {
    bool warn = isTrue(callOpt(resolveModule(m), 'warnOnDelete));
    if (warn && !confirmOKCancel("Really delete module " + m + "?"))
      false;
  }
  disposeInternalFrame(m.vis);
  removeConcept(m);
  triggerUpdate();
  true;
}

svoid visualizeModule(Module m) {
  pcall {
    if (m.vis == null) m.vis = m.visualize();
    if (m.vis == null) m.vis = defaultVisualize(m);
  }
}

svoid hideModule(final Module m) {
  if (m == null) ret;
  lock m.lock;
  cset(m, visible := false);
  pcall { m.unvisualize(); }
}

svoid revisualizeModule(Module m) {
  pcall {
    if (m == null) ret;
    lock m.lock;
    JInternalFrame frame = getInternalFrame(m.vis);
    if (frame == null) ret;
    m.unvisualize1b();
    m.unvisualize2();
    visualizeModule(m);
    setInternalFrameContents(frame, m.vis);
  }
}

abstract concept Module {
  transient JComponent vis;
  transient bool started;
  transient Lock lock = lock();

  bool on = true, visible;
  Rect frameRect;
  S iconID;
  PersistableThrowable error;
  
  JComponent visualize() { null; }
  void unvisualize() { unvisualize1(); unvisualize2(); }
  void enhanceFrame(JInternalFrame frame) {}
  void start() {}
  void unvisualize1() {
    disposeInternalFrame(getInternalFrame(vis));
    unvisualize1b();
  }
  
  void unvisualize1b() {
    grabFrameRect();
    vis = null;
  }
  void unvisualize2() {}
  void update() {}
  
  void grabFrameRect() {
    JInternalFrame f = getInternalFrame(vis);
    if (f != null)
      cset(this, frameRect := toRect(getBounds(f)));
  }
  
  void cleanMeUp_started() { started = false; }

  void delete() {
    unvisualize();
    cleanUp(this);
    super.delete();
  }

  S sourceCode() {  
    ret javaxSourceOfMyClass1(shortClassName(this));
  }
  
  // for all modules
  void triggerUpdate { rst.trigger(); }
  
  void setModuleIcon(S iconID) {
    if (eq(iconID, this.iconID)) ret;
    this.iconID = iconID;
    internalFrameIcon(vis, iconID);
  }
  
  O resolve() { ret this; }
  O getError() { ret error; }
  S moduleID() { ret str(id); }
  
  void setError(Throwable e) {
    cset(this, error := persistableThrowable(e));
  }
  
  AutoCloseable enter() { null; }
  
  JComponent vis() { ret vis; }
  
  static bool needsParameters() { false; }
  
  S moduleName() {
    ret humanizeFormLabel(shortClassName(this));
  }
} // END CONCEPT MODULE

static JComponent defaultVisualize(Module m) {
  ret jCenteredMultiLineLabel(renderConcept(m)); 
}

static <A extends Module> A findModule(Class<A> c) {
  ret findConcept(c, on := true);
}

// returns module ID
static S findDynModuleOfType(S type) {
  DynamicModule m = findConcept(DynamicModule, on := true, _className := "main$" + type);
  ret m == null ? null : m.moduleID();
}

sbool moduleTypeIs(Module m, S type) {
  if (m == null) false;
  ret eq(moduleResolvedClassName(m), "main$" + type);
}

sS moduleResolvedClassName(Module m) {
  if (m == null) null;
  if (m instanceof DynamicModule)
    ret m/DynamicModule._className;
  ret className(m);
}

// returns module ID
sS findClosestModuleTo(O searcher, S type) {
  JInternalFrame f = getInternalFrame(dm_getVisualization(searcher));
  if (f == null) null;
  Pt p = centerOfRect(toRect(getBounds(f)));
  new Lowest<S> best;
  for (Module m : onModules()) {
    JInternalFrame f2 = getInternalFrame(m.vis);
    if (f2 == null || f2 == f) continue;
    if (type != null && !moduleTypeIs(m, type)) continue;
    Rect r2 = toRect(getBounds(f2));
    best.put(m.moduleID(), rectPointDistance(r2, p));
  }
  ret best!;
}

static <A extends Module> L<A> staticModulesOfType(Class<A> type) {
  ret conceptsWhere(type, on := true);
}

static <A extends Module> L<A> staticModulesOfExactType(Class<A> type) {
  ret filterByExactType(type, staticModulesOfType(type));
}

static L listModules() {
  ret map unwrapDynamicModule(onModules());
}

static L visibleModules() {
  ret map unwrapDynamicModule(objectsWhere(onModules(), visible := true));
}

static O unwrapDynamicModule(Module m) {
  ret m instanceof DynamicModule ? or(m/DynamicModule.o, m) : m;
}

sbool moduleStillThere(O o) {
  Module m = o instanceof Module ? o/Module : (Module) get(o, '_host);
  ret isConceptRegistered(mainConcepts, m);
}

static O getDynModuleByID(S moduleID) {
  if (moduleID == null) null;
  ret resolveModule(getConcept(Module, parseLong(moduleID)));
}
  
sS getInterestingString {
  InterestingString m = findModule(InterestingString);
  ret m == null ? null : m.theString;
}

sS modulesSessionGrab() {
  grabFrameRects();
  ret struct(ll(programID(), localDateWithMilliseconds())) + "\n"
    + mainConcepts.xfullgrab();
}

svoid autoSaveModulesSession() {
  infoBox("Auto-saving session.");
  S grab;
  logQuoted(javaxBackupDir(fsI(programID()) + "/auto-saved-sessions.txt"), grab = modulesSessionGrab());
  infoBox("Auto-save done (" + l(grab) + " chars)");
}

svoid deleteAllModules() {
  autoSaveModulesSession();
  deleteConcepts(Module);
  initialModules();
}

svoid restoreModulesSession(S text) {
  autoSaveModulesSession();
  systemStatus.set("shutting down");
  infoBox("Releasing session");
  cleanMeUp();
  cleanUp(mainConcepts);
  mainConcepts = null;
  //sleepSeconds(1);
  infoBox("Loading session");
  systemStatus.set("loading");
  mainConcepts = new Concepts().load(dropFirstLine(text));
  initAfterDBLoad();
  infoBox("Session restore done");
}

svoid grabFrameRects {
  for (Module m : onModules()) m.grabFrameRect();
}

sclass DynamicModule extends Module {
  S moduleID, _className; // "className" is taken by DynamicObject; _className == null for old-style dyn module
  S oStruct; // serialized dynamic object
  
  transient Class c;
  transient O o;

  *() {}
  *(S *moduleID, S *_className) {}
  *(S *moduleID, S *_className, Class *c) {}
  
  static bool needsParameters() { true; }
    
  AutoCloseable enter() {
    ret castForTemp(callOpt(o, 'enter));
  }

  JComponent visualize() {
    temp enter();
    ret (JComponent) callOpt(o, 'visualize);
  }
  
  void enhanceFrame(final JInternalFrame f) {
    internalFrameTitlePopupMenuItem(f, "Reload", rThread(r reload));
    {
      temp enter();
      pcallOpt(o, 'enhanceFrame, f);
    }
    internalFrameTitle(f, moduleName());
  }
  
  S moduleName() {
    S name = (S) callOpt(o, 'moduleName);
    if (nempty(name)) ret name;
    ret dropSuffixICTrimOneOf(snippetTitle(moduleID), "[Dyn Module]", "[Dyn Module, OK]");
  }
  
  void start() {
    if (moduleID == null) ret;
    if (c == null) c = hotwireDependent(moduleID);
    if (oStruct != null) pcall {
      o = unstructureInRealm(oStruct, c);
    }
    if (o == null)
      if (_className == null)
        o = c;
      else
        o = nu(_getClass(c, _className));
    setOpt(o, _host := this);
    if (o instanceof Class)
      callMain(o);
    else
      callOpt(o, 'start);
  }
  
  void unvisualize2() { callOpt(o, 'unvisualize2); }
  
  void update() { callOpt(o, 'update); }
  
  void cleanMeUp() {
    oStruct = null; pcall { if (o != null && !o instanceof Class) oStruct = struct(o); }
    cleanUpObjectAndItsMainClass(o);
    o = null;
    c = null;
  }
  
  void reload() {
    JInternalFrame frame = getInternalFrame(vis);
    unvisualize1b();
    unvisualize2();
    cleanUp(this); // also sets started to false
    if (frame != null)
      setInternalFrameContents(frame, jcenteredlabel("Reloading..."));
    visible = false;
    startModule(this);
    if (frame != null) {
      cset(this, visible := true);
      visualizeModule(this);
      print("New content: " + vis);
      setInternalFrameContents(frame, vis);
    }
    rst.trigger();
  }
  
  S sourceCode() {  
    ret loadSnippet(moduleID);
  }
  
  O resolve() { ret o; }
  O getError() {
    if (o != null) {
      O error = callOpt(o, 'getError);
      if (error != null) ret error;
    }
    ret super.getError();
  }
  
  toString {
    ret "DynModule " + moduleID + "/" + className;
  }
  
  void setError(Throwable e) {
    if (o != null && isTrue(callOpt(o, 'setError, e))) ret;
    super.setError(e);
  }

}

/*static L resolvedModules() {
  new L l;
  for (Module m : onModules())
    l.add(m.resolve());
  ret l;
}*/

static S moduleID(Module m) {
  ret m == null ? null : m.moduleID();
}

static O resolveModule(Module m) {
  ret m == null ? null : m.resolve();
}

static S makeModule(S moduleLibID) {
  // makes dynamic & static modules
  
  if (isIdentifier(moduleLibID))
    ret moduleID(makeOrShowStaticModuleOfType(moduleLibID));
  
  L<S> l = splitAtSlash(moduleLibID);
  if (!isSnippetID(first(l)))
    fail("Unknown module lib ID: " + moduleLibID);
    
  S snippetID = first(l), className = second(l);
  Class c = hotwireDependent(snippetID);
  Module m = DynamicModule(snippetID, className == null ? null : "main$" + className, c);
  showModule(m);
  ret moduleID(m);
}

static Module makeOrShowStaticModuleOfType(S s) {
  ret makeOrShowStaticModuleOfType(classForName("main$" + s));
}
  
static Module makeOrShowStaticModuleOfType(Class<? extends Module> c) {
  final L<? extends Module> l = staticModulesOfExactType(c);
  ret showModule(empty(l) ? nu(c) : first(l));
}

!include once #1015842 // SavedSessions
!include once #1015885 // Standard Modules
!include once #1015959 // More Standard Modules