 
 fetchBinaries = (devicename, options) ->
     remoteConfig = config.config.remotes[devicename]
-    filepath = options.filepath
+    filePath = options.filePath
 
     # Create files and directories in the FS
-    buildFSTree devicename, { filepath: filepath }
+    buildFsTree devicename, { filePath: filePath }
 
     # Fetch only files
-    map = (doc) ->
+    db.db.query { map: (doc) ->
+
         if doc.docType is 'File'
-            emit(doc._id, doc)
-
-    db.db.query { map: map }, (err, res) ->
+            emit doc._id, doc
+
+    }, (err, res) ->
         # We need to authenticate as a device
         client = request.newClient remoteConfig.url
         client.setBasicAuth devicename, remoteConfig.devicePassword
 
         for doc in res.rows
-            if not filepath? or filepath is path.join doc.value.path, doc.value.name
-                filePath = path.join remoteConfig.path, doc.value.path, doc.value.name
-                if doc.value.binary?
-                    binaryUri = 'cozy/' + doc.value.binary.file.id + '/file'
+            doc = doc.value
+            if not filePath? or filePath is path.join doc.path, doc.name
+                filePath = path.join remoteConfig.path, doc.path, doc.name
+                if doc.binary?
+                    binaryUri = "cozy/#{doc.binary.file.id}/file"
                     # Fetch binary via CouchDB API
                     client.saveFile binaryUri, filePath, (err, res, body) ->
                         if err
                             console.log err
 
                         # Rebuild FS Tree to correct utime
-                        buildFSTree devicename, { filepath: path.join doc.value.path, doc.value.name }
-
-
-addFile = (devicename, filePath) ->
-    remoteConfig = config.config.remotes[devicename]
+                        buildFsTree devicename, { filePath: path.join doc.path, doc.name }
+
+
+putDirectory = (devicename, directoryPath, recursive, callback) ->
+    # Fix callback
+    if not callback?
+        callback = () ->
+
+    # Get config
+    remoteConfig = config.config.remotes[devicename]
+
+    # Get dir name
+    dirName = path.basename directoryPath
+
+    # Find directory's parent directory
+    absolutePath = path.resolve directoryPath
+    relativePath = absolutePath.replace remoteConfig.path, ''
+    if relativePath is absolutePath
+        log.error "Directory is not located on the synchronized directory: #{dirPath}"
+        callback
+    if relativePath.split('/').length > 2
+        dirPath = relativePath.replace "/#{dirName}", ''
+    else
+        dirPath = ''
+
+    # Get size and modification time
+    stats = fs.statSync(absolutePath)
+    dirLastModification = stats.mtime
+
+    # Lookup for existing directory
+    db.db.query { map: (doc) ->
+
+        if doc.docType is 'Folder'
+            emit doc._id, doc
+
+    }, (err, res) ->
+        for doc in res.rows
+            doc = doc.value
+            if doc.path is dirPath and doc.name is dirName
+                log.info "Directory already exists: #{doc.path}/#{doc.name}"
+                if recursive
+                    return putSubFiles callback
+                else
+                    return callback err, res
+
+        log.info "Creating directory doc: #{dirPath}/#{dirName}"
+        newId = uuid.v4().split('-').join('')
+        document =
+            creationDate: dirLastModification
+            docType: 'Folder'
+            lastModification: dirLastModification
+            name: dirName
+            path: dirPath
+            tags: []
+
+        db.db.put document, newId, (err, res) ->
+            if recursive
+                return putSubFiles callback
+            else
+                return callback err, res
+
+    putSubFiles = (callback) ->
+        # List files in directory
+        fs.readdir absolutePath, (err, res) ->
+            for file in res
+                fileName = "#{absolutePath}/#{file}"
+                # Upload file if it is a file
+                if fs.lstatSync(fileName).isFile()
+                    putFile devicename, fileName, callback(err, res)
+                # Upload directory recursively if it is a directory
+                else if fs.lstatSync(fileName).isDirectory()
+                    putDirectory devicename, fileName, recursive, callback(err, res)
+            if res.length is 0
+                log.info "No file to upload in: #{relativePath}"
+                callback err, res
+
+
+putFile = (devicename, filePath, callback) ->
+    # Fix callback
+    if not callback?
+        callback = () ->
+
+    # Get config
+    remoteConfig = config.config.remotes[devicename]
+
+    # Initialize remote HTTP client
+    client = request.newClient remoteConfig.url
+    client.setBasicAuth devicename, remoteConfig.devicePassword
+
+    # Get file name
+    fileName = path.basename filePath
+
+    # Find file's parent directory
     absolutePath = path.resolve filePath
     relativePath = absolutePath.replace remoteConfig.path, ''
-
     if relativePath is absolutePath
-        console.log 'file is not in the sync dir'
-        process.exit 1
-
-    fileName = path.basename filePath
+        log.error "File is not located on the synchronized directory: #{filePath}"
+        return callback
     if relativePath.split('/').length > 2
-        parentPath = relativePath.replace '/' + fileName, ''
+        filePath = relativePath.replace "/#{fileName}", ''
     else
