 age_h = (time) -> (new Date() - time)/(3600*1000)
 age_s = (time) -> (new Date() - time)/1000
 
-json_output_cb = (cb) ->
-    (err, output) ->
-        if not err
-            cb(undefined, misc.from_json(output.stdout))
-        else
-            cb(err)
-
-class Instance
+onCompleteOpts =
+    maxAttempts : 30
+
+handle_operation = (err, operation, done, cb) ->
+    if err
+        done()
+        cb?(err)
+    else
+        operation.onComplete onCompleteOpts, (err, metadata) ->
+            done()
+            if err
+                cb?(err)
+            else if metadata.error
+                cb?(metadata.error)
+            else
+                cb?()
+
+class VM
     constructor: (@gcloud, @name, @zone=DEFAULT_ZONE) ->
-
-    dbg: (f) -> @gcloud("instance.#{f}")
-
-    _command : (opts) =>
-        opts = defaults opts,
-            category : 'instances'
-            action   : required   # 'start', 'stop', 'reset', etc.
-            args     : []
-            cb       : undefined
-        @gcloud._command(misc.merge(opts, {name:@name, zone:@zone}))
+        @_vm = @gcloud._gce.zone(@zone).vm(@name)
+
+    dbg: (f) -> @gcloud.dbg("vm.#{f}")
+
+    _action: (cmd, cb) =>
+        dbg = @dbg(cmd)
+        dbg('calling api...')
+        start = misc.walltime()
+        @_vm[cmd] (err, operation, apiResponse) ->
+            handle_operation(err, operation, (->dbg("done -- took #{misc.walltime(start)}s")), cb)
 
     stop : (opts={}) =>
-        @_command(misc.merge(opts, {action:'stop'}))
+        @_action('stop', opts.cb)
 
     start: (opts={}) =>
-        @_command(misc.merge(opts, {action:'start'}))
+        @_action('start', opts.cb)
 
     reset: (opts={}) =>
-        @_command(misc.merge(opts, {action:'reset'}))
-
-    describe: (opts) =>
-        opts.cb = json_output_cb(opts.cb)
-        @_command(misc.merge(opts, {action:'describe', args:['--format', 'json']}))
+        @_action('reset', opts.cb)
+
+    get_metadata: (opts) =>
+        opts = defaults opts,
+            cb : required
+        dbg = @dbg("metadata")
+        dbg("starting")
+        @_vm.getMetadata (err, metadata, apiResponse) =>
+            dbg("done")
+            opts.cb(err, metadata)
+
+    status : (opts) =>
+        opts = defaults opts,
+            cb : required
+        @describe
+            cb : (err, x) =>
+                opts.cb(err, x?.status)
 
 class Disk
     constructor: (@gcloud, @name, @zone=DEFAULT_ZONE) ->
-
-    dbg: (f) -> @gcloud("disk.#{f}")
-
-    _command : (opts) =>
-        opts = defaults opts,
-            category : 'disks'
-            action   : required
-            args     : []
-            cb       : undefined
-        @gcloud._command(misc.merge(opts, {name:@name, zone:@zone}))
-
-    snapshot : (opts) =>
+        @_disk = @gcloud._gce.zone(@zone).disk(@name)
+
+    dbg: (f) -> @gcloud.dbg("disk.#{f}")
+
+    #createSnapshot, delete, getMetadata
+
+    snapshot: (opts) =>
         opts = defaults opts,
             name : required
             cb   : undefined
