package org.tmatesoft.svn.core.internal.wc2.old;

import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetDb;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetStatement;
import org.tmatesoft.svn.core.internal.util.*;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc.SVNTreeConflictUtil;
import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
import org.tmatesoft.svn.core.internal.wc17.SVNWCUtils;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbLock;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbStatus;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbUpgradeData;
import org.tmatesoft.svn.core.internal.wc17.db.SVNWCDb;
import org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbConflicts;
import org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbStatementUtil;
import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbStatements;
import org.tmatesoft.svn.core.internal.wc2.old.SvnOldUpgrade.TextBaseInfo;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.SVNConflictReason;
import org.tmatesoft.svn.core.wc.SVNTreeConflictDescription;
import org.tmatesoft.svn.core.wc2.SvnChecksum;
import org.tmatesoft.svn.core.wc2.SvnChecksum.Kind;
import org.tmatesoft.svn.util.SVNLogType;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class SvnOldUpgradeEntries {
	
	public static WriteBaton writeUpgradedEntries(WriteBaton parentNode, SVNWCDb db,  SVNWCDbUpgradeData upgradeData, File dirAbsPath, 
			Map<String, SVNEntry> entries, SVNHashMap textBases, int targetWorkingCopyFormat) throws SVNException {
		WriteBaton dirNode = new WriteBaton();
		
		SVNEntry thisDir = entries.get("");
		/* If there is no "this dir" entry, something is wrong. */
		if (thisDir == null) {
			SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "No default entry in directory ''{0}''", dirAbsPath);
            SVNErrorManager.error(err, SVNLogType.WC);
		}  
		File oldRootAbsPath = SVNFileUtil.createFilePath(SVNPathUtil.getCommonPathAncestor(
				SVNPathUtil.getAbsolutePath(dirAbsPath.getAbsolutePath()), SVNPathUtil.getAbsolutePath(upgradeData.rootAbsPath.getAbsolutePath())));
		
		assert(oldRootAbsPath != null);
		File dirRelPath = SVNWCUtils.skipAncestor(oldRootAbsPath, dirAbsPath);
		
		/* Write out "this dir" */
		dirNode = writeEntry(true, parentNode, db, upgradeData, thisDir, null, dirRelPath, 
				SVNFileUtil.createFilePath(upgradeData.rootAbsPath, dirRelPath), oldRootAbsPath, thisDir, false, targetWorkingCopyFormat);
				
		for (Iterator<String> names = entries.keySet().iterator(); names.hasNext();) {
            String name = (String) names.next();
            SVNEntry entry = (SVNEntry) entries.get(name);
            TextBaseInfo info = (TextBaseInfo)textBases.get(name);
            if ("".equals(name)) { 
            	continue;
            }
            
            /* Write the entry. Pass TRUE for create locks, because we still use this function for upgrading old working copies. */
            File childAbsPath =  SVNFileUtil.createFilePath(dirAbsPath, name);
            File childRelPath = SVNWCUtils.skipAncestor(oldRootAbsPath, childAbsPath);
            writeEntry(false, dirNode, db, upgradeData, entry, info, childRelPath, 
            		SVNFileUtil.createFilePath(upgradeData.rootAbsPath, childRelPath), oldRootAbsPath, thisDir, true, targetWorkingCopyFormat);
		}
		
		if (dirNode.treeConflicts != null) {
			writeActualOnlyEntries(dirNode.treeConflicts, upgradeData.root.getSDb(), upgradeData.root.getDb(), upgradeData.rootAbsPath, upgradeData.workingCopyId, SVNFileUtil.getFilePath(dirRelPath), targetWorkingCopyFormat);
		}
	
		return dirNode;
	}
	
	private static class DbNode {
		long wcId;
		String localRelPath;
		long opDepth;
		long reposId;
		String reposRelPath;
		String parentRelPath;
		SVNWCDbStatus presence = SVNWCDbStatus.Normal;
		long revision;
		SVNNodeKind kind;  /* ### should switch to svn_wc__db_kind_t */
		SvnChecksum checksum;
		long translatedSize;
		long changedRev;
		SVNDate changedDate;
		String changedAuthor;
		SVNDepth depth;
		SVNDate lastModTime;
		SVNProperties properties;
		boolean isFileExternal;
        SVNProperties inheritedProperties;
	};
	
	private static class DbActualNode {
		long wcId;
		String localRelPath;
		String parentRelPath;
		SVNProperties properties;
		String conflictOld;
		String conflictNew;
		String conflictWorking;
		String propReject;
		String changelist;
		/* ### enum for text_mod */
		String treeConflictData;
	}
		
	static class WriteBaton {
		DbNode base;
		DbNode work;
		DbNode belowWork;
		Map<String, String> treeConflicts;
	};
	
	/* Write the information for ENTRY to WC_DB.  The WC_ID, REPOS_ID and REPOS_ROOT will all be used for writing ENTRY.
	  Transitioning from straight sql to using the wc_db APIs.  For the time being, we'll need both parameters. */
	private static WriteBaton writeEntry(boolean isCalculateEntryNode, WriteBaton parentNode, SVNWCDb db, SVNWCDbUpgradeData upgradeData, SVNEntry entry, TextBaseInfo textBaseInfo,
			File localRelPath, File tmpEntryAbsPath, File rootAbsPath, SVNEntry thisDir, boolean isCreateLocks, int targetWorkingCopyFormat) throws SVNException {
		DbNode baseNode = null;
		DbNode workingNode = null;
		DbNode belowWorkingNode = null;
		DbActualNode actualNode = null;
		
		String parentRelPath = null;
		if (localRelPath != null) {
			parentRelPath = SVNFileUtil.getFilePath(SVNFileUtil.getFileDir(localRelPath));
		}
		
		/* This is how it should work, it doesn't work like this yet because we need proper op_depth to layer the working nodes.

	     Using "svn add", "svn rm", "svn cp" only files can be replaced pre-wcng; directories can only be normal, deleted or added.
	     Files cannot be replaced within a deleted directory, so replaced files can only exist in a normal directory, or a directory that
	     is added+copied.  In a normal directory a replaced file needs a base node and a working node, in an added+copied directory a
	     replaced file needs two working nodes at different op-depths.

	     With just the above operations the conversion for files and directories is straightforward:

	           pre-wcng                             wcng
	     parent         child                 parent     child

	     normal         normal                base       base
	     add+copied     normal+copied         work       work
	     normal+copied  normal+copied         work       work
	     normal         delete                base       base+work
	     delete         delete                base+work  base+work
	     add+copied     delete                work       work
	     normal         add                   base       work
	     add            add                   work       work
	     add+copied     add                   work       work
	     normal         add+copied            base       work
	     add            add+copied            work       work
	     add+copied     add+copied            work       work
	     normal         replace               base       base+work
	     add+copied     replace               work       work+work
	     normal         replace+copied        base       base+work
	     add+copied     replace+copied        work       work+work

	     However "svn merge" make this more complicated.  The pre-wcng "svn merge" is capable of replacing a directory, that is it can
	     mark the whole tree deleted, and then copy another tree on top. 
	     The entries then represent the replacing tree overlayed on the deleted tree.

	       original       replace          schedule in
	       tree           tree             combined tree

	       A              A                replace+copied
	       A/f                             delete+copied
	       A/g            A/g              replace+copied
	                      A/h              add+copied
	       A/B            A/B              replace+copied
	       A/B/f                           delete+copied
	       A/B/g          A/B/g            replace+copied
	                      A/B/h            add+copied
	       A/C                             delete+copied
	       A/C/f                           delete+copied
	                      A/D              add+copied
	                      A/D/f            add+copied

	     The original tree could be normal tree, or an add+copied tree. 
	     Committing such a merge generally worked, but making further tree modifications before commit sometimes failed.

	     The root of the replace is handled like the file replace:

	           pre-wcng                             wcng
	     parent         child                 parent     child

	     normal         replace+copied        base       base+work
	     add+copied     replace+copied        work       work+work

	     although obviously the node is a directory rather then a file.
	     There are then more conversion states where the parent is replaced.

	           pre-wcng                                wcng
	     parent           child              parent            child

	     replace+copied   add                [base|work]+work  work
	     replace+copied   add+copied         [base|work]+work  work
	     replace+copied   delete+copied      [base|work]+work  [base|work]+work
	     delete+copied    delete+copied      [base|work]+work  [base|work]+work
	     replace+copied   replace+copied     [base|work]+work  [base|work]+work
	  */

        if (parentNode == null && entry.getSchedule() != null) {
            String scheduleOperation;
            if (SVNProperty.SCHEDULE_ADD.equals(entry.getSchedule())) {
                scheduleOperation = "addition";
            } else if (SVNProperty.SCHEDULE_DELETE.equals(entry.getSchedule())) {
                scheduleOperation = "deletion";
            } else if (SVNProperty.SCHEDULE_REPLACE.equals(entry.getSchedule())) {
                scheduleOperation = "replacement";
            } else {
                scheduleOperation = entry.getSchedule();
            }

            SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_SCHEDULE, "Working copy root directory is scheduled for {0}; revert it before upgrade.", scheduleOperation);
            SVNErrorManager.error(errorMessage, SVNLogType.WC);
        }

		assert(parentNode != null || entry.getSchedule() == null);
		assert(parentNode == null || parentNode.base != null || parentNode.belowWork != null || parentNode.work != null);
		
		if (entry.getSchedule() == null) {
			if (entry.isCopied() || 
				(entry.getDepth() == SVNDepth.EXCLUDE && parentNode != null && parentNode.base == null && parentNode.work != null)) {
				workingNode = new DbNode();
			} else {
				baseNode = new DbNode();
			}
		}
		else if (entry.isScheduledForAddition()) {
			workingNode = new DbNode();
			if (entry.isDeleted()) {
				if (parentNode != null && parentNode.base != null)
					baseNode = new DbNode();
				else
					belowWorkingNode = new DbNode();
			}
		}
		else if (entry.isScheduledForDeletion()) {
			workingNode = new DbNode();
			if (parentNode != null && parentNode.base != null) 
				baseNode = new DbNode();
			if (parentNode != null && parentNode.work != null)
				belowWorkingNode = new DbNode();
		}
		else if (entry.isScheduledForReplacement()) {
			workingNode = new DbNode();
			if (parentNode != null && parentNode.base != null) 
				baseNode = new DbNode();
			else
				belowWorkingNode = new DbNode();
		}
		
		/* Something deleted in this revision means there should always be a BASE node to indicate the not-present node.  */
		if (entry.isDeleted()) {
			assert(baseNode != null || belowWorkingNode != null);
			assert(!entry.isIncomplete());
			if (baseNode != null)
				baseNode.presence = SVNWCDbStatus.NotPresent;
			else
				belowWorkingNode.presence = SVNWCDbStatus.NotPresent;
		} else if (entry.isAbsent()) {
			assert(baseNode != null && workingNode == null && belowWorkingNode == null);
			assert(!entry.isIncomplete());
			baseNode.presence = SVNWCDbStatus.ServerExcluded;
		}
		
		if (entry.isCopied()) {
			if (SvnOldUpgrade.getEntryCopyFromURL(entry) != null) {
				workingNode.reposId = upgradeData.repositoryId;
				String relPath = SVNURLUtil.getRelativeURL(SvnOldUpgrade.getEntryRepositoryRootURL(thisDir), SvnOldUpgrade.getEntryCopyFromURL(entry), false);
				if (relPath == null)
					workingNode.reposRelPath = null;
				else 
					workingNode.reposRelPath = relPath;
				workingNode.revision = entry.getCopyFromRevision();
				workingNode.opDepth = SVNWCUtils.relpathDepth(localRelPath);
			} else if (parentNode != null && parentNode.work != null && parentNode.work.reposRelPath != null) {
				workingNode.reposId = upgradeData.repositoryId;
				workingNode.reposRelPath = SVNPathUtil.append(parentNode.work.reposRelPath, SVNFileUtil.getFileName(localRelPath));
				workingNode.revision = parentNode.work.revision;
				workingNode.opDepth = parentNode.work.opDepth;
			} else if (parentNode.belowWork != null && parentNode.belowWork.reposRelPath != null) {
                workingNode.reposId = upgradeData.repositoryId;
                workingNode.reposRelPath = SVNFileUtil.getFilePath(SVNFileUtil.createFilePath(parentNode.belowWork.reposRelPath, SVNFileUtil.getFileName(localRelPath)));
                workingNode.revision = parentNode.belowWork.revision;
                workingNode.opDepth = parentNode.belowWork.opDepth;
			} else {
				SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "No copyfrom URL for ''{0}''", localRelPath);
	            SVNErrorManager.error(err, SVNLogType.WC);
			}
		}
		
		if (entry.getConflictOld() != null) {
			actualNode = new DbActualNode();
			if (parentRelPath != null && entry.getConflictOld() != null)
				actualNode.conflictOld = SVNPathUtil.append(parentRelPath, entry.getConflictOld());
			else
				actualNode.conflictOld = entry.getConflictOld();
			
			if (parentRelPath != null && entry.getConflictNew() != null)
				actualNode.conflictNew = SVNPathUtil.append(parentRelPath, entry.getConflictNew());
			else
				actualNode.conflictNew = entry.getConflictNew();
			
			if (parentRelPath != null && entry.getConflictWorking() != null)
				actualNode.conflictWorking = SVNPathUtil.append(parentRelPath, entry.getConflictWorking());
			else
				actualNode.conflictWorking = entry.getConflictWorking();
		}
		
		if (entry.getPropRejectFile() != null) {
			actualNode = new DbActualNode();
			actualNode.propReject = SVNPathUtil.append(
					entry.isDirectory() ? SVNFileUtil.getFilePath(localRelPath) : parentRelPath, entry.getPropRejectFile());
			
		}
		
		if (entry.getChangelistName() != null) {
			actualNode = new DbActualNode();
			actualNode.changelist = entry.getChangelistName();
		}
		
		Map<String, String> treeConflicts = null;
		/* ### set the text_mod value? */
		if (isCalculateEntryNode && entry.getTreeConflictData() != null) {
			/* Issues #3840/#3916: 1.6 stores multiple tree conflicts on the parent node, 1.7 stores them directly on the conflited nodes.
	         So "((skel1) (skel2))" becomes "(skel1)" and "(skel2)" */
			
			treeConflicts = new HashMap<String,String>();
			Map<File, SVNTreeConflictDescription> tcs = entry.getTreeConflicts();
	        for (Iterator<File> keys = tcs.keySet().iterator(); keys.hasNext();) {
	            File entryPath = keys.next();
	            SVNTreeConflictDescription conflict = (SVNTreeConflictDescription) tcs.get(entryPath);
				assert(conflict.isTreeConflict());
				/* Fix dubious data stored by old clients, local adds don't have a repository URL. */
				if (conflict.getConflictReason() == SVNConflictReason.ADDED) {
					conflict.setSourceLeftVersion(null);
				}
				String key = SVNFileUtil.getFilePath(SVNWCUtils.skipAncestor(rootAbsPath, conflict.getPath()));
				treeConflicts.put(key, SVNTreeConflictUtil.getSingleTreeConflictData(conflict));
            }
		}
		
		if (parentNode != null && parentNode.treeConflicts != null) {
			String treeConflictData = (String)parentNode.treeConflicts.get(SVNFileUtil.getFilePath(localRelPath));
			if (treeConflictData != null) {
				actualNode = new DbActualNode();
				actualNode.treeConflictData = treeConflictData;
				/* Reset hash so that we don't write the row again when writing actual-only nodes */
				parentNode.treeConflicts.remove(SVNFileUtil.getFilePath(localRelPath));
			}
			
			
		}
		
		if (entry.getExternalFilePath() != null) {
			baseNode = new DbNode();
		}
		
		/* Insert the base node. */
		if (baseNode != null) {
			baseNode.wcId = upgradeData.workingCopyId;
			baseNode.localRelPath = SVNFileUtil.getFilePath(localRelPath);
			baseNode.opDepth = 0;
			baseNode.parentRelPath = parentRelPath;
			baseNode.revision = entry.getRevision();
			baseNode.lastModTime = entry.isFile() ? SVNDate.parseDate(entry.getTextTime()) : null;
			baseNode.translatedSize = entry.getWorkingSize();
			if (entry.getDepth() != SVNDepth.EXCLUDE) {
				baseNode.depth = entry.getDepth();
			} else {
				baseNode.presence = SVNWCDbStatus.Excluded;
				baseNode.depth = SVNDepth.INFINITY;
			}
			if (entry.isDeleted()) {
				assert(baseNode.presence == SVNWCDbStatus.NotPresent);
				baseNode.kind = entry.getKind();
			} else if (entry.isAbsent()) {
				assert(baseNode.presence == SVNWCDbStatus.ServerExcluded);
				/* ### should be svn_node_unknown, but let's store what we have. */
				baseNode.kind = entry.getKind();
				/* Store the most likely revision in the node to avoid base nodes without a valid revision. Of course
	             we remember that the data is still incomplete. */
				if (baseNode.revision == ISVNWCDb.INVALID_REVNUM && parentNode != null && parentNode.base != null)
					baseNode.revision = parentNode.base.revision;
			} else {
				baseNode.kind = entry.getKind();
				if (baseNode.presence != SVNWCDbStatus.Excluded) {
					/* All subdirs are initially incomplete, they stop being incomplete when the entries file in the subdir is
	                 upgraded and remain incomplete if that doesn't happen. */
					if (entry.isDirectory() && !"".equals(entry.getName())) {
						baseNode.presence = SVNWCDbStatus.Incomplete;
						/* Store the most likely revision in the node to avoid base nodes without a valid revision. Of course
	                     we remember that the data is still incomplete. */
						if (parentNode != null && parentNode.base != null) {
							baseNode.revision = parentNode.base.revision;
						}
					} else if (entry.isIncomplete()) {
							/* ### nobody should have set the presence.  */
							assert(baseNode.presence == SVNWCDbStatus.Normal);
							baseNode.presence = SVNWCDbStatus.Incomplete;
						}
					}
			}
			
			if (entry.isDirectory()) {
				baseNode.checksum = null;
			} else {
				if (textBaseInfo != null && textBaseInfo.revertBase != null && textBaseInfo.revertBase.sha1Checksum != null) {
					baseNode.checksum = textBaseInfo.revertBase.sha1Checksum;
				} else if (textBaseInfo != null && textBaseInfo.normalBase != null && textBaseInfo.normalBase.sha1Checksum != null) {
					baseNode.checksum = textBaseInfo.normalBase.sha1Checksum;
				} else {
					baseNode.checksum = null;
				}
				 /* The base MD5 checksum is available in the entry, unless there is a copied WORKING node.  
				  * If possible, verify that the entry checksum matches the base file that we found. */
				if (!(workingNode != null && entry.isCopied())) {
					SvnChecksum entryMd5Checksum = new SvnChecksum(Kind.md5, entry.getChecksum());
					SvnChecksum foundMd5Checksum = null;
					if (textBaseInfo != null && textBaseInfo.revertBase != null && textBaseInfo.revertBase.md5Checksum != null) {
						foundMd5Checksum = textBaseInfo.revertBase.md5Checksum;
					} else if (textBaseInfo != null && textBaseInfo.normalBase != null && textBaseInfo.normalBase.md5Checksum != null) {
						foundMd5Checksum = textBaseInfo.normalBase.md5Checksum;
					}
										
					if (entryMd5Checksum.getDigest() != null && foundMd5Checksum != null && !entryMd5Checksum.equals(foundMd5Checksum)) {
						SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CORRUPT, 
							"Bad base MD5 checksum for ''{0}''; expected: ''{1}''; found ''{2}'';",
									SVNFileUtil.createFilePath(rootAbsPath, localRelPath), entryMd5Checksum, foundMd5Checksum);
			           SVNErrorManager.error(err, SVNLogType.WC);
					}  else {
		                  /* ### Not sure what conditions this should cover. */
		                  /* SVN_ERR_ASSERT(entry->deleted || ...); */
		            }
					
				}
			}
			
			if (SvnOldUpgrade.getEntryRepositoryRootURL(thisDir) != null) {
				baseNode.reposId = upgradeData.repositoryId;
				if (SvnOldUpgrade.getEntryURL(entry) != null) {
					String relPath = SVNURLUtil.getRelativeURL(SvnOldUpgrade.getEntryRepositoryRootURL(thisDir), SvnOldUpgrade.getEntryURL(entry), false);
					baseNode.reposRelPath = relPath != null ? relPath : "";
				} else {
					String relPath = SVNURLUtil.getRelativeURL(SvnOldUpgrade.getEntryRepositoryRootURL(thisDir), SvnOldUpgrade.getEntryURL(thisDir), false);
					if (relPath == null) {
						baseNode.reposRelPath = entry.getName();
					} else {
						baseNode.reposRelPath = SVNPathUtil.append(relPath, entry.getName());
					}
				}
			}

		      /* TODO: These values should always be present, if they are missing 
		       * during an upgrade, set a flag, and then ask the user to talk to the server.

		         Note: cmt_rev is the distinguishing value. The others may be 0 or NULL if the corresponding revprop has been deleted.  */
			
			baseNode.changedRev = entry.getCommittedRevision();
			baseNode.changedDate = SVNDate.parseDate(entry.getCommittedDate());
			baseNode.changedAuthor = entry.getAuthor();
			
			if (entry.getExternalFilePath() != null) {
				baseNode.isFileExternal = true;
			}

            if (parentNode != null && isSwitched(parentNode.base, baseNode)) {
                baseNode.inheritedProperties = new SVNProperties();
            }
			
			insertNode(upgradeData.root.getSDb(), baseNode);
			
			/* We have to insert the lock after the base node, because the node
	         must exist to lookup various bits of repos related information for the abs path. */
			
			if (entry.getLockToken() != null && isCreateLocks) {
				SVNWCDbLock lock = new SVNWCDbLock();
				lock.token = entry.getLockToken();
				lock.owner = entry.getLockOwner();
				lock.comment = entry.getLockComment();
				lock.date = SVNDate.parseDate(entry.getLockCreationDate());
				
				db.addLock(tmpEntryAbsPath, lock);
			}
		}
		
		if (belowWorkingNode != null) {
			DbNode work = parentNode.belowWork != null ? parentNode.belowWork : parentNode.work;
			belowWorkingNode.wcId = upgradeData.workingCopyId;
			belowWorkingNode.localRelPath = SVNFileUtil.getFilePath(localRelPath);
			belowWorkingNode.opDepth = work.opDepth;
			belowWorkingNode.parentRelPath = parentRelPath;
			belowWorkingNode.presence = SVNWCDbStatus.Normal;
			belowWorkingNode.kind = entry.getKind();
			belowWorkingNode.reposId = upgradeData.repositoryId;
			
			if (work.reposRelPath != null) {
				belowWorkingNode.reposRelPath = SVNPathUtil.append(work.reposRelPath, entry.getName());
			} else {
				belowWorkingNode.reposRelPath = null;
			}
			belowWorkingNode.revision = parentNode.work.revision;
		    
			/* The revert_base checksum isn't available in the entry structure, so the caller provides it. */

			/* text_base_info is NULL for files scheduled to be added. */
			belowWorkingNode.checksum = null;
			if (textBaseInfo != null) {
				if (entry.isScheduledForDeletion()) {
					belowWorkingNode.checksum = textBaseInfo.normalBase.sha1Checksum;
				} else {
					belowWorkingNode.checksum = textBaseInfo.revertBase.sha1Checksum;
				}
			}
			
			belowWorkingNode.translatedSize = 0;
			belowWorkingNode.changedRev = ISVNWCDb.INVALID_REVNUM;
			belowWorkingNode.changedDate = null;
			belowWorkingNode.changedAuthor = null;
			belowWorkingNode.depth = SVNDepth.INFINITY;
			belowWorkingNode.lastModTime = null;
			belowWorkingNode.properties = null;

            if (workingNode != null
                    && entry.isScheduledForDeletion()
                    && workingNode.reposRelPath != null) {
                belowWorkingNode.reposRelPath = workingNode.reposRelPath;
                belowWorkingNode.reposId = workingNode.reposId;
                belowWorkingNode.revision = workingNode.revision;

                belowWorkingNode.changedRev = entry.getCommittedRevision();
                belowWorkingNode.changedDate = SVNDate.parseDate(entry.getCommittedDate());
                belowWorkingNode.changedAuthor = entry.getAuthor();

                workingNode.reposRelPath = null;
                workingNode.reposId = 0;
                workingNode.revision = SVNRepository.INVALID_REVISION;
            }
			
			insertNode(upgradeData.root.getSDb(), belowWorkingNode);
		}
		
		/* Insert the working node. */
		if (workingNode != null) {
			workingNode.wcId = upgradeData.workingCopyId;
			workingNode.localRelPath = SVNFileUtil.getFilePath(localRelPath);
			workingNode.parentRelPath = parentRelPath;
			workingNode.changedRev = ISVNWCDb.INVALID_REVNUM;
			workingNode.lastModTime = SVNDate.parseDate(entry.getTextTime());
			workingNode.translatedSize = entry.getWorkingSize();
			
			if (entry.getDepth() != SVNDepth.EXCLUDE) {
				workingNode.depth = entry.getDepth();
			} else {
				workingNode.presence = SVNWCDbStatus.Excluded;
				workingNode.depth = SVNDepth.INFINITY;
			}
			
			if (entry.isDirectory()) {
				workingNode.checksum = null;
			} else {
                workingNode.checksum = null;
				/* text_base_info is NULL for files scheduled to be added. */
				if (textBaseInfo != null) {
					workingNode.checksum = textBaseInfo.normalBase.sha1Checksum;
				}
				/* If an MD5 checksum is present in the entry, we can verify that it matches the MD5 of the base file we found earlier. */
				/*#ifdef SVN_DEBUG
				if (entry->checksum && text_base_info)
		          {
		            svn_checksum_t *md5_checksum;
		            SVN_ERR(svn_checksum_parse_hex(&md5_checksum, svn_checksum_md5,
		                                           entry->checksum, result_pool));
		            SVN_ERR_ASSERT(
		              md5_checksum && text_base_info->normal_base.md5_checksum);
		            SVN_ERR_ASSERT(svn_checksum_match(
		              md5_checksum, text_base_info->normal_base.md5_checksum));
		          }
		         #endif*/
			}
			workingNode.kind = entry.getKind();
			if (workingNode.presence != SVNWCDbStatus.Excluded) {
				/* All subdirs start of incomplete, and stop being incomplete when the entries file in the subdir is upgraded. */
				if (entry.isDirectory() && !"".equals(entry.getName())) {
					workingNode.presence = SVNWCDbStatus.Incomplete;
					workingNode.kind = SVNNodeKind.DIR;
				} else if (entry.isScheduledForDeletion()) {
					workingNode.presence = SVNWCDbStatus.BaseDeleted;
					workingNode.kind = entry.getKind();
				} else {
					/* presence == normal  */
					workingNode.kind = entry.getKind();
					if (entry.isIncomplete()) {
						/* We shouldn't be overwriting another status.  */
						assert(workingNode.presence == SVNWCDbStatus.Normal);
						workingNode.presence = SVNWCDbStatus.Incomplete;
					}
				}
			}
			
			 /* These should generally be unset for added and deleted files,
	         and contain whatever information we have for copied files. Let's just store whatever we have.

	         Note: cmt_rev is the distinguishing value. The others may be 0 or NULL if the corresponding revprop has been deleted.  */
			if (workingNode.presence != SVNWCDbStatus.BaseDeleted) {
				workingNode.changedRev = entry.getCommittedRevision();
				workingNode.changedDate =  SVNDate.parseDate(entry.getCommittedDate());
				workingNode.changedAuthor = entry.getAuthor();
			}
			
			if (entry.isScheduledForDeletion() && parentNode != null && parentNode.work != null && parentNode.work.presence == SVNWCDbStatus.BaseDeleted) {
				workingNode.opDepth = parentNode.work.opDepth;
			} else if (!entry.isCopied()) {
				workingNode.opDepth = SVNWCUtils.relpathDepth(localRelPath);
			}
			
			insertNode(upgradeData.root.getSDb(), workingNode);
		}
		
		/* Insert the actual node. */
		if (actualNode != null) {
			actualNode.wcId = upgradeData.workingCopyId;
			actualNode.localRelPath = SVNFileUtil.getFilePath(localRelPath);
			actualNode.parentRelPath = parentRelPath;
			insertActualNode(upgradeData.root.getSDb(), upgradeData.root.getDb(), upgradeData.rootAbsPath, actualNode, targetWorkingCopyFormat);
		}
		
		WriteBaton entryNode = null;
		if (isCalculateEntryNode) {
			entryNode = new WriteBaton();
			entryNode.base = baseNode;
			entryNode.work = workingNode;
			entryNode.belowWork = belowWorkingNode;
			entryNode.treeConflicts = treeConflicts;
		}
		
		if (entry.getExternalFilePath() != null) {
			/* TODO: Maybe add a file external registration inside EXTERNALS here, 
            to allow removing file externals that aren't referenced from svn:externals.
      		The svn:externals values are processed anyway after everything is upgraded */
		}
		return entryNode;
	}

    private static boolean isSwitched(DbNode parent, DbNode child) {
        if (parent != null && child != null) {
            if (parent.reposId != child.reposId) {
                return true;
            }

            if (parent.reposRelPath != null && child.reposRelPath != null) {
                File unswitched = SVNFileUtil.createFilePath(parent.reposRelPath, SVNPathUtil.tail(child.localRelPath));
                if (!SVNFileUtil.getFilePath(unswitched).equals(child.reposRelPath)) {
                    return true;
                }
            }
        }
        return false;
    }
	
	/* No transaction required: called from write_entry which is itself transaction-wrapped. */
	private static void insertNode(SVNSqlJetDb sDb, DbNode node) throws SVNException {
		assert(node.opDepth > 0 || node.reposRelPath != null);
		SVNSqlJetStatement stmt = sDb.getStatement(SVNWCDbStatements.INSERT_NODE);
        try {
            stmt.bindf("isisnnnnsnrisnnni",
                    node.wcId,
                    node.localRelPath == null ? "" : node.localRelPath,
                    node.opDepth,
                    node.parentRelPath,
                    /* Setting depth for files? */
                    SVNDepth.asString(node.depth),
                    node.changedRev,
                    node.changedDate != null ? node.changedDate : null,
                    node.changedAuthor,
                    node.lastModTime
                    );

            if (node.reposRelPath != null) {
                stmt.bindLong(5, node.reposId);
                stmt.bindString(6, node.reposRelPath);
                stmt.bindLong(7, node.revision);
            }

            stmt.bindString(8, SvnWcDbStatementUtil.getPresenceText(node.presence));

            if (node.kind == SVNNodeKind.NONE)
                stmt.bindString(10, "unknown");
            else
                stmt.bindString(10, node.kind.toString());

            if (node.kind == SVNNodeKind.FILE)
                stmt.bindChecksum(14, node.checksum);

            if (node.properties != null)  /* ### Never set, props done later */
                stmt.bindProperties(15, node.properties);

            if (node.translatedSize != ISVNWCDb.INVALID_FILESIZE)
                stmt.bindLong(16, node.translatedSize);

            if (node.isFileExternal)
                stmt.bindLong(20, 1);

            if (node.inheritedProperties != null) {
                stmt.bindProperties(23, node.inheritedProperties);
            }

            stmt.done();
        } finally {
            stmt.reset();
        }
	}
	
	private static void insertActualNode(SVNSqlJetDb sDb, ISVNWCDb db, File wriAbsPath, DbActualNode actualNode, int targetWorkingCopyFormat) throws SVNException {
		SVNSqlJetStatement stmt = sDb.getStatement(targetWorkingCopyFormat <= ISVNWCDb.WC_FORMAT_17 ? SVNWCDbStatements.INSERT_ACTUAL_NODE_17: SVNWCDbStatements.INSERT_ACTUAL_NODE);
        try {
            stmt.bindLong(1, actualNode.wcId);
            stmt.bindString(2, actualNode.localRelPath);
            stmt.bindString(3, actualNode.parentRelPath);
            if (actualNode.properties != null) {
                stmt.bindProperties(4, actualNode.properties);
            }
            if (actualNode.changelist != null) {
                if (targetWorkingCopyFormat <= ISVNWCDb.WC_FORMAT_17) {
                    stmt.bindString(9, actualNode.changelist);
                } else {
                    stmt.bindString(5, actualNode.changelist);
                }
            }

            if (targetWorkingCopyFormat <= ISVNWCDb.WC_FORMAT_17) {
                if (actualNode.conflictOld != null) {
                    stmt.bindString(5, actualNode.conflictOld);
                    stmt.bindString(6, actualNode.conflictNew);
                    stmt.bindString(7, actualNode.conflictWorking);
                }
                if (actualNode.propReject != null) {
                    stmt.bindString(8, actualNode.propReject);
                }
                if (actualNode.treeConflictData != null) {
                    stmt.bindString(10, actualNode.treeConflictData);
                }
            } else {
                byte[] treeConflictDataBytes = null;
                if (actualNode.treeConflictData != null) {
                    try {
                        treeConflictDataBytes = actualNode.treeConflictData.getBytes("UTF-8");
                    } catch (UnsupportedEncodingException e) {
                        treeConflictDataBytes = actualNode.treeConflictData.getBytes();
                    }
                }


                SVNSkel conflictData = SvnWcDbConflicts.upgradeConflictSkelFromRaw(db, wriAbsPath,
                        SVNFileUtil.createFilePath(actualNode.localRelPath),
                        actualNode.conflictOld,
                        actualNode.conflictWorking,
                        actualNode.conflictNew,
                        SVNFileUtil.createFilePath(actualNode.propReject),
                        SVNSkel.parse(treeConflictDataBytes));

                if (conflictData != null) {
                    byte[] conflictDataBytes = conflictData.unparse();
                    stmt.bindBlob(6, conflictDataBytes);
                }
            }
            stmt.done();
        } finally {
            stmt.reset();
        }
	}
	
	private static void writeActualOnlyEntries(Map<String, String> treeConflicts, SVNSqlJetDb sDb, ISVNWCDb db, File wriAbsPath, long wcId, String dirRelPath, int targetWorkingCopyFormat) throws SVNException {
		for (Iterator<String> items = treeConflicts.keySet().iterator(); items.hasNext();) {
			String path = items.next();
			DbActualNode actualNode = new DbActualNode();
			actualNode.wcId = wcId;
			actualNode.localRelPath = path;
			actualNode.parentRelPath = dirRelPath;
			actualNode.treeConflictData = treeConflicts.get(path);
			insertActualNode(sDb, db, wriAbsPath, actualNode, targetWorkingCopyFormat);
		}
	}
	
 }
