package com.dbxml.db.enterprise.sync;

/*
 * dbXML - Native XML Database
 * Copyright (C) 1999-2004  The dbXML Group, L.L.C.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: WorkerThread.java,v 1.7 2004/08/22 04:18:42 bradford Exp $
 */

import java.io.*;
import java.util.*;

import com.dbxml.db.client.CollectionClient;
import com.dbxml.db.client.dbXMLClient;
import com.dbxml.labrador.types.Variant;
import com.dbxml.xml.dom.DOMHelper;
import com.dbxml.xml.dom.DOMUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * WorkerThread
 */

public final class WorkerThread extends Thread {
   private static final String[] EmptyStrings = new String[0];

   private String currentGroup = "";
   private String currentFile = "";
   private int percentComplete;
   private boolean cancel;
   private boolean error;
   private List log = new ArrayList();
   private Set groups;

   private SyncManagerClient sourceSyncClient;
   private SyncManagerClient localSyncClient;
   private dbXMLClient sourceContentClient;
   private dbXMLClient localContentClient;
   private Date lastSyncDate;

   public SyncManagerClient getSourceSyncClient() {
      return sourceSyncClient;
   }

   public void setSourceSyncClient(SyncManagerClient sourceSyncClient) {
      this.sourceSyncClient = sourceSyncClient;
   }

   public SyncManagerClient getLocalSyncClient() {
      return localSyncClient;
   }

   public void setLocalSyncClient(SyncManagerClient localSyncClient) {
      this.localSyncClient = localSyncClient;
   }

   public dbXMLClient getSourceContentClient() {
      return sourceContentClient;
   }

   public void setSourceContentClient(dbXMLClient sourceContentClient) {
      this.sourceContentClient = sourceContentClient;
   }

   public dbXMLClient getLocalContentClient() {
      return localContentClient;
   }

   public void setLocalContentClient(dbXMLClient localContentClient) {
      this.localContentClient = localContentClient;
   }

   public Date getLastSyncDate() {
      return lastSyncDate;
   }

   public String getCurrentGroup() {
      return currentGroup;
   }

   public String getCurrentFile() {
      return currentFile;
   }

   public int getPercentComplete() {
      return percentComplete;
   }

   public List getLog() {
      return log;
   }

   public boolean isError() {
      return error;
   }

   public void cancelWork() {
      cancel = true;
   }

   public String[] listGroups() {
      if ( groups != null )
         return (String[])groups.toArray(EmptyStrings);
      else
         return EmptyStrings;
   }

   public void addGroup(String group) {
      if ( groups == null )
         groups = new TreeSet();
      if ( !groups.contains(group) )
         groups.add(group);
   }

   public void removeGroup(String group) {
      if ( groups != null && groups.contains(groups) ) {
         groups.remove(group);
         if ( groups.isEmpty() )
            groups = null;
      }
   }

