content: /**
   * Copyright (C) 2010 EdgyTech LLC.
   *
   * Licensed under the Apache License, Version 2.0 (the "License"); you may not
   * use this file except in compliance with the License. You may obtain a copy of
   * the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   * License for the specific language governing permissions and limitations under
   * the License.
   */
  package com.edgytech.umongo;
  
  import com.edgytech.swingfast.*;
  import com.google.gson.Gson;
  import com.google.gson.GsonBuilder;
  import com.google.gson.JsonElement;
  import com.google.gson.JsonParser;
  import com.mongodb.BasicDBObject;
  import com.mongodb.CommandResult;
  import com.mongodb.DB;
  import com.mongodb.DBCollection;
  import com.mongodb.DBCursor;
  import com.mongodb.DBObject;
  import java.awt.Component;
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.util.Iterator;
  import java.util.concurrent.ExecutionException;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  import javax.swing.SwingUtilities;
  import javax.swing.tree.DefaultMutableTreeNode;
  import javax.swing.tree.TreePath;
  
  /**
   *
   * @author antoine
   */
  public class DocView extends Zone implements EnumListener, TabInterface, Runnable {
  
      enum Item {
  
          docTree,
          tabDiv,
          tabTitle,
          tabClose,
          refresh,
          append,
          expandText,
          expandTextArea,
          indent,
          spawn,
          export,
          cursor,
          getMore,
          getAll,
          tools,
          startAutoUpdate,
          stopAutoUpdate,
          expandAll,
          collapseAll
      }
      Iterator<DBObject> iterator;
      DBCursor dbcursor;
      TabbedDiv tabbedDiv;
      Thread updateThread;
      String updateType;
      int updateInterval;
      int updateCount;
      boolean running;
      DbJob job;
  
      public DocView(String id, String label, DbJob job, Object root) {
          try {
              xmlLoad(Resource.getXmlDir(), Resource.File.docView, null);
          } catch (Exception ex) {
              getLogger().log(Level.SEVERE, null, ex);
          }
          setEnumBinding(Item.values(), this);
  
          setId(id);
          setLabel(label);
          this.job = job;
          setStringFieldValue(Item.tabTitle, label);
  
          getTree().label = root.toString();
  
          if (job != null) {
              if (job.getButton() != null) {
                  getComponentBoundUnit(Item.spawn).enabled = true;
              }
  
              ((MenuItem) getBoundUnit(Item.refresh)).enabled = true;
              ((MenuItem) getBoundUnit(Item.append)).enabled = true;
              ((MenuItem) getBoundUnit(Item.startAutoUpdate)).enabled = true;
          }
      }
  
      /**
       * create a doc view with static document
       *
       * @param id
       * @param label
       * @param job
       * @param root
       * @param doc
       */
      public DocView(String id, String label, DbJob job, Object root, DBObject doc) {
          this(id, label, job, root);
  
          if (doc != null) {
              addDocument(doc, job, true);
          }
      }
  
      /**
       * create a doc view with an iterator or a cursor
       *
       * @param id
       * @param label
       * @param job
       * @param root
       * @param iterator
       */
      public DocView(String id, String label, DbJob job, Object root, Iterator<DBObject> iterator) {
          this(id, label, job, root);
  
          if (iterator instanceof DBCursor) {
              this.dbcursor = (DBCursor) iterator;
              this.iterator = dbcursor;
              ((MenuItem) getBoundUnit(Item.refresh)).enabled = true;
              ((MenuItem) getBoundUnit(Item.startAutoUpdate)).enabled = true;
          } else {
              this.iterator = iterator;
          }
          getMore(null);
      }
  
  //    /**
  //     * create a doc view from a reusable command with existing result
  //     * @param id
  //     * @param label
  //     * @param job
  //     * @param db
  //     * @param cmd
  //     * @param result 
  //     */
  //    public DocView(String id, String label, DbJob job, DB db, DBObject cmd, DBObject result) {
  //        this(id, label, job, db.getName() + ": " + cmd, result);
  //        this.db = db;
  //        this.cmd = cmd;
  //
  //        ((MenuItem) getBoundUnit(Item.startAutoUpdate)).enabled = true;
  //        ((MenuItem) getBoundUnit(Item.refresh)).enabled = true;
  //        ((MenuItem) getBoundUnit(Item.append)).enabled = true;
  //        if (result == null) {
  //            refresh();
  //        }
  //    }
  //    
  //    /**
  //     * create a doc view to run a simple command against a database
  //     * @param id
  //     * @param label
  //     * @param col
  //     * @param panel 
  //     * @param cmdStr 
  //     */
  //    public DocView(String id, String label, DB db, String cmdStr) {
  //        this(id, label, null, db, new BasicDBObject(cmdStr, 1), null);
  //    }
  //    
  //    /**
  //     * create a doc view to run a simple command against a collection
  //     * @param id
  //     * @param label
  //     * @param col
  //     * @param cmdStr 
  //     */
  //    public DocView(String id, String label, DBCollection col, String cmdStr) {
  //        this(id, label, null, col.getDB(), new BasicDBObject(cmdStr, col.getName()), null);
  //    }
      Tree getTree() {
          return (Tree) getBoundUnit(Item.docTree);
      }
  
      public DBCursor getDBCursor() {
          return dbcursor;
      }
  
      public void close(ButtonBase button) {
          if (dbcursor != null) {
              dbcursor.close();
              dbcursor = null;
          }
          tabbedDiv.removeTab(this);
      }
  
      void addToTabbedDiv() {
          tabbedDiv = UMongo.instance.getTabbedResult();
          tabbedDiv.addTab(this, true);
  
          getTree().expandNode(getTree().getTreeNode());
      }
  
      public void actionPerformed(Enum enm, XmlComponentUnit unit, Object src) {
      }
  
      public void export(ButtonBase button) throws IOException {
          // export should be run in thread, to prevent concurrent mods
          ExportDialog dia = UMongo.instance.getGlobalStore().getExportDialog();
          if (!dia.show()) {
              return;
          }
          final DocumentSerializer ds = dia.getDocumentSerializer();
          try {
              DefaultMutableTreeNode root = getTree().getTreeNode();
              for (int i = 0; i < root.getChildCount(); ++i) {
                  DefaultMutableTreeNode child = (DefaultMutableTreeNode) root.getChildAt(i);
                  Object obj = child.getUserObject();
                  DBObject doc = null;
                  if (obj instanceof DBObject) {
                      doc = (DBObject) obj;
                  } else if (obj instanceof TreeNodeDocumentField) {
                      doc = (DBObject) ((TreeNodeDocumentField) obj).getValue();
                  } else if (obj instanceof TreeNodeDocument) {
                      doc = ((TreeNodeDocument) obj).getDBObject();
                  }
                  if (doc != null) {
                      ds.writeObject(doc);
                  }
              }
          } finally {
              ds.close();
          }
      }
  
      public Component getTabComponent() {
          return getComponentBoundUnit("tabDiv").getComponent();
      }
  
      public void startAutoUpdate(ButtonBase button) {
          AutoUpdateDialog dia = UMongo.instance.getGlobalStore().getAutoUpdateDialog();
          if (!dia.show()) {
              return;
          }
  
          if (updateThread != null) {
              stopAutoUpdate(null);
          }
  
          updateThread = new Thread(this);
          updateType = dia.getComponentStringFieldValue(AutoUpdateDialog.Item.autoType);
          updateInterval = dia.getComponentIntFieldValue(AutoUpdateDialog.Item.autoInterval);
          updateCount = dia.getComponentIntFieldValue(AutoUpdateDialog.Item.autoCount);
          running = true;
          updateThread.start();
  
          getComponentBoundUnit(Item.stopAutoUpdate).enabled = true;
          getComponentBoundUnit(Item.stopAutoUpdate).updateComponent();
      }
  
      public void stopAutoUpdate(ButtonBase button) {
          running = false;
          try {
              updateThread.interrupt();
              updateThread.join();
          } catch (InterruptedException ex) {
          }
          updateThread = null;
  
          getComponentBoundUnit(Item.stopAutoUpdate).enabled = false;
          getComponentBoundUnit(Item.stopAutoUpdate).updateComponent();
      }
  
      public void run() {
          int i = 0;
          while (running) {
              try {
                  DbJob job = null;
                  if ("Refresh".equals(updateType) || dbcursor != null) {
                      job = getRefreshJob();
                  } else if ("Append".equals(updateType)) {
                      job = getAppendJob();
                  }
                  final DbJob fjob = job;
  
                  SwingUtilities.invokeAndWait(new Runnable() {
  
                      @Override
                      public void run() {
                          fjob.addJob();
                      }
                  });
  
                  try {
                      fjob.join();
                  } catch (InterruptedException ex) {
                      getLogger().log(Level.WARNING, null, ex);
                  } catch (ExecutionException ex) {
                      getLogger().log(Level.WARNING, null, ex);
                  }
  
                  if (updateCount > 0 && ++i >= updateCount) {
                      break;
                  }
  
                  Thread.sleep(updateInterval * 1000);
              } catch (Exception ex) {
                  getLogger().log(Level.SEVERE, null, ex);
              }
          }
          getLogger().log(Level.INFO, "Ran " + i + " updates");
      }
  
      public void refresh(ButtonBase button) {
          getRefreshJob().addJob();
      }
  
      public DbJob getRefreshJob() {
          if (dbcursor != null) {
              return getUpdateCursorJob();
          } else {
              return getRefreshJob(false);
          }
      }
  
      public void append(ButtonBase button) {
          getAppendJob().addJob();
      }
  
      public DbJob getAppendJob() {
          return getRefreshJob(true);
      }
  
      public void spawn(ButtonBase button) {
          if (job != null) {
              job.spawnDialog();
          }
      }
  
      public void expandText(ButtonBase button) throws IOException {
          FormDialog dia = (FormDialog) button.getDialog();
  
          DefaultMutableTreeNode root = getTree().getTreeNode();
          DefaultMutableTreeNode select = getTree().getSelectionNode();
          String txt = "";
          if (select == null || select == root) {
              txt = getTextAllNodes(false);
          } else {
              txt = getTextForNode(select.getUserObject(), false);
          }
          setStringFieldValue(Item.expandTextArea, txt);
          dia.label = this.label;
  
          dia.show();
      }
  
      String getTextAllNodes(boolean indent) throws IOException {
          StringBuilder b = new StringBuilder();
          DefaultMutableTreeNode root = getTree().getTreeNode();
          for (int i = 0; i < root.getChildCount(); ++i) {
              DefaultMutableTreeNode child = (DefaultMutableTreeNode) root.getChildAt(i);
              Object obj = child.getUserObject();
              b.append(getTextForNode(obj, indent));
          }
          return b.toString();
      }
  
      String getTextForNode(Object obj, boolean indent) throws IOException {
          DBObject doc = null;
          if (obj instanceof DBObject) {
              doc = (DBObject) obj;
          } else if (obj instanceof TreeNodeDocumentField) {
              doc = (DBObject) ((TreeNodeDocumentField) obj).getValue();
          } else if (obj instanceof TreeNodeDocument) {
              doc = ((TreeNodeDocument) obj).getDBObject();
          }
          if (doc == null) {
              return "";
          }
  
          final DocumentSerializer ds = new DocumentSerializer(DocumentSerializer.Format.JSON, null);
          ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
          ds.setOutputStream(baos);
          try {
              ds.writeObject(doc);
          } finally {
              ds.close();
          }
  
          String txt = new String(baos.toByteArray());
  
          if (indent) {
              Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
              JsonParser jp = new JsonParser();
              JsonElement je = jp.parse(txt);
              txt = gson.toJson(je);
              // add newline in case there are following docs
              txt += "\n";
          }
          return txt;
      }
  
      public void indent(ButtonBase button) throws IOException {
          DefaultMutableTreeNode root = getTree().getTreeNode();
          DefaultMutableTreeNode select = getTree().getSelectionNode();
          String txt = "";
          if (select == null || select == root) {
              txt = getTextAllNodes(true);
          } else {
              txt = getTextForNode(select.getUserObject(), true);
          }
          setComponentStringFieldValue(Item.expandTextArea, txt);
      }
  
      public DbJob getRefreshJob(final boolean append) {
          if (job == null) {
              return null;
          }
  
          DbJob newJob = new DbJob() {
  
              BasicDBObject result;
  
              @Override
              public Object doRun() throws Exception {
                  result = (BasicDBObject) job.doRun();
                  return null;
              }
  
              @Override
              public String getNS() {
                  return job.getNS();
              }
  
              @Override
              public String getShortName() {
                  return job.getShortName();
              }
  
              @Override
              public void wrapUp(Object res) {
                  super.wrapUp(res);
                  if (res == null && result != null) {
                      if (!append) {
                          getTree().removeAllChildren();
                      }
                      addDocument(result, this);
                      getTree().structureComponent();
                      // result of command should be fully expanded
                      getTree().expandAll();
  
                      // panel info may need to be refreshed
                      if (job.getPanel() != null) {
                          job.getPanel().refresh();
                      }
                  }
              }
          };
  
          return newJob;
      }
  
      public void getMore(final int max) {
          new DbJob() {
  
              @Override
              public Object doRun() throws IOException {
                  int i = 0;
                  while (iterator.hasNext() && (i++ < max || max <= 0)) {
                      DBObject obj = iterator.next();
                      if (obj == null) {
                          break;
                      }
                      addDocument(obj, null);
                  }
                  return null;
              }
  
              @Override
              public String getNS() {
                  if (dbcursor != null) {
                      return dbcursor.getCollection().getFullName();
                  }
                  return getLabel();
              }
  
              @Override
              public String getShortName() {
                  return "Find";
              }
  
              @Override
              public void wrapUp(Object res) {
                  super.wrapUp(res);
                  if (res == null) {
                      // res should be null
                      // should have a cursor id now
                      if (dbcursor != null) {
                          BasicDBObject desc = getDescription(DocView.this.job.getRoot(dbcursor));
                          getTree().label = desc.toString();
                      }
                      getTree().structureComponent();
                      getTree().expandNode(getTree().getTreeNode());
  
                      DocView.this.updateButtons();
                  }
              }
          }.addJob();
      }
  
      public void getMore(ButtonBase button) {
          getMore(UMongo.instance.getPreferences().getGetMoreSize());
      }
  
      public void getAll(ButtonBase button) {
          getMore(0);
      }
  
      public DefaultMutableTreeNode getSelectedNode() {
          TreePath path = getTree().getSelectionPath();
          if (path == null || path.getPathCount() < 2) {
              return null;
          }
          return (DefaultMutableTreeNode) path.getLastPathComponent();
      }
  
      public DBObject getSelectedDocument() {
          TreePath path = getTree().getSelectionPath();
          if (path == null || path.getPathCount() < 2) {
              return null;
          }
          DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getPathComponent(1);
          Object obj = node.getUserObject();
          if (obj instanceof TreeNodeDocumentField) {
              return (DBObject) ((TreeNodeDocumentField) obj).getValue();
          } else if (obj instanceof DBObject) {
              return (DBObject) obj;
          } else if (obj instanceof TreeNodeDocument) {
              return ((TreeNodeDocument) obj).getDBObject();
          }
          return null;
      }
  
      String getSelectedDocumentPath() {
          TreePath path = getTree().getSelectionPath();
          String pathStr = "";
          if (path.getPathCount() < 2) {
              return null;
          }
          for (int i = 2; i < path.getPathCount(); ++i) {
              DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getPathComponent(i);
              String key = ((TreeNodeDocumentField) node.getUserObject()).getKey();
              pathStr += "." + key;
          }
          return pathStr.substring(1);
      }
  
      public DbJob getUpdateCursorJob() {
          if (dbcursor == null) {
              return null;
          }
  //        final int count = getTree().getChildren().size();
          DbJob newJob = new DbJob() {
  
              @Override
              public Object doRun() throws Exception {
                  dbcursor = (DBCursor) job.doRun();
                  return null;
              }
  
              @Override
              public String getNS() {
                  return job.getNS();
              }
  
              @Override
              public String getShortName() {
                  return job.getShortName();
              }
  
              @Override
              public void wrapUp(Object res) {
                  super.wrapUp(res);
                  if (res == null) {
                      iterator = dbcursor;
                      getTree().removeAllChildren();
                      getMore(null);
                  }
              }
          };
  
          return newJob;
      }
  
      private void updateButtons() {
          boolean canGetMore = false;
          if (dbcursor != null) {
              // can always get more from tailable cursor
              canGetMore = iterator.hasNext() || dbcursor.getCursorId() > 0;
          } else {
              canGetMore = iterator.hasNext();
          }
          getComponentBoundUnit(Item.getMore).enabled = canGetMore;
          getComponentBoundUnit(Item.getMore).updateComponent();
          getComponentBoundUnit(Item.getAll).enabled = canGetMore;
          getComponentBoundUnit(Item.getAll).updateComponent();
  
      }
  
      public void addDocument(DBObject doc, DbJob job) {
          addDocument(doc, job, false);
      }
  
      public void addDocument(DBObject doc, DbJob job, boolean expand) {
          TreeNodeLabel node = new TreeNodeDocument(doc, job);
          getTree().addChild(node);
          if (expand) {
              getTree().expandNode(node);
          }
      }
  
  //    protected void appendDoc(DBObject doc) {
  //        TreeNodeLabel node = new TreeNodeLabel();
  //        node.forceTreeNode(MongoUtils.dbObjectToTreeNode(doc));
  //        getTree().addChild(node);
  //    }
      public void collapseAll(ButtonBase button) {
          getTree().collapseAll();
          // need to reexpand root
          getTree().expandNode(getTree().getTreeNode());
      }
  
      public void expandAll(ButtonBase button) {
          getTree().expandAll();
      }
  }
  