'use strict';
 
 var _ = require('libs/underscore'),
+    smartxmlTransformations = require('smartxml/transformations'),
     metadataKey = 'wlxml.metadata';
 
-var Row = function(key, value) {
+
+var Row = function(key, value, metadata) {
     this.key = key;
     this.value  = value;
+    this.metadata = metadata;
 };
+
 _.extend(Row.prototype, {
+    ChangeProperty: smartxmlTransformations.createContextTransformation({
+        impl: function(t, rowIndex, propName, value) {
+            var row = this.getMetadata().at(rowIndex);
+            t.rowIndex = rowIndex;
+            t.propName = propName;
+            t.oldValue = row[propName];
+            row[propName] = value;
+            this.triggerChangeEvent('metadataChanged', {row:row});
+        },
+        undo: function(t) {
+            var row = this.getMetadata().at(t.rowIndex);
+            row[t.propName] = t.oldValue;
+            this.triggerChangeEvent('metadataChanged', {row:row});
+        }
+    }),
+
     setKey: function(key) {
-        this.key = key;
+        return this.metadata.node.transform(this.ChangeProperty, [this.getIndex(), 'key', key]);
     },
     getKey: function() {
         return this.key;
     },
     setValue: function(value) {
-        this.value = value;
+        return this.metadata.node.transform(this.ChangeProperty, [this.getIndex(), 'value', value]);
     },
     getValue: function() {
         return this.value;
+    },
+    remove: function() {
+        this.metadata.remove(this);
+    },
+    getIndex: function() {
+        return this.metadata.indexOf(this);
     }
 });
 
-// var Metadata = function(node) {
-//     this._rows = [];
-// }
 
-// _.extend(Metadata.prototype, {
-//     forEach: function(callback) {
-//         this.
-//     }
-// })
-
-var methods = {
-    getMetadata: function() {
-        return this.getData(metadataKey) || [];
-    }
+var Metadata = function(node) {
+    this._rows = [];
+    Object.defineProperty(this, 'length', {
+        get: function() {
+            return this._rows.length;
+        }
+    });
+    this.node = node;
 };
 
-var transformations = {
-    addMetadata: function(desc) {
-        var metadata = this.getData(metadataKey) || [],
-            row = new Row(desc.key, desc.value);
-        metadata.push(row);
-        this.setData(metadataKey, metadata);
-        return row;
+_.extend(Metadata.prototype, {
+    Add: smartxmlTransformations.createContextTransformation({
+        impl: function(t, rowDesc) {
+            var metadata = this.getMetadata(),
+                row = new Row(rowDesc.key, rowDesc.value, metadata);
+            metadata._rows.push(row);
+            t.rowIdx = row.getIndex();
+            this.triggerChangeEvent('metadataAdded', {row: row});
+            return row;
+        },
+        undo: function(t) {
+            this.getMetadata().at(t.rowIdx).remove();
+        }
+    }),
+
+    Remove: smartxmlTransformations.createContextTransformation({
+        impl: function(t, rowIdx) {
+            var metadata = this.getMetadata();
+            t.rowIdx = rowIdx;
+            t.row = metadata.at(rowIdx);
+            metadata._rows.splice(rowIdx, 1);
+            this.triggerChangeEvent('metadataRemoved', {row: t.row});
+        },
+        undo: function(t) {
+            var metadata = this.getMetadata();
+            metadata._rows.splice(t.rowIdx, 0, new Row(t.row.getKey(), t.row.getValue(), metadata));
+        }
+    }),
+
+    forEach: function(callback) {
+        return this._rows.forEach(callback);
+    },
+    add: function(rowDesc, options) {
+        var row;
+        options = _.extend({undoable: true}, options);
+        if(options.undoable) {
+            return this.node.transform(this.Add, [rowDesc]);
+        } else {
+            row = new Row(rowDesc.key, rowDesc.value, this);
+            this._rows.push(row);
+            return row;
+        }
+    },
+    at: function(idx) {
+        return this._rows[idx];
+    },
+    indexOf: function(row) {
+        var idx = this._rows.indexOf(row);
+        if(idx !== -1) {
+            return idx;
+        }
+        return undefined;
+    },
+    remove: function(row) {
+        var idx = this.indexOf(row);
+        if(typeof idx !== 'undefined') {
+            this.node.transform(this.Remove, [idx]);
+        }
+    },
+    clone: function(node) {
+        var clone = new Metadata(node);
+        this._rows.forEach(function(row) {
+            clone._rows.push(new Row(row.getKey(), row.getValue(), clone));
+        });
+        return clone;
     }
-};
+});
+
 
 return {
     elementNode: {
-        methods: methods,
-        transformations: transformations,
+        methods: {
+            getMetadata: function() {
+                if(!this.getData(metadataKey)) {
+                    this.setData(metadataKey, new Metadata(this));
+                }
+                return this.getData(metadataKey);
+            }
+        }
     }
 };
 
 
     expect = chai.expect,
     $ = require('libs/jquery');
 
+
 var getDocumentFromXML = function(xml, options) {
     return wlxml.WLXMLDocumentFromXML(xml, options || {});
 };
 
 
-describe.only('Metadata API', function() {
+describe('Metadata API', function() {
     it('returns empty metadata for node without metadata', function() {
         var doc = getDocumentFromXML('<section></section>');
         expect(doc.root.getMetadata().length).to.equal(0);
     });
     it('allows to set metadata on an element node', function() {
-        var doc = getDocumentFromXML('<section></section>');
-
-        var row = doc.root.addMetadata({key: 'key', value: 'value'}),
+        var doc = getDocumentFromXML('<section></section>'),
             metadata = doc.root.getMetadata();
-
+        
+        var row = metadata.add({key: 'key', value: 'value'});
         expect(metadata.length).to.equal(1);
-        expect(metadata[0]).to.equal(row, 'aaa');
-
+        expect(metadata.at(0)).to.equal(row);
         expect(row.getKey()).to.equal('key');
         expect(row.getValue()).to.equal('value');
     });
-    // it('allows to remove specific metadata row', function() {
-    //     var doc = getDocumentFromXML('<section><metadata><dc:key>value</dc:key><dc:key>value</dc:key></metadata></section>'),
-    //         metadata = doc.root.getMetadata();
-    //     expect(metadata.length).to.equal(2);
-    //     row.remove();
-    //     expect(metadata.length)
-    //     expect(metadata[0].getValue()).to.equal('value');
-    // });
+    it('allows to remove specific metadata row', function() {
+        var doc = getDocumentFromXML('<section><metadata><dc:key>value</dc:key><dc:key>value</dc:key></metadata></section>'),
+            metadata = doc.root.getMetadata();
+        
+        expect(metadata.length).to.equal(2);
+        metadata.at(0).remove();
+        expect(metadata.length).to.equal(1);
+        expect(metadata.at(0).getValue()).to.equal('value');
+    });
     it('reads node\'s metadata from source of its metadata child node', function() {
         var doc = getDocumentFromXML('<section><metadata><dc:key>value</dc:key></metadata></section>'),
             metadata = doc.root.getMetadata();
         expect(metadata.length).to.equal(1);
-        expect(metadata[0].getKey()).to.equal('key');
-        expect(metadata[0].getValue()).to.equal('value');
+        expect(metadata.at(0).getKey()).to.equal('key');
+        expect(metadata.at(0).getValue()).to.equal('value');
     });
 
     it('serializes node\'s metadata to its metadata child node', function() {
         var doc = getDocumentFromXML('<section></section>');
 
-        doc.root.addMetadata({key: 'key', value: 'value'});
+        doc.root.getMetadata().add({key: 'key', value: 'value'});
 
         var metadataNodes = $(doc.toXML()).children('metadata'),
             keyNodes = metadataNodes.children();
         expect(keyNodes[0].tagName.toLowerCase()).to.equal('dc:key');
         expect($(keyNodes[0]).text()).to.equal('value');
     });
-    it('doesn\'t show metadata node on nodes contents', function() {
-        var doc = getDocumentFromXML('<section><metadata><dc:key>value</dc:key></metadata></section>');
-        expect(doc.root.contents()).to.have.length(0);
+
+    describe('Hiding metadata nodes from document api', function() {
+        it('hides metadata nodes from document api', function() {
+            var doc = getDocumentFromXML('<section><div></div><metadata><dc:key>value</dc:key></metadata><div></div></section>'),
+                rootContents = doc.root.contents();
+            expect(rootContents).to.have.length(2);
+            expect(rootContents[0].getTagName()).to.equal('div');
+            expect(rootContents[1].getTagName()).to.equal('div');
+            expect(rootContents[0].next().sameNode(rootContents[1])).to.equal(true);
+            expect(rootContents[1].prev().sameNode(rootContents[0])).to.equal(true);
+        });
+
+        it('merges adjacent text nodes', function() {
+            var doc = getDocumentFromXML('<section>Alice<metadata></metadata> has a cat</section>'),
+                contents = doc.root.contents();
+            expect(contents.length).to.equal(1);
+            expect(contents[0].getText()).to.equal('Alice has a cat');
+        });
     });
 
+    describe('undo', function() {
+        it('undoes adding metadata', function() {
+            var doc = getDocumentFromXML('<section></section>'),
+                metadata = doc.root.getMetadata();
+            metadata.add({key: 'k', value: 'v'});
+            doc.undo();
+            expect(metadata.length).to.equal(0);
+            doc.redo();
+            expect(metadata.length).to.equal(1);
+            expect(metadata.at(0).getValue()).to.equal('v');
+        });
+        it('undoes changing metadata key', function() {
+            var doc = getDocumentFromXML('<section><metadata><dc:key>value</dc:key></metadata></section>'),
+                metadata = doc.root.getMetadata(),
+                row = metadata.at(0);
+
+            row.setKey('key2');
+            doc.undo();
+            expect(row.getKey()).to.equal('key');
+            doc.redo();
+            expect(row.getKey()).to.equal('key2');
+        });
+        it('undoes changing metadata value', function() {
+            var doc = getDocumentFromXML('<section><metadata><dc:key>value</dc:key></metadata></section>'),
+                metadata = doc.root.getMetadata(),
+                row = metadata.at(0);
+
+            row.setValue('value2');
+            doc.undo();
+            expect(row.getValue()).to.equal('value');
+            doc.redo();
+            expect(row.getValue()).to.equal('value2');
+        });
+        it('undoes removing metadata', function() {
+            var doc = getDocumentFromXML('<section><metadata><dc:key>value</dc:key></metadata></section>'),
+                metadata = doc.root.getMetadata(),
+                row = metadata.at(0);
+            
+            row.remove();
+            doc.undo();
+            expect(metadata.length).to.equal(1, 'undo brought back metadata');
+            doc.redo();
+            expect(metadata.length).to.equal(0, 'redo removed metadata');
+        });
+    });
+
+
+
 });