   public void run() {
      cancel = false;

      Map tempFiles = new HashMap();   // String to File
      Map delList = new HashMap();     // String to CollectionClient
      Map lclColCache = new HashMap(); // String to CollectionClient
      Set lclColXML = new TreeSet();   // String
      Map srcColCache = new HashMap(); // String to CollectionClient
      Set srcColXML = new TreeSet();   // String

      String src_host = sourceContentClient.getProperty(dbXMLClient.HOST)
         + ':' + sourceContentClient.getProperty(dbXMLClient.PORT);

      lastSyncDate = new Date(0);
      try {
         localSyncClient.getMachineID();
         Document doc = localContentClient.getContent("/system/SysConfig/sync" + src_host + ".xml").getDocument();
         Element e = doc.getDocumentElement();
         lastSyncDate = new Variant(e.getAttribute("date")).getDate();
      }
      catch ( Exception e ) {
         // Document doesn't exist I guess
         //out.println(e.getMessage());
      }

      try {
         // Do Stuff
         Manifest mf;
         if ( groups != null )
            mf = sourceSyncClient.listGroupChanges(lastSyncDate, (String[])groups.toArray(EmptyStrings));
         else
            mf = sourceSyncClient.listAllChanges(lastSyncDate);
         Date startTime = mf.getDate();
         String[] grps = mf.getGroupNames();
         int totalDocs = 0;
         for ( int i = 0; i < grps.length; i++ )
            totalDocs += mf.getGroupContent(grps[i]).length;

         if ( grps.length > 0 ) {
            log.add("Updating " + grps.length + " files");
            int docNum = 0;
            for ( int i = 0; !cancel && i < grps.length; i++ ) {
               currentGroup = grps[i];
               String[] files = mf.getGroupContent(currentGroup);
               log.add("Group '" + currentGroup + "' contains " + files.length + " updated files");
               for ( int j = 0; !cancel && j < files.length; j++ ) {
                  currentFile = files[j];
                  int idx = currentFile.lastIndexOf('/');
                  String colName = currentFile.substring(0, idx);
                  String docName = currentFile.substring(idx + 1);
                  Content c = mf.getContent(currentFile);

                  CollectionClient lclCol = (CollectionClient)lclColCache.get(colName);
                  if ( lclCol == null ) {
                     lclCol = localContentClient.getCollection(colName);
                     lclColCache.put(colName, lclCol);
                     if ( lclCol.getCollectionType() == CollectionClient.TYPE_DOCUMENTS )
                        lclColXML.add(colName);
                  }

                  if ( c.getStatus() != Constants.STATUS_DELETED ) {
                     CollectionClient srcCol = (CollectionClient)srcColCache.get(colName);
                     if ( srcCol == null ) {
                        srcCol = sourceContentClient.getCollection(colName);
                        srcColCache.put(colName, srcCol);
                        if ( srcCol.getCollectionType() == CollectionClient.TYPE_DOCUMENTS )
                           srcColXML.add(colName);
                     }
                     File f = File.createTempFile("dbxml_", ".sync");
                     tempFiles.put(currentFile, f);
                     if ( srcColXML.contains(colName) ) {
                        String doc = srcCol.getDocumentAsText(docName);
                        FileOutputStream fos = new FileOutputStream(f);
                        BufferedOutputStream bos = new BufferedOutputStream(fos, 4096);
                        OutputStreamWriter osw = new OutputStreamWriter(bos, "UTF8");
                        osw.write(doc);
                        osw.flush();
                        osw.close();
                     }
                     else {
                        byte[] doc = srcCol.getValue(docName);
                        FileOutputStream fos = new FileOutputStream(f);
                        fos.write(doc);
                        fos.close();
                     }
                  }
                  else
                     delList.put(currentFile, lclCol);
                  percentComplete = 100 * docNum++ / totalDocs;
               }
            }

            if ( !cancel ) {
               Iterator iter = delList.entrySet().iterator();
               while ( iter.hasNext() ) {
                  Map.Entry entry = (Map.Entry)iter.next();
                  String path = (String)entry.getKey();
                  CollectionClient col = (CollectionClient)entry.getValue();
                  int idx = path.lastIndexOf('/');
                  String docName = path.substring(idx + 1);
                  col.remove(docName);
               }

               iter = tempFiles.entrySet().iterator();
               while ( iter.hasNext() ) {
                  Map.Entry entry = (Map.Entry)iter.next();
                  String path = (String)entry.getKey();
                  File f = (File)entry.getValue();
                  int idx = path.lastIndexOf('/');
                  String colName = path.substring(0, idx);
                  String docName = path.substring(idx + 1);

                  CollectionClient lclCol = (CollectionClient)lclColCache.get(colName);
                  if ( lclCol == null ) {
                     lclCol = localContentClient.getCollection(colName);
                     lclColCache.put(colName, lclCol);
                     if ( lclCol.getCollectionType() == CollectionClient.TYPE_DOCUMENTS )
                        lclColXML.add(colName);
                  }

                  if ( lclColXML.contains(colName) ) {
                     FileInputStream fis = new FileInputStream(f);
                     BufferedInputStream bis = new BufferedInputStream(fis, 4096);
                     InputStreamReader isr = new InputStreamReader(bis, "UTF8");
                     char[] buf = new char[(int)f.length()];
                     int size = isr.read(buf);
                     isr.close();
                     lclCol.setDocumentAsText(docName, new String(buf, 0, size));
                  }
                  else {
                     FileInputStream fis = new FileInputStream(f);
                     byte[] buf = new byte[(int)f.length()];
                     fis.read(buf);
                     fis.close();
                     lclCol.setValue(docName, buf);
                  }
               }
            }
            log.add("Synchronization complete");
         }
         else
            log.add("No files are out of date");

         if ( !cancel ) {
            // Write the last sync to the Database
            Document doc = DOMHelper.newDocument();
            Element srvElem = doc.createElement("server");
            doc.appendChild(srvElem);
            srvElem.setAttribute("host", src_host);
            srvElem.setAttribute("date", new Variant(startTime).getString());
            for ( int i = 0; i < log.size(); i++ ) {
               Element logElem = doc.createElement("log");
               DOMUtils.setText(logElem, (String)log.get(i));
               srvElem.appendChild(logElem);
            }

            CollectionClient col = localContentClient.getCollection("/system/SysConfig");
            col.setDocument("sync" + src_host + ".xml", doc);
         }
      }
      catch ( Exception e ) {
         error = true;
         log.add("Exception: " + e.getMessage());
         log.add("Group: " + currentGroup + " -- File: " + currentFile);
         log.add("Synchronization cancelled");
      }
      finally {
         // Delete any temporary files
         Iterator iter = tempFiles.values().iterator();
         while ( iter.hasNext() ) {
            try {
               File f = (File)iter.next();
               f.delete();
            }
            catch ( Exception e ) {
               // Whatever
            }
         }
      }
   }
}
