internal NotificationEventArgs(DataEntry entry, string userKey, NotificationReason reason, object oldValue)
 {
     this._Entry = entry;
     this._reason = reason;
     this._userKey = userKey;
     this._owner = entry.lastOwnerID;
     this._value = entry.value;
     this._oldValue = oldValue;
 }
        ///// <summary>
        ///// respond only by header
        ///// </summary>
        ///// <param name="verb">GET or HEAD</param>
        ///// <param name="resource">Same as key or contentLocation</param>
        ///// <param name="senderList"></param>
        ///// <param name="contentLength"></param>
        ///// <param name="mimeType"></param>
        ///// <param name="lastVer"></param>
        ///// <param name="isDeleted">Indicates that the current entry is deleted</param>
        ///// <param name="willClose">Writes a close message in the header</param>
        ///// <param name="bufferedOutput">A memory buffer that will be filled with contents produced
        ///// from this method</param>
        //private void ResponseHeadStub(string verb, string resource, List<int> senderList, List<int> proxyResponsePath, int contentLength,
        //    string mimeType, ETag lastVer, bool isDeleted, bool willClose, MemoryStream bufferedOutput)
        //{
        //    StreamWriter writer = new StreamWriter(bufferedOutput, Encoding.ASCII);
        //        writer.NewLine = NEWLINE;
        //        if (isDeleted)
        //        {
        //            WriteResponseDeleted(bufferedOutput, resource, senderList, proxyResponsePath, lastVer.UID, lastVer.Revision);
        //        }
        //        else
        //        {
        //            WriteResponseHeader(writer, resource, mimeType, contentLength, lastVer.UID, lastVer.Revision, senderList, proxyResponsePath, verb, willClose);
        //        }
        //}
        // respond to a GET request, HEAD request, and push data on wire
        private void Response(string verb, string resource, List<int> senderList,List<int> proxyResponsePath,  DataEntry entry, MemoryStream bufferedOutput, bool willClose)
        {
            lock (entry)
                {
                    if (entry.IsEmpty)
                    {
                        WriteResponseDeleted(bufferedOutput, resource, senderList, proxyResponsePath, entry.lastOwnerID, entry.lastOwnerRevision);
                    }

                    else if (entry.IsSimpleValue || entry.type == DataEntry.ValueType.String)
                    {
                        StreamWriter writer = new StreamWriter(bufferedOutput, Encoding.ASCII);
                        writer.NewLine = NEWLINE;

                        string translation ="";
                        if (entry.value != null)
                            translation = entry.value.ToString();

                        byte[] bytesToWrite = System.Text.Encoding.UTF8.GetBytes(translation);

                        WriteResponseHeader(writer, resource, entry.GetMime(), bytesToWrite.Length, entry.lastOwnerID, entry.lastOwnerRevision, senderList, proxyResponsePath, verb, willClose);
                        writer.Flush();
                        if (verb == GET)
                        {
                            bufferedOutput.Write(bytesToWrite, 0, bytesToWrite.Length);
                            //StreamWriter tw = new StreamWriter(bufferedOutput, Encoding.UTF8);
                            //    tw.NewLine = NEWLINE;

                            //    tw.BaseStream.Seek(0, SeekOrigin.End);
                            //    tw.Write(translation);
                            //    tw.Flush();

                        }
                        writer.Flush();
                    }
                    else if (entry.IsComplexValue)
                    {
                        if (entry.type == DataEntry.ValueType.Binary)
                        {
                            StreamWriter writer = new StreamWriter(bufferedOutput, Encoding.ASCII);
                            writer.NewLine = NEWLINE;

                            byte[] bentry = (byte[])entry.value;
                            WriteResponseHeader(writer, resource, entry.GetMime(), bentry.Length, entry.lastOwnerID, entry.lastOwnerRevision, senderList, proxyResponsePath, verb, willClose);
                            writer.Flush();

                            if (verb == GET)
                            {
                                bufferedOutput.Write(bentry, 0, bentry.Length);
                            }

                        }
                        else if (entry.type == DataEntry.ValueType.Object)
                        {
                            StreamWriter writer = new StreamWriter(bufferedOutput, Encoding.ASCII);
                            writer.NewLine = NEWLINE;

                            MemoryStream bentry;
                            try
                            {
                                bentry = new MemoryStream();
                                BinaryFormatter formatter = new BinaryFormatter();
                                formatter.Serialize(bentry, entry.value);
                            }
                            catch
                            {
                                throw;
                            }

                            System.Diagnostics.Debug.Assert(bentry.Length <= int.MaxValue);

                            WriteResponseHeader(writer, resource, entry.GetMime(), (int)bentry.Length, entry.lastOwnerID, entry.lastOwnerRevision, senderList, proxyResponsePath, verb, willClose);
                            writer.Flush();

                            if (verb == GET)
                            {

                                bentry.WriteTo(bufferedOutput);
                            }
                        }
                        else if (entry.type == Data.ValueType.Unknown)
                        {
                            StreamWriter writer = new StreamWriter(bufferedOutput, Encoding.ASCII);
                            writer.NewLine = NEWLINE;

                            byte[] bentry = ((DataUnsupported) entry.value).GetPayload();
                            WriteResponseHeader(writer, resource, entry.GetMime(), bentry.Length, entry.lastOwnerID, entry.lastOwnerRevision, senderList, proxyResponsePath, verb, willClose);
                            writer.Flush();

                            if (verb == GET)
                            {
                                bufferedOutput.Write(bentry, 0, bentry.Length);
                            }
                        }
                        else
                        {
                            throw new NotImplementedException();
                        }

                    }

                    else
                    {
                        throw new NotImplementedException();
                    }
                }
        }
        /// <summary>
        /// TODO: add in some code to reduce round-trips to simple data types 
        /// </summary>
        /// <param name="reader">File to read</param>
        /// <param name="sentFromList">Sent from list</param>
        /// <param name="getEntriesFromSender">This function fills in a list of entries that need to be requested from the sender</param>
        /// <param name="addEntriesToSender">These are entries that the sender does not know about</param>
        /// <seealso cref="ReadHeadStub"/>
        private void ReadDictionaryTextFile(StreamReader reader, List<int> sentFromList, List<DataHeader> getEntriesFromSender, List<SendMemoryToPeer> addEntriesToSender)
        {
            // 0 - key name
            // 1 - owner
            // 2 - revision
            // 3 - rw flag
            // 4 - MIME type

            // WriteDebug(this.local_uid + " ReadDictionaryTextFile");

            string nsLine = reader.ReadLine();
            string[] nsLineParts = nsLine.Split('\t');

            System.Diagnostics.Debug.Assert(nsLineParts[0] == DATA_NAMESPACE + "/");

            // if the owner of the dictionary is the same as myself, skip reading the changes
            if (nsLineParts[1] == this.local_uid.ToString())
            {
                throw new NotSupportedException("ReadDictionaryTextFile cannot read itself");// i want to see if this actually can happen (only when multiple connections happen on the same server)

            }

            int itemCount = int.Parse(nsLineParts[4]); // count of all the items in the dictionary

            List<DataEntry> entriesCovered = new List<DataEntry>(itemCount + this.data.Count);

            for (int i = 0; i < itemCount; i++)
            {
                nsLine = reader.ReadLine();
                nsLineParts = nsLine.Split('\t');
                //WriteDebug(nsLine);

                DataHeader getEntry = null;
                SendMemoryToPeer addEntryToSender = null;

                ETag tag = new ETag(int.Parse(nsLineParts[1]), int.Parse(nsLineParts[2]));

                // this entry is used only to call ReadMimeSimpleData
                DataEntry fakeEntry = new DataEntry("/fake", tag, new List<int>(0));
                fakeEntry.ReadMimeSimpleData(nsLineParts[4]);

                dataLock.EnterReadLock();
                try
                {
                    if (this.data.ContainsKey(nsLineParts[0]))
                    {
                        entriesCovered.Add(this.data[nsLineParts[0]]);
                    }
                }
                finally { dataLock.ExitReadLock(); }

                // the dictionary does not report the current sender so let's tack it on
                List<int> listOfSenders = new List<int>(GetArrayOf(nsLineParts[5]));
                if (!listOfSenders.Contains(this.remote_uid))
                    listOfSenders.Add(this.remote_uid);

                ResponseAction action = ReadDataStub(nsLineParts[0], fakeEntry.GetMime(), nsLineParts[1] + "." + nsLineParts[2], new List<int>( listOfSenders), out getEntry, out addEntryToSender);

                if (getEntry != null)
                {
                    getEntriesFromSender.Add(getEntry);
                }
                if (addEntryToSender != null)
                {
                    addEntriesToSender.Add(addEntryToSender);
                }

                if (action == ResponseAction.ForwardToAll)
                {
                    listOfSenders.Clear();
                }
                if (action != ResponseAction.DoNotForward)
                {
                    DataEntry get = P2PDictionary.GetEntry( this.data, this.dataLock, nsLineParts[0]);
                    System.Diagnostics.Debug.Assert(get != null);

                    listOfSenders.Add(this.local_uid);
                    SendBroadcastMemory msg = new SendBroadcastMemory(get.key , listOfSenders);
                    WriteMethodPush(get.key, listOfSenders, null, 0, get.GetMime(), get.GetETag(), get.IsEmpty, false, msg.MemBuffer);
                    this.controller.BroadcastToWire(msg);
                }
            }

            // now check to see which dictionary entries that the sender does not have; i'll send my entries to the caller
            this.dataLock.EnterWriteLock();
            try
            {
                foreach (KeyValuePair<string, DataEntry> senderDoesNotHave in this.data.SkipWhile(x => entriesCovered.Contains(x.Value)))
                {
                    DataEntry get = senderDoesNotHave.Value;

                    // i know about something that the sender does not know
                    SendMemoryToPeer mem = new SendMemoryToPeer(get.key, sentFromList);
                    WriteMethodPush(get.key, GetListOfThisLocalID(), null, 0, get.GetMime(), get.GetETag(), get.IsEmpty, false, mem.MemBuffer);
                    addEntriesToSender.Add(mem);
                }
            }
            finally { this.dataLock.ExitWriteLock();}
        }
        private ResponseAction ReadDelete(string contentLocation, string eTag, List<int> senderPath)
        {
            ResponseAction success = ResponseAction.DoNotForward;
            ETag tag = ReadETag(eTag);

            bool upgradeable = true;
            this.dataLock.EnterUpgradeableReadLock();
            try
            {
                if (this.data.ContainsKey(contentLocation))
                {
                    DataEntry get = this.data[contentLocation];
                    object oldValue = null;

                    // exit lock
                    this.dataLock.ExitUpgradeableReadLock();
                    upgradeable = false;

                    lock (get)
                    {
                        ETagCompare compareResult = ETag.CompareETags(tag, get.GetETag());
                        if (compareResult == ETagCompare.FirstIsNewer || compareResult == ETagCompare.Conflict
                            || compareResult == ETagCompare.Same)
                        {
                            oldValue = get.value;

                            if (compareResult == ETagCompare.Conflict)
                            {
                                success = ResponseAction.ForwardToAll;

                                tag.UID = this.local_uid;
                                tag.Revision = IncrementRevisionRandomizer(tag.Revision);
                            }
                            else if (DataMissing.IsSingleton(oldValue))
                            {
                                success = ResponseAction.ForwardToSuccessor;
                            }
                            else if (compareResult == ETagCompare.Same)
                            {
                                success = ResponseAction.DoNotForward;
                            }
                            else//first is newer
                            {
                                success = ResponseAction.ForwardToSuccessor;
                            }

                            get.lastOwnerID = tag.UID;
                            get.lastOwnerRevision = tag.Revision;
                            get.Delete();

                            if (compareResult != ETagCompare.Same)
                            {
                                get.senderPath = senderPath;
                            }

                            if (!get.subscribed)
                            {
                                get.value = DataMissing.Singleton;
                            }
                        }
                        else//if (compareResult == ETagCompare.SecondIsNewer)
                        {
                            // return to sender
                            success = ResponseAction.ForwardToAll;

                        }
                    } // end lock

                    // notify to subscribers
                    if (success != ResponseAction.DoNotForward && get.subscribed)
                        this.controller.Notified(new NotificationEventArgs(get, "", NotificationReason.Remove, oldValue));

                }
                else
                {

                    // create a stub of the item
                    DataEntry create = new DataEntry(contentLocation, tag, senderPath);
                    create.Delete();
                    create.subscribed = keysToListen.IsSubscribed(contentLocation);
                    if (!create.subscribed)
                    {
                        create.value = DataMissing.Singleton;
                    }

                    dataLock.EnterWriteLock();
                    try
                    {
                        this.data.Add(contentLocation, create);
                    }
                    finally
                    {
                        this.dataLock.ExitWriteLock();
                    }

                    if (create.subscribed)
                    {
                        // notify for subscribers
                        this.controller.Notified(new NotificationEventArgs(create, "", NotificationReason.Remove, null));
                    }

                    success = ResponseAction.ForwardToSuccessor;
                }
            }
            finally
            {
                if (upgradeable)
                    this.dataLock.ExitUpgradeableReadLock();
            }

            return success;
        }
        /// <summary>
        /// Reads data using only header information. Can be used by ReadDictionary
        /// so it handles deleted content too.
        /// </summary>
        /// <param name="contentLocation">Location of the data item without /proxy</param>
        /// <param name="contentType">GetMime()</param>
        /// <param name="eTag">Version number</param>
        /// <param name="addEntryToSender">These are entries that the sender does not know about</param>
        /// <param name="getEntryFromSender">This function fills in a list of entries that need to be requested from the sender</param>
        private ResponseAction ReadDataStub(string contentLocation, string contentType, string eTag, 
            List<int> sentFromList, out DataHeader getEntryFromSender, out SendMemoryToPeer addEntryToSender)
        {
            ResponseAction success = ResponseAction.DoNotForward;
            ETag tag = ReadETag(eTag);

            DataEntry get = null;
            getEntryFromSender = null;
            addEntryToSender = null;

            // create a stub of the item
            DataEntry create = new DataEntry(contentLocation, tag, sentFromList);
            create.subscribed = keysToListen.IsSubscribed(contentLocation);
            create.SetMime(contentType);

            // manually erase the value (TODO, don't erase the value)
            // always default to singleton because we assume that a GET is required to complete the request
            if (create.type != Data.ValueType.Removed)
                create.value = DataMissing.Singleton;

            this.dataLock.EnterUpgradeableReadLock();
            try
            {
                if (this.data.ContainsKey(contentLocation))
                {
                    // update the version number of the stub
                    get = this.data[contentLocation];
                }
                else
                {
                    this.dataLock.EnterWriteLock();
                    try
                    {
                        this.data.Add(contentLocation, create);
                    }
                    finally { this.dataLock.ExitWriteLock(); }

                    if (create.subscribed && DataMissing.IsSingleton(create.value))
                    {
                        // we'll have to wait for the data to arrive on the wire
                        // actually get the data
                        getEntryFromSender = new DataHeader(contentLocation, tag, sentFromList);
                        success = ResponseAction.DoNotForward;
                    }
                    else
                    {
                        success = ResponseAction.ForwardToSuccessor;
                    }

                }
            }

            finally
            {
                this.dataLock.ExitUpgradeableReadLock();
            }

            if (get != null)
            {
                lock (get)
                {
                    if (create.subscribed)
                    {
                        ETagCompare compareResult = ETag.CompareETags(tag, get.GetETag());

                        if (compareResult == ETagCompare.FirstIsNewer ||
                            compareResult == ETagCompare.Same || compareResult == ETagCompare.Conflict)
                        {
                            getEntryFromSender = new DataHeader(create.key, create.GetETag(), sentFromList);
                            success = ResponseAction.DoNotForward;
                        }
                        else //if (compareResult == ETagCompare.SecondIsNewer )
                        {
                            // i know about something newer than the sender, tell the sender
                            //SendMemoryToPeer mem = new SendMemoryToPeer(get.key,sentFromList);
                            //ResponseHeadStub(HEAD, get.key, GetListOfThisLocalID(), 0, get.GetMime(), get.GetETag(), get.IsEmpty, mem.MemBuffer, false);
                            //addEntryToSender = mem;

                            // well, predecessor already been handled above, so we only need to tell
                            // the others
                            success = ResponseAction.ForwardToAll;
                        }
                    }
                    else
                    {
                        // not subscribed
                        // just record the fact that there is newer data on the wire; cannot resolve conflicts without being a subscriber
                        ETagCompare compareResult = ETag.CompareETags(create.GetETag(), get.GetETag());
                        if (compareResult == ETagCompare.FirstIsNewer || compareResult == ETagCompare.Same || compareResult == ETagCompare.Conflict)
                        {
                            get.lastOwnerID = create.lastOwnerID;
                            get.lastOwnerRevision = create.lastOwnerRevision;
                            get.value = DataMissing.Singleton;

                            if (compareResult != ETagCompare.Same)
                            {
                                get.senderPath = create.senderPath;

                                success = ResponseAction.ForwardToSuccessor;
                            }
                            else
                                success = ResponseAction.DoNotForward;
                        }
                        else // if (compareResult == ETagCompare.SecondIsNewer )
                        {
                            //// i know about something newer than the sender
                            //SendMemoryToPeer mem = new SendMemoryToPeer(get.key,sentFromList);
                            //ResponseHeadStub(HEAD, get.key, GetListOfThisLocalID(), 0, get.GetMime(), get.GetETag(), get.IsEmpty, mem.MemBuffer, false);
                            //addEntryToSender = mem;

                            // tell the others too (already told predecessor above)
                            success = ResponseAction.ForwardToAll;
                        }
                    }
                }
            }

            return success;
        }
        /// <summary>
        ///  reads all sorts of data types
        /// </summary>
        /// <param name="contentLocation">location of the data</param>
        /// <param name="eTag">latest version of data being read</param>
        /// <param name="contentType"></param>
        /// <param name="dataOnWire">data read</param>
        /// <returns></returns>
        private ResponseAction ReadData(string contentLocation, string eTag, string contentType, List<int> senders, byte[] dataOnWire)
        {
            ResponseAction success = ResponseAction.DoNotForward;
            ETag tag = ReadETag(eTag);

            // constitute object
            DataEntry create = new DataEntry(contentLocation, tag, senders);
            create.subscribed = this.keysToListen.IsSubscribed(contentLocation);
            create.ReadBytesUsingMime(contentType, dataOnWire);

            bool upgradeable = true;
            DataEntry get = null;

            this.dataLock.EnterUpgradeableReadLock();
            try
            {
                if (this.data.ContainsKey(contentLocation))
                {
                    // update exisitng entry
                    get = this.data[contentLocation];
                }

                if (get == null)
                {
                    this.dataLock.EnterWriteLock();
                    try
                    {
                        // don't save the value if not subscribed
                        if (!create.subscribed)
                        {
                            create.value = DataMissing.Singleton;
                        }

                        this.data.Add(contentLocation, create);
                    }
                    finally
                    {
                        this.dataLock.ExitWriteLock();
                        this.dataLock.ExitUpgradeableReadLock();
                        upgradeable = false;
                    }

                    if (create.subscribed)
                    {
                        // notify API user
                        this.controller.Notified(new NotificationEventArgs(create, contentLocation, NotificationReason.Add, null));
                    }

                    // never seen before, thus tell others
                    success = ResponseAction.ForwardToSuccessor;
                }
            }
            finally
            {
                if (upgradeable)
                    this.dataLock.ExitUpgradeableReadLock();
            }

            if (get != null)
            {
                object oldValue = null;

                lock (get)
                {
                    if (create.subscribed)
                    {

                        ETagCompare compareResult = ETag.CompareETags(create.GetETag(), get.GetETag());
                        if (compareResult == ETagCompare.FirstIsNewer || compareResult == ETagCompare.Conflict || compareResult == ETagCompare.Same)
                        {
                            oldValue = get.value;

                            if (compareResult == ETagCompare.Conflict)
                            {
                                success = ResponseAction.ForwardToAll;

                                // increment the revision and take ownership
                                create.lastOwnerID = this.local_uid;
                                create.lastOwnerRevision = IncrementRevisionRandomizer(create.lastOwnerRevision);
                            }
                            else if (DataMissing.IsSingleton(oldValue))
                            {
                                success = ResponseAction.ForwardToSuccessor;
                            }
                            else if (compareResult == ETagCompare.Same)
                            {
                                success = ResponseAction.DoNotForward;
                            }
                            else//first is newer
                            {
                                success = ResponseAction.ForwardToSuccessor;
                            }

                            get.lastOwnerID = create.lastOwnerID;
                            get.lastOwnerRevision = create.lastOwnerRevision;
                            get.type = create.type;
                            get.value = create.value;

                        }
                        else // SecondIsNewer
                        {
                            // return this data to the sender
                            success = ResponseAction.ForwardToAll;
                        }

                    }
                    else
                    {
                        ETagCompare compareResult = ETag.CompareETags(create.GetETag(), get.GetETag());

                        if (compareResult == ETagCompare.FirstIsNewer || compareResult == ETagCompare.Conflict || compareResult == ETagCompare.Same)
                        {
                            if (compareResult == ETagCompare.Conflict)
                            {
                                success = ResponseAction.ForwardToAll;
                            }
                            else if (compareResult == ETagCompare.Same)
                            {
                                success = ResponseAction.DoNotForward;
                            }
                            else//first is newer
                            {
                                success = ResponseAction.ForwardToSuccessor;
                            }

                            if (compareResult != ETagCompare.Same)
                            {
                                get.lastOwnerID = create.lastOwnerID;
                                get.lastOwnerRevision = create.lastOwnerRevision;
                                get.type = create.type;
                                get.value = DataMissing.Singleton;
                                get.senderPath = create.senderPath;
                            }

                            System.Diagnostics.Debug.Assert(get.type != Data.ValueType.Removed);
                        }
                        else // second is newer
                        {
                            // return this data to the sender
                            success = ResponseAction.ForwardToAll;
                        }

                    }
                }//lock

                // notify API user
                if (success != ResponseAction.DoNotForward && get.subscribed && !DataMissing.IsSingleton(get.value))
                {
                    get.senderPath = create.senderPath;

                    this.controller.Notified(new NotificationEventArgs(get,contentLocation, NotificationReason.Change, oldValue));
                }

            } // else if

            return success;
        }