-        parentPath = ''
-
-    fileStats = fs.statSync(filePath)
-    lastModification = fileStats.mtime
-    fileSize = fileStats.size
-
-    mimeType = mime.lookup absolutePath
-    fileId = uuid.v4().split('-').join('')
-    binaryId = uuid.v4().split('-').join('')
-
-    client = request.newClient remoteConfig.url
-    client.setBasicAuth devicename, remoteConfig.devicePassword
-
-    # Create binary document
-    client.put 'cozy/'+ binaryId, { docType: 'Binary' }, (err, res, body) ->
-        if res.statusCode isnt 201
-            console.log err
-        else
-            # Upload binary
-            client.putFile 'cozy/' + binaryId + '/file?rev='+ body.rev, absolutePath, {}, (err, res, body) ->
-                if res.statusCode isnt 201
-                    console.log err
+        filePath = ''
+
+    # Lookup MIME type
+    fileMimeType = mime.lookup absolutePath
+
+    # Get size and modification time
+    stats = fs.statSync absolutePath
+    fileLastModification = stats.mtime
+    fileSize = stats.size
+
+    # Ensure that directory exists
+    putDirectory devicename, ".#{filePath}", false, (err, res) ->
+
+        # Fetch only files with the same path/filename
+        db.db.query { map: (doc) ->
+
+            if doc.docType is 'File'
+                emit doc._id, doc
+
+        }, (err, res) ->
+            for doc in res.rows
+                doc = doc.value
+                if doc.name is fileName and doc.path is filePath
+                    existingFileId    = doc._id
+                    existingFileRev   = doc._rev
+                    existingBinaryId  = doc.binary.file.id
+                    if new Date(doc.lastModification) >= new Date(fileLastModification)
+                        log.info "Unchanged file: #{doc.path}/#{doc.name}"
+                        return callback err, res
+
+            if existingBinaryId?
+                # Fetch last revision from remote
+                client.get "cozy/#{existingBinaryId}", (err, res, body) ->
+                    if res.statusCode isnt 200
+                        log.error "#{body.error}: #{body.reason}"
+                    else
+                        return uploadBinary body.id, body.rev, absolutePath, putFileDoc
+            else
+                # Create the doc and get revision
+                newBinaryId = uuid.v4().split('-').join('')
+                client.put "cozy/#{newBinaryId}", { docType: 'Binary' }, (err, res, body) ->
+                    if res.statusCode isnt 201
+                        log.error "#{body.error}: #{body.reason}"
+                    else
+                        return uploadBinary body.id, body.rev, absolutePath, putFileDoc
+
+            uploadBinary = (id, rev, absolutePath, callback) ->
+                log.info "Uploading binary: #{absolutePath}"
+                client.putFile "cozy/#{id}/file?rev=#{rev}", absolutePath, {}, (err, res, body) ->
+                    if res.statusCode isnt 201
+                        log.error "#{body.error}: #{body.reason}"
+                    else
+                        body = JSON.parse body
+                        return callback existingFileId, existingFileRev, body.id, body.rev
+
+            putFileDoc = (id, rev, binaryId, binaryRev, callback) ->
+                doc =
+                    binary:
+                        file:
+                            id: binaryId
+                            rev: binaryRev
+                    class: 'document'
+                    creationDate: fileLastModification
+                    docType: 'File'
+                    lastModification: fileLastModification
+                    mime: fileMimeType
+                    name: fileName
+                    path: filePath
+                    size: fileSize
+                    tags: []
+
+                if id?
+                    log.info "Updating file doc: #{absolutePath}"
+                    db.db.put doc, id, rev, (err, res) ->
+                        if err
+                            console.log err
+                        return
+
                 else
-                    document =
-                        binary:
-                            file:
-                                id: body.id
-                                rev: body.rev
-                        class: 'document'
-                        creationDate: lastModification
-                        docType: 'File'
-                        lastModification: lastModification
-                        mime: mimeType
-                        name: fileName
-                        path: parentPath
-                        size: fileSize
-
-                    db.db.put document, fileId, (err, res) ->
-                        console.log err
-                        console.log res
+                    newId = uuid.v4().split('-').join('')
+                    log.info "Creating file doc: #{absolutePath}"
+                    db.db.put doc, newId, (err, res) ->
+                        if err
+                            console.log err
+                        return
+
 
 watchChanges = (devicename) ->
     remoteConfig = config.config.remotes[devicename]
 
 
 runSync = (devicename) ->
+    remoteConfig = config.config.remotes[devicename]
+
+    options =
+        filter: (doc) ->
+            doc.docType is 'Folder' or doc.docType is 'File'
+
+    urlParser = require 'url'
+    url = urlParser.parse remoteConfig.url
+    url.auth = "#{devicename}:#{remoteConfig.devicePassword}"
+    replication = db.db.replicate.to(urlParser.format(url) + 'cozy', options)
+      .on 'change', (info) ->
+          console.log info
+      .on 'complete', (info) ->
+          log.info 'Replication is complete'
+      .on 'error', (err) ->
+          log.error err
 
 
 displayConfig = ->



program
    .command('add-remote-cozy <url> <devicename> <syncpath>')
    .description('Configure current device to sync with given cozy')
    .action addRemote


program
    .command('build-tree <devicename>')
    .description('Create empty files and directories in the filesystem')
    .option('-f, --filePath [filePath]', 'specify file to build FS tree')
    .action buildFsTree

program
    .command('fetch-binaries <devicename> ')
    .description('Replicate DB binaries')
    .option('-f, --filePath [filePath]', 'specify file to fetch associated binary')
    .action fetchBinaries

program
    .command('put-file <devicename> <filePath>')
    .description('Add file descriptor to PouchDB')
    .action putFile

program
    .command('put-dir <devicename> <dirPath>')
    .description('Add folder descriptor to PouchDB')
    .option('-r, --recursive', 'add every file/folder inside')
    .action putDirectory

program
    .command('sync <devicename>')
