text: ﻿// The MIT License (MIT)
  
  // Copyright (c) 2016 Ben Abelshausen
  
  // Permission is hereby granted, free of charge, to any person obtaining a copy
  // of this software and associated documentation files (the "Software"), to deal
  // in the Software without restriction, including without limitation the rights
  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  // copies of the Software, and to permit persons to whom the Software is
  // furnished to do so, subject to the following conditions:
  
  // The above copyright notice and this permission notice shall be included in
  // all copies or substantial portions of the Software.
  
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  // THE SOFTWARE.
  
  using OsmSharp.Complete;
  using OsmSharp.Db;
  using OsmSharp.Db.Impl;
  using System;
  using System.Collections.Generic;
  
  namespace OsmSharp.Streams.Complete
  {
      /// <summary>
      /// Represents a complete stream source that converts a simple stream into a complete stream.
      /// </summary>
      public class OsmSimpleCompleteStreamSource : OsmCompleteStreamSource
      {
          private readonly ISnapshotDb _dataCache; // Caches objects that are needed later to complete objects.
          private readonly OsmStreamSource _simpleSource; // Holds the simple source of object.
  
          /// <summary>
          /// Creates a new osm simple complete stream.
          /// </summary>
          public OsmSimpleCompleteStreamSource(OsmStreamSource source)
          {
              // create an in-memory cache by default.
              _dataCache = (new MemorySnapshotDb()).CreateSnapshotDb();
              _simpleSource = source;
  
              _nodesToInclude = new HashSet<long>();
              _nodesUsedTwiceOrMore = new Dictionary<long, int>();
              _waysToInclude = new HashSet<long>();
              _waysUsedTwiceOrMore = new Dictionary<long, int>();
              _relationsToInclude = new HashSet<long>();
              _relationsUsedTwiceOrMore = new Dictionary<long, int>();
          }
  
          /// <summary>
          /// Creates a new osm simple complete stream.
          /// </summary>
          public OsmSimpleCompleteStreamSource(OsmStreamSource source, ISnapshotDb cache)
          {
              _dataCache = cache;
              _simpleSource = source;
  
              _nodesToInclude = new HashSet<long>();
              _nodesUsedTwiceOrMore = new Dictionary<long, int>();
              _waysToInclude = new HashSet<long>();
              _waysUsedTwiceOrMore = new Dictionary<long, int>();
              _relationsToInclude = new HashSet<long>();
              _relationsUsedTwiceOrMore = new Dictionary<long, int>();
          }
  
          /// <summary>
          /// Initializes this source.
          /// </summary>
          public override void Initialize()
          {
              _cachingDone = false;
              _nodesToInclude.Clear();
              _nodesUsedTwiceOrMore.Clear();
              _waysToInclude.Clear();
              _waysUsedTwiceOrMore.Clear();
              _relationsToInclude.Clear();
              _relationsUsedTwiceOrMore.Clear();
  
              if (!_simpleSource.CanReset)
              { // the simple source cannot be reset, each object can be a child, no other option than caching everything!
                  // TODO: support this scenario, can be usefull when streaming data from a non-seekable stream.
                  throw new NotSupportedException("Creating a complete stream from a non-resettable simple stream is not supported. Wrap the source stream and create a resettable stream.");
              }
              else
              { // the simple source can be reset.
  
              }
          }
  
          private bool _cachingDone; // Flag indicating that the caching was done.
          private ICompleteOsmGeo _current; // Holds the current complete object.
  
          /// <summary>
          /// Moves to the next object.
          /// </summary>
          public override bool MoveNext()
          {
              if (!_cachingDone)
              { // first seek & cache.
                  this.Seek();
                  _cachingDone = true;
              }
  
              while (_simpleSource.MoveNext())
              { // there is data.
                  var currentSimple = _simpleSource.Current();
  
                  switch (currentSimple.Type)
                  {
                      case OsmGeoType.Node:
                          // create complete version.
                          _current = currentSimple as Node;
  
                          if (_current != null && _current.Tags == null)
                          { // make sure nodes have a default tag collection that is empty not null.
                              _current.Tags = new OsmSharp.Tags.TagsCollection();
                          }
                          if (_nodesToInclude.Contains(currentSimple.Id.Value))
                          { // node needs to be cached.
                              _dataCache.AddOrUpdate(currentSimple as Node);
                              _nodesToInclude.Remove(currentSimple.Id.Value);
                          }
                          break;
                      case OsmGeoType.Way:
                          // create complete way.
                          _current = (currentSimple as Way).CreateComplete(_dataCache);
  
                          if (_waysToInclude.Contains(currentSimple.Id.Value))
                          { // keep the way because it is needed later on.
                              _dataCache.AddOrUpdate(currentSimple as Way);
                              _waysToInclude.Remove(currentSimple.Id.Value);
                          }
                          else
                          { // only report that the nodes have been used when the way can be let-go.
                              var way = currentSimple as Way;
                              if (way.Nodes != null)
                              { // report usage.
                                  for(var i = 0; i < way.Nodes.Length; i++)
                                  {
                                      this.ReportNodeUsage(way.Nodes[i]);
                                  }
                              }
                          }
                          break;
                      case OsmGeoType.Relation:
                          // create complate relation.
                          _current = (currentSimple as Relation).CreateComplete(_dataCache);
  
                          if (!_relationsToInclude.Contains(currentSimple.Id.Value))
                          { // only report relation usage when the relation can be let go.
                              var relation = currentSimple as Relation;
                              if (relation.Members != null)
                              {
                                  foreach (var member in relation.Members)
                                  {
                                      switch (member.Type)
                                      {
                                          case OsmGeoType.Node:
                                              this.ReportNodeUsage(member.Id);
                                              break;
                                          case OsmGeoType.Way:
                                              this.ReportWayUsage(member.Id);
                                              break;
                                          case OsmGeoType.Relation:
                                              this.ReportRelationUsage(member.Id);
                                              break;
                                      }
                                  }
                              }
                          }
                          break;
                  }
                  if (_current != null)
                  { // only return complete objects.
                      return true;
                  }
              }
              return false;
          }
  
          private readonly HashSet<long> _nodesToInclude; // An index of extra nodes to include.
          private readonly Dictionary<long, int> _nodesUsedTwiceOrMore; // An index of nodes that are being used twice or more.
          private readonly HashSet<long> _waysToInclude; // An index of extra relations to include.
          private readonly Dictionary<long, int> _waysUsedTwiceOrMore; // An index of ways that are being used twice or more.
          private readonly HashSet<long> _relationsToInclude; // An index of extra ways to include.
          private readonly Dictionary<long, int> _relationsUsedTwiceOrMore; // An index of relations that are being used twice or more.
  
          /// <summary>
          /// Seeks for objects that are children of other objects.
          /// </summary>
          private void Seek()
          {
              var relations = new List<Relation>();
              while (_simpleSource.MoveNext(true, false, false))
              {
                  var osmGeo = _simpleSource.Current();
  
                  switch (osmGeo.Type)
                  {
                      case OsmGeoType.Way:
                          var way = (osmGeo as Way);
                          if (way.Nodes != null)
                          {
                              foreach (var nodeId in way.Nodes)
                              {
                                  this.MarkNodeAsChild(nodeId);
                              }
                          }
                          break;
                      case OsmGeoType.Relation:
                          var relation = (osmGeo as Relation);
                          if (relation.Members != null)
                          {
                              foreach (var member in relation.Members)
                              {
                                  switch (member.Type)
                                  {
                                      case OsmGeoType.Node:
                                          this.MarkNodeAsChild(member.Id);
                                          break;
                                      case OsmGeoType.Way:
                                          this.MarkWayAsChild(member.Id);
                                          break;
                                      case OsmGeoType.Relation:
                                          this.MarkRelationAsChild(member.Id);
                                          break;
                                  }
                              }
                          }
                          relations.Add(relation);
                          break;
                  }
              }
              foreach (var relation in relations)
              {
                  if (_relationsToInclude.Contains(relation.Id.Value))
                  { // yep, cache relation!
                      _dataCache.AddOrUpdate(relation);
                  }
              }
              _simpleSource.Reset();
          }
  
          /// <summary>
          /// Reports that the node with the given id was used.
          /// </summary>
          private void ReportNodeUsage(long nodeId)
          {
              int nodeCount;
              if (_nodesUsedTwiceOrMore.TryGetValue(nodeId, out nodeCount))
              { // node is used twice or more.
                  nodeCount--;
                  if (nodeCount > 0)
                  { // update count.
                      _nodesUsedTwiceOrMore[nodeId] = nodeCount;
                  }
                  else
                  { // remove twice or more.
                      _nodesUsedTwiceOrMore.Remove(nodeId);
                  }
              }
              else
              { // the node was used for the last time.
                  _dataCache.DeleteNode(nodeId);
              }
          }
  
          /// <summary>
          /// Marks the given node as child.
          /// </summary>
          private void MarkNodeAsChild(long nodeId)
          {
              if (_nodesToInclude.Contains(nodeId))
              { // increase the node counter.
                  int nodeCount;
                  if (!_nodesUsedTwiceOrMore.TryGetValue(nodeId, out nodeCount))
                  { // the node is used twice or more.
                      nodeCount = 1;
                      _nodesUsedTwiceOrMore.Add(nodeId, nodeCount);
                  }
                  else
                  { // increase the count.
                      nodeCount++;
                      _nodesUsedTwiceOrMore[nodeId] = nodeCount;
                  }
              }
              else
              {
                  // just add the node.
                  _nodesToInclude.Add(nodeId);
              }
          }
  
          /// <summary>
          /// Reports that the way with the given id was used.
          /// </summary>
          private void ReportWayUsage(long wayId)
          {
              int wayCount;
              if (_waysUsedTwiceOrMore.TryGetValue(wayId, out wayCount))
              { // way is used twice or more.
                  wayCount--;
                  if (wayCount > 0)
                  { // update count.
                      _waysUsedTwiceOrMore[wayId] = wayCount;
                  }
                  else
                  { // remove twice or more.
                      _waysUsedTwiceOrMore.Remove(wayId);
                  }
              }
              else
              { // the way was used for the last time.
                  var way = _dataCache.GetWay(wayId); // get the way before it is removed.
                  _dataCache.DeleteWay(wayId); // remove from cache.
                  if (way != null && way.Nodes != null)
                  { // report usage.
                      for(var i = 0; i < way.Nodes.Length; i++)
                      {
                          this.ReportNodeUsage(way.Nodes[i]);
                      }
                  }
              }
          }
  
          /// <summary>
          /// Marks the given way as child.
          /// </summary>
          private void MarkWayAsChild(long wayId)
          {
              if (_waysToInclude.Contains(wayId))
              { // increase the way counter.
                  int wayCount;
                  if (!_waysUsedTwiceOrMore.TryGetValue(wayId, out wayCount))
                  { // the way is used twice or more.
                      wayCount = 1;
                      _waysUsedTwiceOrMore.Add(wayId, wayCount);
                  }
                  else
                  { // increase the count. 
                      wayCount++;
                      _waysUsedTwiceOrMore[wayId] = wayCount;
                  }
              }
              else
              {
                  // just add the way.
                  _waysToInclude.Add(wayId);
              }
          }
  
          /// <summary>
          /// Reports that the relation with the given id was used.
          /// </summary>
          private void ReportRelationUsage(long relationId)
          {
              int relationCount;
              if (_relationsUsedTwiceOrMore.TryGetValue(relationId, out relationCount))
              { // relation is used twice or more.
                  relationCount--;
                  if (relationCount > 0)
                  { // update count.
                      _relationsUsedTwiceOrMore[relationId] = relationCount;
                  }
                  else
                  { // remove twice or more.
                      _relationsUsedTwiceOrMore.Remove(relationId);
                  }
              }
              else
              { // the relation was used for the last time.
                  var relation = _dataCache.GetRelation(relationId); // get relation before it is removed.
                  _dataCache.DeleteRelation(relationId); // remove relation.
                  _relationsToInclude.Remove(relationId); // remove from the to-include list.
              }
          }
  
          /// <summary>
          /// Marks the given relation as child.
          /// </summary>
          private void MarkRelationAsChild(long relationId)
          {
              if (_relationsToInclude.Contains(relationId))
              { // increase the relation counter.
                  int relationCount;
                  if (!_relationsUsedTwiceOrMore.TryGetValue(relationId, out relationCount))
                  { // the relation is used twice or more.
                      relationCount = 1;
                      _relationsUsedTwiceOrMore.Add(relationId, relationCount);
                  }
                  else
                  { // increase the count.
                      relationCount++;
                      _relationsUsedTwiceOrMore[relationId] = relationCount;
                  }
              }
              else
              {
                  // just add the relation.
                  _relationsToInclude.Add(relationId);
              }
          }
  
          /// <summary>
          /// Returns the current object.
          /// </summary>
          public override ICompleteOsmGeo Current()
          {
              return _current;
          }
  
          /// <summary>
          /// Resets this source.
          /// </summary>
          public override void Reset()
          {
              _cachingDone = false;
  
              _dataCache.Clear();
              _simpleSource.Reset();
          }
  
          /// <summary>
          /// Returns true if this source can be reset.
          /// </summary>
          public override bool CanReset
          {
              get { return _simpleSource.CanReset; }
          }
      }
  }