-        @_command
-            action : 'snapshot'
-            args   : ['--snapshot-names', opts.name]
+        dbg = @dbg('snapshot')
+        dbg('calling api')
+        start = misc.walltime()
+        done = -> dbg("done  -- took #{misc.walltime(start)}s")
+        @_disk.createSnapshot opts.name, (err, snapshot, operation, apiResponse) =>
+            handle_operation(err, operation, done, opts.cb)
+
+    get_metadata: (opts) =>
+        opts = defaults opts,
+            cb : required
+        dbg = @dbg("metadata")
+        dbg("starting")
+        @_disk.getMetadata (err, metadata, apiResponse) =>
+            dbg("done")
+            opts.cb(err, metadata)
+
+    # return the snapshots of this disk
+    get_snapshots: (opts) =>
+        opts = defaults opts,
+            cb : required
+        dbg = @dbg("get_snapshots")
+        id = undefined
+        s = undefined
+        async.series([
+            (cb) =>
+                dbg("determining id of disk")
+                @get_metadata
+                    cb : (err, data) =>
+                        id = data?.id; cb(err)
+            (cb) =>
+                dbg("get all snapshots with given id as source")
+                @gcloud._gce.getSnapshots {filter:"sourceDiskId eq #{id}", maxResults:500}, (err, snapshots) =>
+                    if err
+                        cb(err)
+                    else
+                        s = (@gcloud.snapshot(name:x.name) for x in snapshots)
+                        cb()
+        ], (err) =>
+            opts.cb(err, s)
+        )
+
+    delete: (opts) =>
+        opts = defaults opts,
+            cb : undefined
+        dbg = @dbg("delete")
+        dbg("starting")
+        @_disk.delete (err, operation, apiResponse) =>
+            handle_operation(err, operation, (->dbg('done')), opts.cb)
 
 class Snapshot
     constructor: (@gcloud, @name) ->
-
-    dbg: (f) -> @gcloud("snapshot.#{f}")
-
-    describe: (opts) =>
-        opts = defaults opts,
-            cb : required
-        @gcloud._gcloud
-            args : ['snapshots', 'describe', @name, '--format', json]
-            cb   : json_output_cb(opts.cb)
-
+        @_snapshot = @gcloud._gce.snapshot(@name)
+
+    dbg: (f) -> @gcloud.dbg("snapshot.#{f}")
+
+    delete: (opts) =>
+        opts = defaults opts,
+            cb : undefined
+        dbg = @dbg("delete")
+        dbg("starting")
+        @_snapshot.delete (err, operation, apiResponse) =>
+            handle_operation(err, operation, (->dbg('done')), opts.cb)
+
+    get_metadata: (opts) =>
+        opts = defaults opts,
+            cb : required
+        dbg = @dbg("metadata")
+        dbg("starting")
+        @_snapshot.getMetadata (err, metadata, apiResponse) =>
+            dbg("done")
+            opts.cb(err, metadata)
+
+    get_size_GB: (opts) =>
+        opts = defaults opts,
+            cb : required
+        if @_snapshot.metadata.storageBytes
+            opts.cb(undefined, @_snapshot.metadata.storageBytes / 1000 / 1000 / 1000)
+        else
+            @get_metadata
+                cb : (err, data) =>
+                    if err
+                        opts.cb(err)
+                    else
+                        opts.cb(undefined, data.storageBytes / 1000 / 1000 / 1000)
 
 class GoogleCloud
     constructor: (opts={}) ->

        @_disk_cache ?= {}
        return (@_disk_cache[key] ?= new Disk(@, opts.name, opts.zone))


    snapshot: (opts) =>
        opts = defaults opts,
            name : required

###

g = require('./smc_gcloud.coffee').gcloud(db:require('rethink').rethinkdb(hosts:'db0', pool:1))

Rewrite this to use the official node.js driver, which is pretty good now, and seems an order
of magnitude faster than using the gcloud command line!

https://googlecloudplatform.github.io/gcloud-node/#/docs/v0.23.0/compute/
https://github.com/GoogleCloudPlatform/gcloud-node

npm install --save gcloud

TODO:

- [ ] increase the boot disk size of a vm
- [ ] change the machine type of a vm
- [ ] switch a vm between being pre-empt or not
- [ ] increase the size of a disk image that is attached to a VM

Rules we care about are:

1. If a VM is TERMINATED, but the desired state is RUNNING and preempt is true, then:
     if it was TERMINATED within 5 minutes create it as non-preempt and start it
     if it was TERMINATED > 5 minutes ago create it as preempt and start it.

2. If a VM has been RUNNING for 12 hours and is not preempt, but the desired state
   is RUNNING and preempt is true, then:
     stop the VM and recreate and start it as prempt.

###
winston     = require('winston')
