text:
# cpp-fstlib

[![](https://github.com/yhirose/cpp-fstlib/workflows/CMake/badge.svg)](https://github.com/yhirose/cpp-fstlib/actions)

C++17 header-only FST (finite state transducer) library.
We can use it as [Trie data structure](https://en.wikipedia.org/wiki/Trie).
This library uses the algorithm "[Minimal Acyclic Subsequential Transducers](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.24.3698&rep=rep1&type=pdf)".

## Play cpp-fstlib with cli

```bash
> git clone http://github/yhirose/cpp-fstlib
> cd cpp-fstlib
> make build && cd build
> cmake .. && make
> ./cmd/fst compile /usr/share/dict/words words.fst

> ./cmd/fst search words.fst hello
83713

> ./cmd/fst prefix words.fst helloworld
h: 81421
he: 82951
hell: 83657
hello: 83713

> ./cmd/fst longest words.fst helloworld
hello: 83713

> ./cmd/fst predictive words.fst predictiv
predictive: 153474
predictively: 153475
predictiveness: 153476

> ./cmd/fst fuzzy words.fst fuzzy -ed 2 // Edit distance 2
Suzy: 195759
buzz: 28064
buzzy: 28076
...

> ./cmd/fst spellcheck words.fst thier
their: 0.946667
thir: 0.762667
tier: 0.752
thief: 0.736
trier: 0.704
```

## API reference

```cpp
namespace fst {

enum class Result { Success, EmptyKey, UnsortedKey, DuplicateKey };

std::pair<Result, size_t /* error input index */> compile<uint32_t>(
  const std::vector<std::pair<std::string, uint32_t>> &input,
  std::ostream &os,
  bool sorted
);

std::pair<Result, size_t /* error input index */> compile<std::string>(
  const std::vector<std::pair<std::string, std::string>> &input,
  std::ostream &os
);

std::pair<Result, size_t /* error input index */> compile(
  const std::vector<std::string> &key_only_input,
  std::ostream &os,
  bool need_output, // true: map, false: set
  bool sorted
);

template <typename output_t> class map {
public:
  map(const char *byte_code, size_t byte_code_size);

  operator bool() const;

  bool contains(std::string_view sv) const;

  output_t operator[](std::string_view sv) const;

  output_t at(std::string_view sv) const;

  bool exact_match_search(std::string_view sv, output_t &output) const;

  std::vector<std::pair<size_t length, output_t output>>
  common_prefix_search(std::string_view sv) const;

  size_t longest_common_prefix_search(std::string_view sv, output_t &output) const;

  std::vector<std::pair<std::string, output_t>>
  predictive_search(std::string_view sv) const;

  std::vector<std::pair<std::string, output_t>>
  edit_distance_search(std::string_view sv, size_t max_edits) const;

  std::vector<std::tuple<double, std::string, output_t>>
  suggest(std::string_view word) const;
}

class set {
public:
  set(const char *byte_code, size_t byte_code_size);

  operator bool() const;

  bool contains(std::string_view sv) const;

  std::vector<size_t> common_prefix_search(std::string_view sv) const;

  size_t longest_common_prefix_search(std::string_view sv) const;

  std::vector<std::string> predictive_search(std::string_view sv) const;

  std::vector<std::string>
  edit_distance_search(std::string_view sv, size_t max_edits) const;

  std::vector<std::pair<double, std::string>>
  suggest(std::string_view word) const;
}

} // namespace fst
```

## API usage

```cpp
const std::vector<std::pair<std::string, std::string>> items = {
  {"hello", "こんにちは!"},
  {"world", "世界!"},
  {"hello world", "こんにちは世界!"}, // incorrect sort order entry...
};

std::stringstream out;
auto sorted = false; // ask fst::compile to sort entries
auto [result, error_line] = fst::compile<std::string>(items, out, sorted);

if (result == fst::Result::Success) {
  const auto& byte_code = out.str();
  fst::map<std::string> matcher(byte_code.data(), byte_code.size());

  if (matcher) {
    assert(matcher.contains("hello world"));
    assert(!matcher.contains("Hello World"));
    assert(matcher["hello"] == "こんにちは!");

    auto prefixes = matcher.common_prefix_search("hello world!");
    assert(prefixes.size() == 2);
    assert(prefixes[0].first == 5);
    assert(prefixes[0].second == "こんにちは!");
    assert(prefixes[1].first == 11);
    assert(prefixes[1].second == "こんにちは世界!");

    std::string output;
    auto length = matcher.longest_common_prefix_search("hello world!", output);
    assert(length == 11);
    assert(output == "こんにちは世界!");

    auto predictives = matcher.predictive_search("he");
    assert(predictives.size() == 2);
    assert(predictives[0].first == "hello");
    assert(predictives[0].second == "こんにちは!");
    assert(predictives[1].first == "hello world");
    assert(predictives[1].second == "こんにちは世界!");

    std::cout << "[Edit distance 1]" << std::endl;
    for (auto [k, o]: matcher.edit_distance_search("hellow", 1)) {
      std::cout << "key: " << k << " output: " << o << std::endl;
    }

    std::cout << "[Suggestions]" << std::endl;
    for (auto [r, k, o]: matcher.suggest("hellow")) {
      std::cout << "ratio: " << r << " key: " << k << " output: " << o << std::endl;
    }
  }
}
```

```
[Edit distance 1]
key: hello output: こんにちは
[Suggestions]
ratio: 0.810185 key: hello output: こんにちは
ratio: 0.504132 key: hello world output: こんにちは世界!
ratio: 0.0962963 key: world output: 世界!
```

License
-------

MIT license (© 2022 Yuji Hirose)


text:
package permafrost.tundra.org.springframework.util;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantLock;

/**
 * A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or
 * {@linkplain ReferenceType#WEAK weak} references for both {@code keys} and {@code values}.
 *
 *
This class can be used as an alternative to
 * {@code Collections.synchronizedMap(new WeakHashMap<K, Reference<V>>())} in order to
 * support better performance when accessed concurrently. This implementation follows the
 * same design constraints as {@link ConcurrentHashMap} with the exception that
 * {@code null} values and {@code null} keys are supported.
 *
 *
NOTE: The use of references means that there is no guarantee that items
 * placed into the map will be subsequently available. The garbage collector may discard
 * references at any time, so it may appear that an unknown thread is silently removing
 * entries.
 *
 *
If not explicitly specified, this implementation will use
 * {@linkplain SoftReference soft entry references}.
 *
 * @author Phillip Webb
 * @since 3.2
 * @param <K> the key type
 * @param <V> the value type
 */
public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {

    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    private static final float DEFAULT_LOAD_FACTOR = 0.75f;

    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;

    private static final ReferenceType DEFAULT_REFERENCE_TYPE = ReferenceType.SOFT;

    private static final int MAXIMUM_CONCURRENCY_LEVEL = 1 << 16;

    private static final int MAXIMUM_SEGMENT_SIZE = 1 << 30;

    /**
     * Array of segments indexed using the high order bits from the hash.
     */
    private final Segment[] segments;

    /**
     * When the average number of references per table exceeds this value resize will be attempted.
     */
    private final float loadFactor;

    /**
     * The reference type: SOFT or WEAK.
     */
    private final ReferenceType referenceType;

    /**
     * The shift value used to calculate the size of the segments array and an index from the hash.
     */
    private final int shift;

    /**
     * Late binding entry set.
     */
    private Set<Map.Entry<K, V>> entrySet;

    /**
     * Create a new {@code ConcurrentReferenceHashMap} instance.
     */
    public ConcurrentReferenceHashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
    }

    /**
     * Create a new {@code ConcurrentReferenceHashMap} instance.
     * @param initialCapacity the initial capacity of the map
     */
    public ConcurrentReferenceHashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
    }

    /**
     * Create a new {@code ConcurrentReferenceHashMap} instance.
     * @param initialCapacity the initial capacity of the map
     * @param loadFactor the load factor. When the average number of references per table
     * exceeds this value resize will be attempted
     */
    public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) {
        this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
    }

    /**
     * Create a new {@code ConcurrentReferenceHashMap} instance.
     * @param initialCapacity the initial capacity of the map
     * @param concurrencyLevel the expected number of threads that will concurrently
     * write to the map
     */
    public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, DEFAULT_REFERENCE_TYPE);
    }

    /**
     * Create a new {@code ConcurrentReferenceHashMap} instance.
     * @param initialCapacity the initial capacity of the map
     * @param referenceType the reference type used for entries (soft or weak)
     */
    public ConcurrentReferenceHashMap(int initialCapacity, ReferenceType referenceType) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, referenceType);
    }

    /**
     * Create a new {@code ConcurrentReferenceHashMap} instance.
     * @param initialCapacity the initial capacity of the map
     * @param loadFactor the load factor. When the average number of references per
     * table exceeds this value, resize will be attempted.
     * @param concurrencyLevel the expected number of threads that will concurrently
     * write to the map
     */
    public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
        this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE);
    }

    /**
     * Create a new {@code ConcurrentReferenceHashMap} instance.
     * @param initialCapacity the initial capacity of the map
     * @param loadFactor the load factor. When the average number of references per
     * table exceeds this value, resize will be attempted.
     * @param concurrencyLevel the expected number of threads that will concurrently
     * write to the map
     * @param referenceType the reference type used for entries (soft or weak)
     */
    @SuppressWarnings("unchecked")
    public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel,
                                      ReferenceType referenceType) {

        Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative");
        Assert.isTrue(loadFactor > 0f, "Load factor must be positive");
        Assert.isTrue(concurrencyLevel > 0, "Concurrency level must be positive");
        Assert.notNull(referenceType, "Reference type must not be null");
        this.loadFactor = loadFactor;
        this.shift = calculateShift(concurrencyLevel, MAXIMUM_CONCURRENCY_LEVEL);
        int size = 1 << this.shift;
        this.referenceType = referenceType;
        int roundedUpSegmentCapacity = (int) ((initialCapacity + size - 1L) / size);
        this.segments = (Segment[]) Array.newInstance(Segment.class, size);
        for (int i = 0; i < this.segments.length; i++) {
            this.segments[i] = new Segment(roundedUpSegmentCapacity);
        }
    }

    protected final float getLoadFactor() {
        return this.loadFactor;
    }

    protected final int getSegmentsSize() {
        return this.segments.length;
    }

    protected final Segment getSegment(int index) {
        return this.segments[index];
    }

    /**
     * Factory method that returns the {@link ReferenceManager}.
     * This method will be called once for each {@link Segment}.
     * @return a new reference manager
     */
    protected ReferenceManager createReferenceManager() {
        return new ReferenceManager();
    }

    /**
     * Get the hash for a given object, apply an additional hash function to reduce
     * collisions. This implementation uses the same Wang/Jenkins algorithm as
     * {@link ConcurrentHashMap}. Subclasses can override to provide alternative hashing.
     * @param o the object to hash (may be null)
     * @return the resulting hash code
     */
    protected int getHash(Object o) {
        int hash = o == null ? 0 : o.hashCode();
        hash += (hash << 15) ^ 0xffffcd7d;
        hash ^= (hash >>> 10);
        hash += (hash << 3);
        hash ^= (hash >>> 6);
        hash += (hash << 2) + (hash << 14);
        hash ^= (hash >>> 16);
        return hash;
    }

    @Override
    public V get(Object key) {
        Entry<K, V> entry = getEntryIfAvailable(key);
        return (entry != null ? entry.getValue() : null);
    }

    public V getOrDefault(Object key, V defaultValue) {
        Entry<K, V> entry = getEntryIfAvailable(key);
        return (entry != null ? entry.getValue() : defaultValue);
    }

    @Override
    public boolean containsKey(Object key) {
        Entry<K, V> entry = getEntryIfAvailable(key);
        return (entry != null && ObjectUtils.nullSafeEquals(entry.getKey(), key));
    }

    private Entry<K, V> getEntryIfAvailable(Object key) {
        Reference<K, V> reference = getReference(key, Restructure.WHEN_NECESSARY);
        return (reference != null ? reference.get() : null);
    }

    /**
     * Return a {@link Reference} to the {@link Entry} for the specified {@code key},
     * or {@code null} if not found.
     * @param key the key (can be {@code null})
     * @param restructure types of restructure allowed during this call
     * @return the reference, or {@code null} if not found
     */
    protected final Reference<K, V> getReference(Object key, Restructure restructure) {
        int hash = getHash(key);
        return getSegmentForHash(hash).getReference(key, hash, restructure);
    }

    @Override
    public V put(K key, V value) {
        return put(key, value, true);
    }

    @Override
    public V putIfAbsent(K key, V value) {
        return put(key, value, false);
    }

    private V put(final K key, final V value, final boolean overwriteExisting) {
        return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
            @Override
            protected V execute(Reference<K, V> reference, Entry<K, V> entry, Entries entries) {
                if (entry != null) {
                    V previousValue = entry.getValue();
                    if (overwriteExisting) {
                        entry.setValue(value);
                    }
                    return previousValue;
                }
                entries.add(value);
                return null;
            }
        });
    }

    @Override
    public V remove(Object key) {
        return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
            @Override
            protected V execute(Reference<K, V> reference, Entry<K, V> entry) {
                if (entry != null) {
                    reference.release();
                    return entry.value;
                }
                return null;
            }
        });
    }

    @Override
    public boolean remove(Object key, final Object value) {
        return doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
            @Override
            protected Boolean execute(Reference<K, V> reference, Entry<K, V> entry) {
                if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), value)) {
                    reference.release();
                    return true;
                }
                return false;
            }
        });
    }

    @Override
    public boolean replace(K key, final V oldValue, final V newValue) {
        return doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
            @Override
            protected Boolean execute(Reference<K, V> reference, Entry<K, V> entry) {
                if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), oldValue)) {
                    entry.setValue(newValue);
                    return true;
                }
                return false;
            }
        });
    }

    @Override
    public V replace(K key, final V value) {
        return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
            @Override
            protected V execute(Reference<K, V> reference, Entry<K, V> entry) {
                if (entry != null) {
                    V previousValue = entry.getValue();
                    entry.setValue(value);
                    return previousValue;
                }
                return null;
            }
        });
    }

    @Override
    public void clear() {
        for (Segment segment : this.segments) {
            segment.clear();
        }
    }

    /**
     * Remove any entries that have been garbage collected and are no longer referenced.
     * Under normal circumstances garbage collected entries are automatically purged as
     * items are added or removed from the Map. This method can be used to force a purge,
     * and is useful when the Map is read frequently but updated less often.
     */
    public void purgeUnreferencedEntries() {
        for (Segment segment : this.segments) {
            segment.restructureIfNecessary(false);
        }
    }

    @Override
    public int size() {
        int size = 0;
        for (Segment segment : this.segments) {
            size += segment.getCount();
        }
        return size;
    }

    @Override
    public Set<java.util.Map.Entry<K, V>> entrySet() {
        if (this.entrySet == null) {
            this.entrySet = new EntrySet();
        }
        return this.entrySet;
    }

    private <T> T doTask(Object key, Task<T> task) {
        int hash = getHash(key);
        return getSegmentForHash(hash).doTask(hash, key, task);
    }

    private Segment getSegmentForHash(int hash) {
        return this.segments[(hash >>> (32 - this.shift)) & (this.segments.length - 1)];
    }

    /**
     * Calculate a shift value that can be used to create a power-of-two value between
     * the specified maximum and minimum values.
     * @param minimumValue the minimum value
     * @param maximumValue the maximum value
     * @return the calculated shift (use {@code 1 << shift} to obtain a value)
     */
    protected static int calculateShift(int minimumValue, int maximumValue) {
        int shift = 0;
        int value = 1;
        while (value < minimumValue && value < maximumValue) {
            value <<= 1;
            shift++;
        }
        return shift;
    }

    /**
     * Various reference types supported by this map.
     */
    public enum ReferenceType {

        /** Use {@link SoftReference}s */
        SOFT,

        /** Use {@link WeakReference}s */
        WEAK
    }

    /**
     * A single segment used to divide the map to allow better concurrent performance.
     */
    @SuppressWarnings("serial")
    protected final class Segment extends ReentrantLock {

        private final ReferenceManager referenceManager;

        private final int initialSize;

        /**
         * Array of references indexed using the low order bits from the hash.
         * This property should only be set along with {@code resizeThreshold}.
         */
        private volatile Reference<K, V>[] references;

        /**
         * The total number of references contained in this segment. This includes chained
         * references and references that have been garbage collected but not purged.
         */
        private volatile int count = 0;

        /**
         * The threshold when resizing of the references should occur. When {@code count}
         * exceeds this value references will be resized.
         */
        private int resizeThreshold;

        public Segment(int initialCapacity) {
            this.referenceManager = createReferenceManager();
            this.initialSize = 1 << calculateShift(initialCapacity, MAXIMUM_SEGMENT_SIZE);
            setReferences(createReferenceArray(this.initialSize));
        }

        public Reference<K, V> getReference(Object key, int hash, Restructure restructure) {
            if (restructure == Restructure.WHEN_NECESSARY) {
                restructureIfNecessary(false);
            }
            if (this.count == 0) {
                return null;
            }
            // Use a local copy to protect against other threads writing
            Reference<K, V>[] references = this.references;
            int index = getIndex(hash, references);
            Reference<K, V> head = references[index];
            return findInChain(head, key, hash);
        }

        /**
         * Apply an update operation to this segment.
         * The segment will be locked during the update.
         * @param hash the hash of the key
         * @param key the key
         * @param task the update operation
         * @return the result of the operation
         */
        public <T> T doTask(final int hash, final Object key, final Task<T> task) {
            boolean resize = task.hasOption(TaskOption.RESIZE);
            if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) {
                restructureIfNecessary(resize);
            }
            if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && this.count == 0) {
                return task.execute(null, null, null);
            }
            lock();
            try {
                final int index = getIndex(hash, this.references);
                final Reference<K, V> head = this.references[index];
                Reference<K, V> reference = findInChain(head, key, hash);
                Entry<K, V> entry = (reference != null ? reference.get() : null);
                Entries entries = new Entries() {
                    @Override
                    public void add(V value) {
                        @SuppressWarnings("unchecked")
                        Entry<K, V> newEntry = new Entry<K, V>((K) key, value);
                        Reference<K, V> newReference = Segment.this.referenceManager.createReference(newEntry, hash, head);
                        Segment.this.references[index] = newReference;
                        Segment.this.count++;
                    }
                };
                return task.execute(reference, entry, entries);
            }
            finally {
                unlock();
                if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) {
                    restructureIfNecessary(resize);
                }
            }
        }

        /**
         * Clear all items from this segment.
         */
        public void clear() {
            if (this.count == 0) {
                return;
            }
            lock();
            try {
                setReferences(createReferenceArray(this.initialSize));
                this.count = 0;
            }
            finally {
                unlock();
            }
        }

        /**
         * Restructure the underlying data structure when it becomes necessary. This
         * method can increase the size of the references table as well as purge any
         * references that have been garbage collected.
         * @param allowResize if resizing is permitted
         */
        protected final void restructureIfNecessary(boolean allowResize) {
            boolean needsResize = ((this.count > 0) && (this.count >= this.resizeThreshold));
            Reference<K, V> reference = this.referenceManager.pollForPurge();
            if ((reference != null) || (needsResize && allowResize)) {
                lock();
                try {
                    int countAfterRestructure = this.count;

                    Set<Reference<K, V>> toPurge = Collections.emptySet();
                    if (reference != null) {
                        toPurge = new HashSet<Reference<K, V>>();
                        while (reference != null) {
                            toPurge.add(reference);
                            reference = this.referenceManager.pollForPurge();
                        }
                    }
                    countAfterRestructure -= toPurge.size();

                    // Recalculate taking into account count inside lock and items that
                    // will be purged
                    needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold);
                    boolean resizing = false;
                    int restructureSize = this.references.length;
                    if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) {
                        restructureSize <<= 1;
                        resizing = true;
                    }

                    // Either create a new table or reuse the existing one
                    Reference<K, V>[] restructured = (resizing ? createReferenceArray(restructureSize) : this.references);

                    // Restructure
                    for (int i = 0; i < this.references.length; i++) {
                        reference = this.references[i];
                        if (!resizing) {
                            restructured[i] = null;
                        }
                        while (reference != null) {
                            if (!toPurge.contains(reference) && (reference.get() != null)) {
                                int index = getIndex(reference.getHash(), restructured);
                                restructured[index] = this.referenceManager.createReference(
                                        reference.get(), reference.getHash(),
                                        restructured[index]);
                            }
                            reference = reference.getNext();
                        }
                    }

                    // Replace volatile members
                    if (resizing) {
                        setReferences(restructured);
                    }
                    this.count = Math.max(countAfterRestructure, 0);
                }
                finally {
                    unlock();
                }
            }
        }

        private Reference<K, V> findInChain(Reference<K, V> reference, Object key, int hash) {
            Reference<K, V> currRef = reference;
            while (currRef != null) {
                if (currRef.getHash() == hash) {
                    Entry<K, V> entry = currRef.get();
                    if (entry != null) {
                        K entryKey = entry.getKey();
                        if (ObjectUtils.nullSafeEquals(entryKey, key)) {
                            return currRef;
                        }
                    }
                }
                currRef = currRef.getNext();
            }
            return null;
        }

        @SuppressWarnings("unchecked")
        private Reference<K, V>[] createReferenceArray(int size) {
            return (Reference<K, V>[]) Array.newInstance(Reference.class, size);
        }

        private int getIndex(int hash, Reference<K, V>[] references) {
            return (hash & (references.length - 1));
        }

        /**
         * Replace the references with a new value, recalculating the resizeThreshold.
         * @param references the new references
         */
        private void setReferences(Reference<K, V>[] references) {
            this.references = references;
            this.resizeThreshold = (int) (references.length * getLoadFactor());
        }

        /**
         * Return the size of the current references array.
         */
        public final int getSize() {
            return this.references.length;
        }

        /**
         * Return the total number of references in this segment.
         */
        public final int getCount() {
            return this.count;
        }
    }

    /**
     * A reference to an {@link Entry} contained in the map. Implementations are usually
     * wrappers around specific Java reference implementations (e.g., {@link SoftReference}).
     */
    protected interface Reference<K, V> {

        /**
         * Return the referenced entry, or {@code null} if the entry is no longer available.
         */
        Entry<K, V> get();

        /**
         * Return the hash for the reference.
         */
        int getHash();

        /**
         * Return the next reference in the chain, or {@code null} if none.
         */
        Reference<K, V> getNext();

        /**
         * Release this entry and ensure that it will be returned from
         * {@code ReferenceManager#pollForPurge()}.
         */
        void release();
    }

    /**
     * A single map entry.
     */
    protected static final class Entry<K, V> implements Map.Entry<K, V> {

        private final K key;

        private volatile V value;

        public Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }

        @Override
        public K getKey() {
            return this.key;
        }

        @Override
        public V getValue() {
            return this.value;
        }

        @Override
        public V setValue(V value) {
            V previous = this.value;
            this.value = value;
            return previous;
        }

        @Override
        public String toString() {
            return (this.key + "=" + this.value);
        }

        @Override
        @SuppressWarnings("rawtypes")
        public final boolean equals(Object other) {
            if (this == other) {
                return true;
            }
            if (!(other instanceof Map.Entry)) {
                return false;
            }
            Map.Entry otherEntry = (Map.Entry) other;
            return (ObjectUtils.nullSafeEquals(getKey(), otherEntry.getKey()) &&
                    ObjectUtils.nullSafeEquals(getValue(), otherEntry.getValue()));
        }

        @Override
        public final int hashCode() {
            return (ObjectUtils.nullSafeHashCode(this.key) ^ ObjectUtils.nullSafeHashCode(this.value));
        }
    }

    /**
     * A task that can be {@link Segment#doTask run} against a {@link Segment}.
     */
    private abstract class Task<T> {

        private final EnumSet<TaskOption> options;

        public Task(TaskOption... options) {
            this.options = (options.length == 0 ? EnumSet.noneOf(TaskOption.class) : EnumSet.of(options[0], options));
        }

        public boolean hasOption(TaskOption option) {
            return this.options.contains(option);
        }

        /**
         * Execute the task.
         * @param reference the found reference or {@code null}
         * @param entry the found entry or {@code null}
         * @param entries access to the underlying entries
         * @return the result of the task
         * @see #execute(Reference, Entry)
         */
        protected T execute(Reference<K, V> reference, Entry<K, V> entry, Entries entries) {
            return execute(reference, entry);
        }

        /**
         * Convenience method that can be used for tasks that do not need access to {@link Entries}.
         * @param reference the found reference or {@code null}
         * @param entry the found entry or {@code null}
         * @return the result of the task
         * @see #execute(Reference, Entry, Entries)
         */
        protected T execute(Reference<K, V> reference, Entry<K, V> entry) {
            return null;
        }
    }

    /**
     * Various options supported by a {@code Task}.
     */
    private enum TaskOption {

        RESTRUCTURE_BEFORE, RESTRUCTURE_AFTER, SKIP_IF_EMPTY, RESIZE
    }

    /**
     * Allows a task access to {@link Segment} entries.
     */
    private abstract class Entries {

        /**
         * Add a new entry with the specified value.
         * @param value the value to add
         */
        public abstract void add(V value);
    }

    /**
     * Internal entry-set implementation.
     */
    private class EntrySet extends AbstractSet<Map.Entry<K, V>> {

        @Override
        public Iterator<Map.Entry<K, V>> iterator() {
            return new EntryIterator();
        }

        @Override
        public boolean contains(Object o) {
            if (o != null && o instanceof Map.Entry<?, ?>) {
                Map.Entry<?, ?> entry = (java.util.Map.Entry<?, ?>) o;
                Reference<K, V> reference = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER);
                Entry<K, V> other = (reference != null ? reference.get() : null);
                if (other != null) {
                    return ObjectUtils.nullSafeEquals(entry.getValue(), other.getValue());
                }
            }
            return false;
        }

        @Override
        public boolean remove(Object o) {
            if (o instanceof Map.Entry<?, ?>) {
                Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
                return ConcurrentReferenceHashMap.this.remove(entry.getKey(), entry.getValue());
            }
            return false;
        }

        @Override
        public int size() {
            return ConcurrentReferenceHashMap.this.size();
        }

        @Override
        public void clear() {
            ConcurrentReferenceHashMap.this.clear();
        }
    }

    /**
     * Internal entry iterator implementation.
     */
    private class EntryIterator implements Iterator<Map.Entry<K, V>> {

        private int segmentIndex;

        private int referenceIndex;

        private Reference<K, V>[] references;

        private Reference<K, V> reference;

        private Entry<K, V> next;

        private Entry<K, V> last;

        public EntryIterator() {
            moveToNextSegment();
        }

        @Override
        public boolean hasNext() {
            getNextIfNecessary();
            return (this.next != null);
        }

        @Override
        public Entry<K, V> next() {
            getNextIfNecessary();
            if (this.next == null) {
                throw new NoSuchElementException();
            }
            this.last = this.next;
            this.next = null;
            return this.last;
        }

        private void getNextIfNecessary() {
            while (this.next == null) {
                moveToNextReference();
                if (this.reference == null) {
                    return;
                }
                this.next = this.reference.get();
            }
        }

        private void moveToNextReference() {
            if (this.reference != null) {
                this.reference = this.reference.getNext();
            }
            while (this.reference == null && this.references != null) {
                if (this.referenceIndex >= this.references.length) {
                    moveToNextSegment();
                    this.referenceIndex = 0;
                }
                else {
                    this.reference = this.references[this.referenceIndex];
                    this.referenceIndex++;
                }
            }
        }

        private void moveToNextSegment() {
            this.reference = null;
            this.references = null;
            if (this.segmentIndex < ConcurrentReferenceHashMap.this.segments.length) {
                this.references = ConcurrentReferenceHashMap.this.segments[this.segmentIndex].references;
                this.segmentIndex++;
            }
        }

        @Override
        public void remove() {
            Assert.state(this.last != null, "No element to remove");
            ConcurrentReferenceHashMap.this.remove(this.last.getKey());
        }
    }

    /**
     * The types of restructuring that can be performed.
     */
    protected enum Restructure {

        WHEN_NECESSARY, NEVER
    }

    /**
     * Strategy class used to manage {@link Reference}s. This class can be overridden if
     * alternative reference types need to be supported.
     */
    protected class ReferenceManager {

        private final ReferenceQueue<Entry<K, V>> queue = new ReferenceQueue<Entry<K, V>>();

        /**
         * Factory method used to create a new {@link Reference}.
         * @param entry the entry contained in the reference
         * @param hash the hash
         * @param next the next reference in the chain, or {@code null} if none
         * @return a new {@link Reference}
         */
        public Reference<K, V> createReference(Entry<K, V> entry, int hash, Reference<K, V> next) {
            if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) {
                return new WeakEntryReference<K, V>(entry, hash, next, this.queue);
            }
            return new SoftEntryReference<K, V>(entry, hash, next, this.queue);
        }

        /**
         * Return any reference that has been garbage collected and can be purged from the
         * underlying structure or {@code null} if no references need purging. This
         * method must be thread safe and ideally should not block when returning
         * {@code null}. References should be returned once and only once.
         * @return a reference to purge or {@code null}
         */
        @SuppressWarnings("unchecked")
        public Reference<K, V> pollForPurge() {
            return (Reference<K, V>) this.queue.poll();
        }
    }

    /**
     * Internal {@link Reference} implementation for {@link SoftReference}s.
     */
    private static final class SoftEntryReference<K, V> extends SoftReference<Entry<K, V>> implements Reference<K, V> {

        private final int hash;

        private final Reference<K, V> nextReference;

        public SoftEntryReference(Entry<K, V> entry, int hash, Reference<K, V> next, ReferenceQueue<Entry<K, V>> queue) {
            super(entry, queue);
            this.hash = hash;
            this.nextReference = next;
        }

        @Override
        public int getHash() {
            return this.hash;
        }

        @Override
        public Reference<K, V> getNext() {
            return this.nextReference;
        }

        @Override
        public void release() {
            enqueue();
            clear();
        }
    }

    /**
     * Internal {@link Reference} implementation for {@link WeakReference}s.
     */
    private static final class WeakEntryReference<K, V> extends WeakReference<Entry<K, V>> implements Reference<K, V> {

        private final int hash;

        private final Reference<K, V> nextReference;

        public WeakEntryReference(Entry<K, V> entry, int hash, Reference<K, V> next, ReferenceQueue<Entry<K, V>> queue) {
            super(entry, queue);
            this.hash = hash;
            this.nextReference = next;
        }

        @Override
        public int getHash() {
            return this.hash;
        }

        @Override
        public Reference<K, V> getNext() {
            return this.nextReference;
        }

        @Override
        public void release() {
            enqueue();
            clear();
        }
    }

}

redpajama_set_name: RedPajamaGithub


text:
This plugin is designed to scrape reviews and review data from Vivino
as they currently lack any form of API. All this plugin does is leverage
[Simple HTML DOM Parser](https://github.com/sunra/php-simple-html-dom-parser)
to extract data from Vivino. It does **not** provide any front-end and exists
solely to provide you with a clean array of data to manipulate.

This plugin has been tested to scrape a handful of wineries /wines/ pages without issue, though if you do encounter any issues feel free to open a new Github Issue.

# Important note about deployments
As this plugin leverages composer and has the `/vendor` directory excluded via the gitignore file, it is possible that if you use git-based deployments/builds you will receive some sort of error getting the plugin to deploy. It is recommended that you run `composer update` on deployment and/or add `composer update` as a build-step in your automated deployment process.

## Requirements
* PHP >= 5.6 (Tested on 5.6 / 7, may work on others)
* php-curl (for Simple DOM Parser)
* A WordPress installation
* [Composer](https://getcomposer.org/) (to install plugin dependencies)

## Installation
* Clone this repo into your plugins directory (`git clone git@github.com:Radau/WordPress-Vivino-Reviews-Scraper.git cc-vivino-review-scraper`), then run `composer update` inside of
the newly downloaded plugin folder.
* Activate the plugin via the WordPress plugins menu.
* Go to `Settings > Vivino Reviews` and fill out your options data.

## How does it work?
#### Cron
This plugin leverages WP-Cron which triggers every hour provided there is a visitor, which allows for a non-blocking way to scrape the data (so your visitors don't end up waiting 20 seconds to load a page). If you deactivate, then reactivate this plugin it will immediately attempt to scrape data with the options you had when it was deactivated.

#### Manually execute the scraping job
If for any reason you need to re-run the scraping job. Simply go to the options page and click "Save Changes". This will take anywhere from a second to a few minutes depending on how much data it will scrape, so be patient.

## Usage in templates, WordPress PHP in general.
This plugin serializes the scraped data into a WP Options field as JSON to maintain relationships. When attempting to access the data, simply use `json_decode(get_option('cc_vivino_review_reviews'))` to get the array of all of your data. From there, it is up to you to sort it out as you see fit. This plugin exists solely to get you the data.

## Loading into Timber
Under your `StarterSite extends TimberSite`, have a filter for `timber_context` that points to a method within the class that grabs the options data. The example below will let you access `{{ cc_vivino_reviews_data }}` within Twig templates in Timber. If you want a different name, simply change the context name.
```php
// Example
class StarterSite extends TimberSite {
    function __construct()
    {
        add_filter('timber_context', array( $this, 'add_to_context' )); // Make sure you have this...
        parent::__construct();
    }

    function add_to_context( $context )
    {
        $context['cc_vivino_reviews_data'] = json_decode(get_option('cc_vivino_review_reviews')); // And this
        return $context;
    }

}
```

<img src="http://i.imgur.com/4uwaf07.png">

## Examples
#### Pulling data into Twig Template (General layout applies for vanilla WordPress templates)
It's assumed you've done the above and loaded your data into Timber Context. In this situation I will have `$context['cc_vivino_reviews_data']`, so I will be calling `{{ cc_vivino_reviews_data }}`.

```php
{% if cc_vivino_reviews_data %}
    {% for wine in cc_vivino_reviews_data %}
        <div class="meta">
            <!-- Title/Link -->
            {% if wine.title_url and wine.title %}
                <h1><a href="{{ wine.title_url }}">{{ wine.title }}</a></h1>
            {% endif %}

            {% if wine.location_district or wine.location_country %}
                <!-- Location Data -->
                <div class="location">

                    <!-- Location District (State) and Link to Vivino page for location -->
                    {% if wine.location_district_url and wine.location_district %}
                        <a href="{{ wine.location_district_url }}">{{ wine.location_district }}</a>
                    {% endif %}

                    <!-- Location Country and Link to Vivino page for Country -->
                    {% if wine.location_country_url and wine.location_country %}
                        <a href="{{ wine.location_country_url }}">{{ wine.location_country }}</a>
                    {% endif %}

                </div>
            {% endif %}

            {% if wine.rating_average or wine.rating_count %}
                <!-- Ratings -->
                <div class="ratingInfo">

                    <!-- Rating Average -->
                    {% if wine.rating_average %}
                        <p>Rating Average: {{ wine.rating_average }}</p>
                    {% endif %}

                    <!-- Amount of Ratings -->
                    {% if wine.rating_count %}
                        <p>Amount of Ratings: {{ wine.rating_count }}</p>
                    {% endif %}

                </div>
            {% endif %}

            {% if wine.reviews %}
                <!-- Reviews -->
                <div class="reviews">

                    {% for review in wine.reviews %}

                        <!-- Rating -->
                        {% if review.rating %}
                            <p>{{ review.rating }}</p>
                        {% endif %}

                        <!-- Description -->
                        {% if review.description %}
                            <p>{{ review.description }}</p>
                        {% endif %}

                        <!-- Author -->
                        {% if review.author %}
                            <p>{{ review.author }}</p>
                        {% endif %}

                    {% endfor %}

                </div>
            {% endif %}

        </div>

    {% endfor %}
{% endif %}
```


text:
namespace {
struct ScopedICONINFO : ICONINFO {
  ScopedICONINFO() {
    hbmColor = NULL;
    hbmMask = NULL;
  }

  ~ScopedICONINFO() {
    if (hbmColor)
      ::DeleteObject(hbmColor);
    if (hbmMask)
      ::DeleteObject(hbmMask);
  }
};
}

// Defining the dimensions for the icon images. We store only one value because
// we always resize to a square image; that is, the value 48 means that we are
// going to resize the given bitmap to a 48 by 48 pixels bitmap.
//
// The icon images appear in the icon file in same order in which their
// corresponding dimensions appear in the |icon_dimensions_| array, so it is
// important to keep this array sorted. Also note that the maximum icon image
// size we can handle is 255 by 255.
const int IconUtil::icon_dimensions_[] = {
  8,    // Recommended by the MSDN as a nice to have icon size.
  10,   // Used by the Shell (e.g. for shortcuts).
  14,   // Recommended by the MSDN as a nice to have icon size.
  16,   // Toolbar, Application and Shell icon sizes.
  22,   // Recommended by the MSDN as a nice to have icon size.
  24,   // Used by the Shell (e.g. for shortcuts).
  32,   // Toolbar, Dialog and Wizard icon size.
  40,   // Quick Launch.
  48,   // Alt+Tab icon size.
  64,   // Recommended by the MSDN as a nice to have icon size.
  96,   // Recommended by the MSDN as a nice to have icon size.
  128   // Used by the Shell (e.g. for shortcuts).
};

HICON IconUtil::CreateHICONFromSkBitmap(const SkBitmap& bitmap) {
  // Only 32 bit ARGB bitmaps are supported. We also try to perform as many
  // validations as we can on the bitmap.
  SkAutoLockPixels bitmap_lock(bitmap);
  if ((bitmap.getConfig() != SkBitmap::kARGB_8888_Config) ||
      (bitmap.width() <= 0) || (bitmap.height() <= 0) ||
      (bitmap.getPixels() == NULL))
    return NULL;

  // We start by creating a DIB which we'll use later on in order to create
  // the HICON. We use BITMAPV5HEADER since the bitmap we are about to convert
  // may contain an alpha channel and the V5 header allows us to specify the
  // alpha mask for the DIB.
  BITMAPV5HEADER bitmap_header;
  InitializeBitmapHeader(&bitmap_header, bitmap.width(), bitmap.height());
  void* bits;
  HDC hdc = ::GetDC(NULL);
  HBITMAP dib;
  dib = ::CreateDIBSection(hdc, reinterpret_cast<BITMAPINFO*>(&bitmap_header),
                           DIB_RGB_COLORS, &bits, NULL, 0);
  DCHECK(dib);
  ::ReleaseDC(NULL, hdc);
  memcpy(bits, bitmap.getPixels(), bitmap.width() * bitmap.height() * 4);

  // Icons are generally created using an AND and XOR masks where the AND
  // specifies boolean transparency (the pixel is either opaque or
  // transparent) and the XOR mask contains the actual image pixels. If the XOR
  // mask bitmap has an alpha channel, the AND monochrome bitmap won't
  // actually be used for computing the pixel transparency. Even though all our
  // bitmap has an alpha channel, Windows might not agree when all alpha values
  // are zero. So the monochrome bitmap is created with all pixels transparent
  // for this case. Otherwise, it is created with all pixels opaque.
  bool bitmap_has_alpha_channel = PixelsHaveAlpha(
      static_cast<const uint32*>(bitmap.getPixels()),
      bitmap.width() * bitmap.height());

  scoped_array<uint8> mask_bits;
  if (!bitmap_has_alpha_channel) {
    // Bytes per line with paddings to make it word alignment.
    size_t bytes_per_line = (bitmap.width() + 0xF) / 16 * 2;
    size_t mask_bits_size = bytes_per_line * bitmap.height();

    mask_bits.reset(new uint8[mask_bits_size]);
    DCHECK(mask_bits.get());

    // Make all pixels transparent.
    memset(mask_bits.get(), 0xFF, mask_bits_size);
  }

  HBITMAP mono_bitmap = ::CreateBitmap(bitmap.width(), bitmap.height(), 1, 1,
      reinterpret_cast<LPVOID>(mask_bits.get()));
  DCHECK(mono_bitmap);

  ICONINFO icon_info;
  icon_info.fIcon = TRUE;
  icon_info.xHotspot = 0;
  icon_info.yHotspot = 0;
  icon_info.hbmMask = mono_bitmap;
  icon_info.hbmColor = dib;
  HICON icon = ::CreateIconIndirect(&icon_info);
  ::DeleteObject(dib);
  ::DeleteObject(mono_bitmap);
  return icon;
}

SkBitmap* IconUtil::CreateSkBitmapFromHICON(HICON icon, const gfx::Size& s) {
  // We start with validating parameters.
  if (!icon || s.IsEmpty())
    return NULL;
  ScopedICONINFO icon_info;
  if (!::GetIconInfo(icon, &icon_info))
    return NULL;
  if (!icon_info.fIcon)
    return NULL;
  return new SkBitmap(CreateSkBitmapFromHICONHelper(icon, s));
}

SkBitmap* IconUtil::CreateSkBitmapFromHICON(HICON icon) {
  // We start with validating parameters.
  if (!icon)
    return NULL;

  ScopedICONINFO icon_info;
  BITMAP bitmap_info = { 0 };

  if (!::GetIconInfo(icon, &icon_info))
    return NULL;

  if (!::GetObject(icon_info.hbmMask, sizeof(bitmap_info), &bitmap_info))
    return NULL;

  gfx::Size icon_size(bitmap_info.bmWidth, bitmap_info.bmHeight);
  return new SkBitmap(CreateSkBitmapFromHICONHelper(icon, icon_size));
}

SkBitmap IconUtil::CreateSkBitmapFromHICONHelper(HICON icon,
                                                 const gfx::Size& s) {
  DCHECK(icon);
  DCHECK(!s.IsEmpty());

  // Allocating memory for the SkBitmap object. We are going to create an ARGB
  // bitmap so we should set the configuration appropriately.
  SkBitmap bitmap;
  bitmap.setConfig(SkBitmap::kARGB_8888_Config, s.width(), s.height());
  bitmap.allocPixels();
  bitmap.eraseARGB(0, 0, 0, 0);
  SkAutoLockPixels bitmap_lock(bitmap);

  // Now we should create a DIB so that we can use ::DrawIconEx in order to
  // obtain the icon's image.
  BITMAPV5HEADER h;
  InitializeBitmapHeader(&h, s.width(), s.height());
  HDC hdc = ::GetDC(NULL);
  uint32* bits;
  HBITMAP dib = ::CreateDIBSection(hdc, reinterpret_cast<BITMAPINFO*>(&h),
      DIB_RGB_COLORS, reinterpret_cast<void**>(&bits), NULL, 0);
  DCHECK(dib);
  HDC dib_dc = CreateCompatibleDC(hdc);
  ::ReleaseDC(NULL, hdc);
  DCHECK(dib_dc);
  HGDIOBJ old_obj = ::SelectObject(dib_dc, dib);

  // Windows icons are defined using two different masks. The XOR mask, which
  // represents the icon image and an AND mask which is a monochrome bitmap
  // which indicates the transparency of each pixel.
  //
  // To make things more complex, the icon image itself can be an ARGB bitmap
  // and therefore contain an alpha channel which specifies the transparency
  // for each pixel. Unfortunately, there is no easy way to determine whether
  // or not a bitmap has an alpha channel and therefore constructing the bitmap
  // for the icon is nothing but straightforward.
  //
  // The idea is to read the AND mask but use it only if we know for sure that
  // the icon image does not have an alpha channel. The only way to tell if the
  // bitmap has an alpha channel is by looking through the pixels and checking
  // whether there are non-zero alpha bytes.
  //
  // We start by drawing the AND mask into our DIB.
  size_t num_pixels = s.GetArea();
  memset(bits, 0, num_pixels * 4);
  ::DrawIconEx(dib_dc, 0, 0, icon, s.width(), s.height(), 0, NULL, DI_MASK);

  // Capture boolean opacity. We may not use it if we find out the bitmap has
  // an alpha channel.
  bool* opaque = new bool[num_pixels];
  DCHECK(opaque);
  for (size_t i = 0; i < num_pixels; ++i)
    opaque[i] = !bits[i];

  // Then draw the image itself which is really the XOR mask.
  memset(bits, 0, num_pixels * 4);
  ::DrawIconEx(dib_dc, 0, 0, icon, s.width(), s.height(), 0, NULL, DI_NORMAL);
  memcpy(bitmap.getPixels(), static_cast<void*>(bits), num_pixels * 4);

  // Finding out whether the bitmap has an alpha channel.
  bool bitmap_has_alpha_channel = PixelsHaveAlpha(
      static_cast<const uint32*>(bitmap.getPixels()), num_pixels);

  // If the bitmap does not have an alpha channel, we need to build it using
  // the previously captured AND mask. Otherwise, we are done.
  if (!bitmap_has_alpha_channel) {
    uint32* p = static_cast<uint32*>(bitmap.getPixels());
    for (size_t i = 0; i < num_pixels; ++p, ++i) {
      DCHECK_EQ((*p & 0xff000000), 0u);
      if (opaque[i])
        *p |= 0xff000000;
      else
        *p &= 0x00ffffff;
    }
  }

  delete [] opaque;
  ::SelectObject(dib_dc, old_obj);
  ::DeleteObject(dib);
  ::DeleteDC(dib_dc);

  return bitmap;
}

bool IconUtil::CreateIconFileFromSkBitmap(const SkBitmap& bitmap,
                                          const FilePath& icon_path) {
  // Only 32 bit ARGB bitmaps are supported. We also make sure the bitmap has
  // been properly initialized.
  SkAutoLockPixels bitmap_lock(bitmap);
  if ((bitmap.getConfig() != SkBitmap::kARGB_8888_Config) ||
      (bitmap.height() <= 0) || (bitmap.width() <= 0) ||
      (bitmap.getPixels() == NULL))
    return false;

  // We start by creating the file.
  base::win::ScopedHandle icon_file(::CreateFile(icon_path.value().c_str(),
       GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL));

  if (!icon_file.IsValid())
    return false;

  // Creating a set of bitmaps corresponding to the icon images we'll end up
  // storing in the icon file. Each bitmap is created by resizing the given
  // bitmap to the desired size.
  std::vector<SkBitmap> bitmaps;
  CreateResizedBitmapSet(bitmap, &bitmaps);
  DCHECK(!bitmaps.empty());
  size_t bitmap_count = bitmaps.size();

  // Computing the total size of the buffer we need in order to store the
  // images in the desired icon format.
  size_t buffer_size = ComputeIconFileBufferSize(bitmaps);
  unsigned char* buffer = new unsigned char[buffer_size];
  DCHECK(buffer != NULL);
  memset(buffer, 0, buffer_size);

  // Setting the information in the structures residing within the buffer.
  // First, we set the information which doesn't require iterating through the
  // bitmap set and then we set the bitmap specific structures. In the latter
  // step we also copy the actual bits.
  ICONDIR* icon_dir = reinterpret_cast<ICONDIR*>(buffer);
  icon_dir->idType = kResourceTypeIcon;
  icon_dir->idCount = bitmap_count;
  size_t icon_dir_count = bitmap_count - 1;  // Note DCHECK(!bitmaps.empty())!
  size_t offset = sizeof(ICONDIR) + (sizeof(ICONDIRENTRY) * icon_dir_count);
  for (size_t i = 0; i < bitmap_count; i++) {
    ICONIMAGE* image = reinterpret_cast<ICONIMAGE*>(buffer + offset);
    DCHECK_LT(offset, buffer_size);
    size_t icon_image_size = 0;
    SetSingleIconImageInformation(bitmaps[i], i, icon_dir, image, offset,
                                  &icon_image_size);
    DCHECK_GT(icon_image_size, 0U);
    offset += icon_image_size;
  }
  DCHECK_EQ(offset, buffer_size);

  // Finally, writing the data info the file.
  DWORD bytes_written;
  bool delete_file = false;
  if (!WriteFile(icon_file.Get(), buffer, buffer_size, &bytes_written, NULL) ||
      bytes_written != buffer_size)
    delete_file = true;

  ::CloseHandle(icon_file.Take());
  delete [] buffer;
  if (delete_file) {
    bool success = file_util::Delete(icon_path, false);
    DCHECK(success);
  }

  return !delete_file;
}

bool IconUtil::PixelsHaveAlpha(const uint32* pixels, size_t num_pixels) {
  for (const uint32* end = pixels + num_pixels; pixels != end; ++pixels) {
    if ((*pixels & 0xff000000) != 0)
      return true;
  }

  return false;
}

void IconUtil::InitializeBitmapHeader(BITMAPV5HEADER* header, int width,
                                      int height) {
  DCHECK(header);
  memset(header, 0, sizeof(BITMAPV5HEADER));
  header->bV5Size = sizeof(BITMAPV5HEADER);

  // Note that icons are created using top-down DIBs so we must negate the
  // value used for the icon's height.
  header->bV5Width = width;
  header->bV5Height = -height;
  header->bV5Planes = 1;
  header->bV5Compression = BI_RGB;

  // Initializing the bitmap format to 32 bit ARGB.
  header->bV5BitCount = 32;
  header->bV5RedMask = 0x00FF0000;
  header->bV5GreenMask = 0x0000FF00;
  header->bV5BlueMask = 0x000000FF;
  header->bV5AlphaMask = 0xFF000000;

  // Use the system color space.  The default value is LCS_CALIBRATED_RGB, which
  // causes us to crash if we don't specify the approprite gammas, etc.  See
  // <http://msdn.microsoft.com/en-us/library/ms536531(VS.85).aspx> and
  // <http://b/1283121>.
  header->bV5CSType = LCS_WINDOWS_COLOR_SPACE;

  // Use a valid value for bV5Intent as 0 is not a valid one.
  // <http://msdn.microsoft.com/en-us/library/dd183381(VS.85).aspx>
  header->bV5Intent = LCS_GM_IMAGES;
}

void IconUtil::SetSingleIconImageInformation(const SkBitmap& bitmap,
                                             size_t index,
                                             ICONDIR* icon_dir,
                                             ICONIMAGE* icon_image,
                                             size_t image_offset,
                                             size_t* image_byte_count) {
  DCHECK(icon_dir != NULL);
  DCHECK(icon_image != NULL);
  DCHECK_GT(image_offset, 0U);
  DCHECK(image_byte_count != NULL);

  // We start by computing certain image values we'll use later on.
  size_t xor_mask_size, bytes_in_resource;
  ComputeBitmapSizeComponents(bitmap,
                              &xor_mask_size,
                              &bytes_in_resource);

  icon_dir->idEntries[index].bWidth = static_cast<BYTE>(bitmap.width());
  icon_dir->idEntries[index].bHeight = static_cast<BYTE>(bitmap.height());
  icon_dir->idEntries[index].wPlanes = 1;
  icon_dir->idEntries[index].wBitCount = 32;
  icon_dir->idEntries[index].dwBytesInRes = bytes_in_resource;
  icon_dir->idEntries[index].dwImageOffset = image_offset;
  icon_image->icHeader.biSize = sizeof(BITMAPINFOHEADER);

  // The width field in the BITMAPINFOHEADER structure accounts for the height
  // of both the AND mask and the XOR mask so we need to multiply the bitmap's
  // height by 2. The same does NOT apply to the width field.
  icon_image->icHeader.biHeight = bitmap.height() * 2;
  icon_image->icHeader.biWidth = bitmap.width();
  icon_image->icHeader.biPlanes = 1;
  icon_image->icHeader.biBitCount = 32;

  // We use a helper function for copying to actual bits from the SkBitmap
  // object into the appropriate space in the buffer. We use a helper function
  // (rather than just copying the bits) because there is no way to specify the
  // orientation (bottom-up vs. top-down) of a bitmap residing in a .ico file.
  // Thus, if we just copy the bits, we'll end up with a bottom up bitmap in
  // the .ico file which will result in the icon being displayed upside down.
  // The helper function copies the image into the buffer one scanline at a
  // time.
  //
  // Note that we don't need to initialize the AND mask since the memory
  // allocated for the icon data buffer was initialized to zero. The icon we
  // create will therefore use an AND mask containing only zeros, which is OK
  // because the underlying image has an alpha channel. An AND mask containing
  // only zeros essentially means we'll initially treat all the pixels as
  // opaque.
  unsigned char* image_addr = reinterpret_cast<unsigned char*>(icon_image);
  unsigned char* xor_mask_addr = image_addr + sizeof(BITMAPINFOHEADER);
  CopySkBitmapBitsIntoIconBuffer(bitmap, xor_mask_addr, xor_mask_size);
  *image_byte_count = bytes_in_resource;
}

void IconUtil::CopySkBitmapBitsIntoIconBuffer(const SkBitmap& bitmap,
                                              unsigned char* buffer,
                                              size_t buffer_size) {
  SkAutoLockPixels bitmap_lock(bitmap);
  unsigned char* bitmap_ptr = static_cast<unsigned char*>(bitmap.getPixels());
  size_t bitmap_size = bitmap.height() * bitmap.width() * 4;
  DCHECK_EQ(buffer_size, bitmap_size);
  for (size_t i = 0; i < bitmap_size; i += bitmap.width() * 4) {
    memcpy(buffer + bitmap_size - bitmap.width() * 4 - i,
           bitmap_ptr + i,
           bitmap.width() * 4);
  }
}

void IconUtil::CreateResizedBitmapSet(const SkBitmap& bitmap_to_resize,
                                      std::vector<SkBitmap>* bitmaps) {
  DCHECK(bitmaps != NULL);
  DCHECK(bitmaps->empty());

  bool inserted_original_bitmap = false;
  for (size_t i = 0; i < arraysize(icon_dimensions_); i++) {
    // If the dimensions of the bitmap we are resizing are the same as the
    // current dimensions, then we should insert the bitmap and not a resized
    // bitmap. If the bitmap's dimensions are smaller, we insert our bitmap
    // first so that the bitmaps we return in the vector are sorted based on
    // their dimensions.
    if (!inserted_original_bitmap) {
      if ((bitmap_to_resize.width() == icon_dimensions_[i]) &&
          (bitmap_to_resize.height() == icon_dimensions_[i])) {
        bitmaps->push_back(bitmap_to_resize);
        inserted_original_bitmap = true;
        continue;
      }

      if ((bitmap_to_resize.width() < icon_dimensions_[i]) &&
          (bitmap_to_resize.height() < icon_dimensions_[i])) {
        bitmaps->push_back(bitmap_to_resize);
        inserted_original_bitmap = true;
      }
    }
    bitmaps->push_back(skia::ImageOperations::Resize(
        bitmap_to_resize, skia::ImageOperations::RESIZE_LANCZOS3,
        icon_dimensions_[i], icon_dimensions_[i]));
  }

  if (!inserted_original_bitmap)
    bitmaps->push_back(bitmap_to_resize);
}

size_t IconUtil::ComputeIconFileBufferSize(const std::vector<SkBitmap>& set) {
  DCHECK(!set.empty());

  // We start by counting the bytes for the structures that don't depend on the
  // number of icon images. Note that sizeof(ICONDIR) already accounts for a
  // single ICONDIRENTRY structure, which is why we subtract one from the
  // number of bitmaps.
  size_t total_buffer_size = sizeof(ICONDIR);
  size_t bitmap_count = set.size();
  total_buffer_size += sizeof(ICONDIRENTRY) * (bitmap_count - 1);
  DCHECK_GE(bitmap_count, arraysize(icon_dimensions_));

  // Add the bitmap specific structure sizes.
  for (size_t i = 0; i < bitmap_count; i++) {
    size_t xor_mask_size, bytes_in_resource;
    ComputeBitmapSizeComponents(set[i],
                                &xor_mask_size,
                                &bytes_in_resource);
    total_buffer_size += bytes_in_resource;
  }
  return total_buffer_size;
}

void IconUtil::ComputeBitmapSizeComponents(const SkBitmap& bitmap,
                                           size_t* xor_mask_size,
                                           size_t* bytes_in_resource) {
  // The XOR mask size is easy to calculate since we only deal with 32bpp
  // images.
  *xor_mask_size = bitmap.width() * bitmap.height() * 4;

  // Computing the AND mask is a little trickier since it is a monochrome
  // bitmap (regardless of the number of bits per pixels used in the XOR mask).
  // There are two things we must make sure we do when computing the AND mask
  // size:
  //
  // 1. Make sure the right number of bytes is allocated for each AND mask
  //    scan line in case the number of pixels in the image is not divisible by
  //    8. For example, in a 15X15 image, 15 / 8 is one byte short of
  //    containing the number of bits we need in order to describe a single
  //    image scan line so we need to add a byte. Thus, we need 2 bytes instead
  //    of 1 for each scan line.
  //
  // 2. Make sure each scan line in the AND mask is 4 byte aligned (so that the
  //    total icon image has a 4 byte alignment). In the 15X15 image example
  //    above, we can not use 2 bytes so we increase it to the next multiple of
  //    4 which is 4.
  //
  // Once we compute the size for a singe AND mask scan line, we multiply that
  // number by the image height in order to get the total number of bytes for
  // the AND mask. Thus, for a 15X15 image, we need 15 * 4 which is 60 bytes
  // for the monochrome bitmap representing the AND mask.
  size_t and_line_length = (bitmap.width() + 7) >> 3;
  and_line_length = (and_line_length + 3) & ~3;
  size_t and_mask_size = and_line_length * bitmap.height();
  size_t masks_size = *xor_mask_size + and_mask_size;
  *bytes_in_resource = masks_size + sizeof(BITMAPINFOHEADER);
}


text:
<!DOCTYPE html>

  <meta charset="utf-8">
  <title>Riot App Example 01</title>
  <link rel="stylesheet" href="//cdn.jsdelivr.net/semantic-ui/2/semantic.min.css">

  <app/>


text:
using System;
using System.Linq;
using UnityEngine;

namespace FarrokhGames.Inventory
{
    public class InventoryManager : IInventoryManager
    {
        private Vector2Int _size = Vector2Int.one;
        private IInventoryProvider _provider;
        private Rect _fullRect;

        public InventoryManager(IInventoryProvider provider, int width, int height)
        {
            _provider = provider;
            Rebuild();
            Resize(width, height);
        }

        /// <inheritdoc />
        public int width => _size.x;

        /// <inheritdoc />
        public int height => _size.y;

        /// <inheritdoc />
        public void Resize(int newWidth, int newHeight)
        {
            _size.x = newWidth;
            _size.y = newHeight;
            RebuildRect();
        }

        private void RebuildRect()
        {
            _fullRect = new Rect(0, 0, _size.x, _size.y);
            HandleSizeChanged();
            onResized?.Invoke();
        }

        private void HandleSizeChanged()
        {
            // Drop all items that no longer fit the inventory
            for (int i = 0; i < allItems.Length;)
            {
                var item = allItems[i];
                var shouldBeDropped = false;
                var padding = Vector2.one * 0.01f;

                if (!_fullRect.Contains(item.GetMinPoint() + padding) || !_fullRect.Contains(item.GetMaxPoint() - padding))
                {
                    shouldBeDropped = true;
                }

                if (shouldBeDropped)
                {
                    TryDrop(item);
                }
                else
                {
                    i++;
                }
            }
        }

        /// <inheritdoc />
        public void Rebuild()
        {
            Rebuild(false);
        }

        private void Rebuild(bool silent)
        {
            allItems = new IInventoryItem[_provider.inventoryItemCount];
            for (var i = 0; i < _provider.inventoryItemCount; i++)
            {
                allItems[i] = _provider.GetInventoryItem(i);
            }
            if (!silent)onRebuilt?.Invoke();
        }

        public void Dispose()
        {
            _provider = null;
            allItems = null;
        }

        /// <inheritdoc />
        public bool isFull
        {
            get
            {
                if (_provider.isInventoryFull)return true;

                for (var x = 0; x < width; x++)
                {
                    for (var y = 0; y < height; y++)
                    {
                        if (GetAtPoint(new Vector2Int(x, y)) == null) { return false; }
                    }
                }
                return true;
            }
        }

        /// <inheritdoc />
        public IInventoryItem[] allItems { get; private set; }

        /// <inheritdoc />
        public Action onRebuilt { get; set; }

        /// <inheritdoc />
        public Action<IInventoryItem> onItemDropped { get; set; }

        /// <inheritdoc />
        public Action<IInventoryItem> onItemDroppedFailed { get; set; }

        /// <inheritdoc />
        public Action<IInventoryItem> onItemAdded { get; set; }

        /// <inheritdoc />
        public Action<IInventoryItem> onItemAddedFailed { get; set; }

        /// <inheritdoc />
        public Action<IInventoryItem> onItemRemoved { get; set; }

        /// <inheritdoc />
        public Action onResized { get; set; }

        /// <inheritdoc />
        public IInventoryItem GetAtPoint(Vector2Int point)
        {
            // Single item override
            if (_provider.inventoryRenderMode == InventoryRenderMode.Single && _provider.isInventoryFull && allItems.Length > 0)
            {
                return allItems[0];
            }

            foreach (var item in allItems)
            {
                if (item.Contains(point)) { return item; }
            }
            return null;
        }

        /// <inheritdoc />
        public IInventoryItem[] GetAtPoint(Vector2Int point, Vector2Int size)
        {
            var posibleItems = new IInventoryItem[size.x * size.y];
            var c = 0;
            for (var x = 0; x < size.x; x++)
            {
                for (var y = 0; y < size.y; y++)
                {
                    posibleItems[c] = GetAtPoint(point + new Vector2Int(x, y));
                    c++;
                }
            }
            return posibleItems.Distinct().Where(x => x != null).ToArray();
        }

        /// <inheritdoc />
        public bool TryRemove(IInventoryItem item)
        {
            if (!CanRemove(item)) return false;
            if (!_provider.RemoveInventoryItem(item)) return false;
            Rebuild(true);
            onItemRemoved?.Invoke(item);
            return true;
        }

        /// <inheritdoc />
        public bool TryDrop(IInventoryItem item)
        {
            if (!CanDrop(item) || !_provider.DropInventoryItem(item))
			{
				onItemDroppedFailed?.Invoke(item);
				return false;
			}
            Rebuild(true);
            onItemDropped?.Invoke(item);
            return true;
        }

		internal bool TryForceDrop(IInventoryItem item)
		{
			if(!item.canDrop)
			{
				onItemDroppedFailed?.Invoke(item);
				return false;
			}
			onItemDropped?.Invoke(item);
			return true;
		}

        /// <inheritdoc />
        public bool CanAddAt(IInventoryItem item, Vector2Int point)
        {
            if (!_provider.CanAddInventoryItem(item) || _provider.isInventoryFull)
            {
                return false;
            }

            if (_provider.inventoryRenderMode == InventoryRenderMode.Single)
            {
                return true;
            }

            var previousPoint = item.position;
            item.position = point;
            var padding = Vector2.one * 0.01f;

            // Check if item is outside of inventory
            if (!_fullRect.Contains(item.GetMinPoint() + padding) || !_fullRect.Contains(item.GetMaxPoint() - padding))
            {
                item.position = previousPoint;
                return false;
            }

            // Check if item overlaps another item already in the inventory
            if (!allItems.Any(otherItem => item.Overlaps(otherItem))) return true; // Item can be added
            item.position = previousPoint;
            return false;

        }

        /// <inheritdoc />
        public bool TryAddAt(IInventoryItem item, Vector2Int point)
        {
            if (!CanAddAt(item, point) || !_provider.AddInventoryItem(item))
			{
				onItemAddedFailed?.Invoke(item);
				return false;
			}
            switch (_provider.inventoryRenderMode)
            {
                case InventoryRenderMode.Single:
                    item.position = GetCenterPosition(item);
                    break;
                case InventoryRenderMode.Grid:
                    item.position = point;
                    break;
                default:
                    throw new NotImplementedException($"InventoryRenderMode.{_provider.inventoryRenderMode.ToString()} have not yet been implemented");
            }
            Rebuild(true);
            onItemAdded?.Invoke(item);
            return true;
        }

        /// <inheritdoc />
        public bool CanAdd(IInventoryItem item)
        {
            Vector2Int point;
            if (!Contains(item) && GetFirstPointThatFitsItem(item, out point))
            {
                return CanAddAt(item, point);
            }
            return false;
        }

        /// <inheritdoc />
        public bool TryAdd(IInventoryItem item)
        {
            if (!CanAdd(item))return false;
            Vector2Int point;
            return GetFirstPointThatFitsItem(item, out point) && TryAddAt(item, point);
        }

        /// <inheritdoc />
        public bool CanSwap(IInventoryItem item)
        {
            return _provider.inventoryRenderMode == InventoryRenderMode.Single &&
                DoesItemFit(item) &&
                _provider.CanAddInventoryItem(item);
        }

        /// <inheritdoc />
        public void DropAll()
        {
            var itemsToDrop = allItems.ToArray();
            foreach (var item in itemsToDrop)
            {
                TryDrop(item);
            }
        }

        /// <inheritdoc />
        public void Clear()
        {
            foreach (var item in allItems)
            {
                TryRemove(item);
            }
        }

        /// <inheritdoc />
        public bool Contains(IInventoryItem item) => allItems.Contains(item);

        /// <inheritdoc />
        public bool CanRemove(IInventoryItem item) => Contains(item) && _provider.CanRemoveInventoryItem(item);

        /// <inheritdoc />
        public bool CanDrop(IInventoryItem item) => Contains(item) && _provider.CanDropInventoryItem(item) && item.canDrop;

        /*
         * Get first free point that will fit the given item
         */
        private bool GetFirstPointThatFitsItem(IInventoryItem item, out Vector2Int point)
        {
            if (DoesItemFit(item))
            {
                for (var x = 0; x < width - (item.width - 1); x++)
                {
                    for (var y = 0; y < height - (item.height - 1); y++)
                    {
                        point = new Vector2Int(x, y);
                        if (CanAddAt(item, point))return true;
                    }
                }
            }
            point = Vector2Int.zero;
            return false;
        }

        /*
         * Returns true if given items physically fits within this inventory
         */
        private bool DoesItemFit(IInventoryItem item) => item.width <= width && item.height <= height;

        /*
         * Returns the center post position for a given item within this inventory
         */
        private Vector2Int GetCenterPosition(IInventoryItem item)
        {
            return new Vector2Int(
                (_size.x - item.width) / 2,
                (_size.y - item.height) / 2
            );
        }
    }
}


text:
//******************************************************************************************************
//  LocalInputAdapter.cs - Gbtc
//
//  Copyright © 2015, Grid Protection Alliance.  All Rights Reserved.
//
//  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
//  the NOTICE file distributed with this work for additional information regarding copyright ownership.
//  The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
//  not use this file except in compliance with the License. You may obtain a copy of the License at:
//
//      http://www.opensource.org/licenses/MIT
//
//  Unless agreed to in writing, the subject software distributed under the License is distributed on an
//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
//  License for the specific language governing permissions and limitations.
//
//  Code Modification History:
//  ----------------------------------------------------------------------------------------------------
//  09/23/2015 - J. Ritchie Carroll
//       Generated original version of source code.
//
//******************************************************************************************************

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Timers;
using GSF;
using GSF.Collections;
using GSF.Diagnostics;
using GSF.TimeSeries;
using GSF.TimeSeries.Adapters;
using Timer = System.Timers.Timer;

namespace openHistorian.Adapters
{
    ///
    /// Represents an output adapter that publishes measurements to openHistorian for archival.
    ///
    [Description("openHistorian 2.0 (Local): reads data from a local openHistorian for replay.")]
    public class LocalInputAdapter : InputAdapterBase
    {
        #region [ Members ]

        // Constants
        private const long DefaultPublicationInterval = Ticks.PerSecond;

        // Fields
        private Timer m_readTimer;
        private string m_historianServer;
        private string m_instanceName;
        private Connection m_archiveReader;
        private IEnumerator<IMeasurement> m_dataReader;
        private long m_publicationTime;
        private int[] m_historianIDs;
        private DateTime m_startTime;
        private DateTime m_stopTime;
        private Ticks m_currentTimeOffset;
        private bool m_disposed;

        #endregion

        #region [ Constructors ]

        ///
        /// Creates a new instance of the <see cref="LocalInputAdapter"/>.
        ///
        public LocalInputAdapter()
        {
            m_historianServer = "127.0.0.1";

            // Setup a read timer
            m_readTimer = new Timer();
            m_readTimer.Elapsed += m_readTimer_Elapsed;
        }

        #endregion

        #region [ Properties ]

        ///
        /// Gets or sets instance name defined for this <see cref="LocalInputAdapter"/>.
        ///
        [ConnectionStringParameter,
        Description("Define the instance name the archive to read. Leave this value blank to default to the adapter name."),
        DefaultValue("")]
        public string InstanceName
        {
            get
            {
                if (string.IsNullOrEmpty(m_instanceName))
                    return Name.ToLower();

                return m_instanceName;
            }
            set => m_instanceName = value;
        }

        ///
        /// Gets or sets archive path for this <see cref="LocalInputAdapter"/>.
        ///
        [ConnectionStringParameter,
        Description("Define the historian server connection string, e.g., \"192.168.1.10\" or \"myhistorian:38402\"."),
        DefaultValue("127.0.0.1")]
        public string HistorianServer
        {
            get => m_historianServer;
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                    throw new ArgumentNullException(nameof(value), "The historianServer setting must be specified.");

                m_historianServer = value;
            }
        }

        ///
        /// Gets or sets the publication interval for this <see cref="LocalInputAdapter"/>.
        ///
        [ConnectionStringParameter,
        Description("Define the publication time interval in 100-nanosecond tick intervals for reading historical data."),
        DefaultValue(DefaultPublicationInterval)]
        public long PublicationInterval { get; set; }

        ///
        /// Gets the start time for reading data.
        ///
        [ConnectionStringParameter,
        Description("Define the start time for reading data into real-time session, or do not define to start reading from the beginning of the available data. Either StartTimeConstraint or StopTimeConstraint must be defined in order to start reading data into real-time session. Value should not be defined when using adapter for subscription based temporal session support."),
        DefaultValue("")]
        public new string StartTimeConstraint { get; set; }

        ///
        /// Gets the stop time for reading data.
        ///
        [ConnectionStringParameter,
        Description("Define the stop time for reading data into real-time session, or do not define to keep reading until the end of the available data. Either StartTimeConstraint or StopTimeConstraint must be defined in order to start reading data into real-time session. Value should not be defined when using adapter for subscription based temporal session support."),
        DefaultValue("")]
        public new string StopTimeConstraint { get; set; }

        ///
        /// Gets or sets a value that determines whether timestamps are
        /// simulated for the purposes of real-time concentration.
        ///
        [ConnectionStringParameter,
        Description("Indicate whether timestamps are simulated for real-time concentration."),
        DefaultValue(false)]
        public bool SimulateTimestamp { get; set; }

        ///
        /// Gets or sets value that determines if the input data should be replayed repeatedly.
        ///
        [ConnectionStringParameter,
        Description("Define if the input data should be replayed repeatedly."),
        DefaultValue(false)]
        public bool AutoRepeat { get; set; }

        ///
        /// Gets the flag indicating if this adapter supports temporal processing.
        ///
        public override bool SupportsTemporalProcessing
        {
            get
            {
                // If the start/time constraints are defined in the connection string, it is expected that this adapter
                // will be used in real-time. For temporal sessions these properties will be defined via method call to
                // the SetTemporalConstraint function.
                Dictionary<string, string> settings = Settings;
                return !(settings.ContainsKey("startTimeConstraint") || settings.ContainsKey("stopTimeConstraint"));
            }
        }

        ///
        /// Gets or sets the desired processing interval, in milliseconds, for the adapter.
        ///
        /// <remarks>
        /// With the exception of the values of -1 and 0, this value specifies the desired processing interval for data, i.e.,
        /// basically a delay, or timer interval, over which to process data. A value of -1 means to use the default processing
        /// interval while a value of 0 means to process data as fast as possible.
        /// </remarks>
        public override int ProcessingInterval
        {
            get => base.ProcessingInterval;
            set
            {
                base.ProcessingInterval = value;

                // Set read timer interval to the requested processing interval
                m_readTimer.Interval = value <= 0 ? 1 : value;
            }
        }

        ///
        /// Gets flag that determines if this <see cref="LocalInputAdapter"/> uses an asynchronous connection.
        ///
        protected override bool UseAsyncConnect => false;

        ///
        /// Returns the detailed status of the data input source.
        ///
        public override string Status
        {
            get
            {
                StringBuilder status = new StringBuilder();
                status.Append(base.Status);

                status.AppendFormat("             Instance name: {0}\r\n", InstanceName);
                status.AppendFormat("          Historian server: {0}\r\n", HistorianServer);
                status.AppendFormat("      Publication interval: {0}\r\n", PublicationInterval);
                status.AppendFormat("               Auto-repeat: {0}\r\n", AutoRepeat);
                status.AppendFormat("            Start time-tag: {0:yyyy-MM-dd HH:mm:ss.fff}\r\n", StartTime);
                status.AppendFormat("             Stop time-tag: {0:yyyy-MM-dd HH:mm:ss.fff}\r\n", StopTime);
                status.AppendFormat("        Simulate timestamp: {0}\r\n", SimulateTimestamp);

                return status.ToString();
            }
        }

        #endregion

        #region [ Methods ]

        ///
        /// Releases the unmanaged resources used by the <see cref="LocalInputAdapter"/> object and optionally releases the managed resources.
        ///
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            if (!m_disposed)
            {
                try
                {
                    if (disposing)
                    {
                        if (m_readTimer != null)
                        {
                            m_readTimer.Elapsed -= m_readTimer_Elapsed;
                            m_readTimer.Dispose();
                            m_readTimer = null;
                        }

                        if (m_archiveReader != null)
                        {
                            m_archiveReader.Dispose();
                            m_archiveReader = null;
                        }
                    }
                }
                finally
                {
                    m_disposed = true;          // Prevent duplicate dispose.
                    base.Dispose(disposing);    // Call base class Dispose().
                }
            }
        }

        ///
        /// Initializes this <see cref="LocalInputAdapter"/>.
        ///
        /// <exception cref="ArgumentException">HistorianID, Server, Port, Protocol, or InitiateConnection is missing from the <see cref="AdapterBase.Settings"/>.</exception>
        public override void Initialize()
        {
            const string errorMessage = "{0} is missing from settings - Example: instanceName=PPA; publicationInterval=333333";

            base.Initialize();

            Dictionary<string, string> settings = Settings;

            // Validate settings.
            settings.TryGetValue("instanceName", out string setting);
            InstanceName = setting;

            if ((OutputSourceIDs is null || OutputSourceIDs.Length == 0) && string.IsNullOrEmpty(InstanceName))
                throw new ArgumentException(string.Format(errorMessage, "instanceName"));

            if (settings.TryGetValue("historianServer", out setting))
                HistorianServer = setting;

            if (settings.TryGetValue("publicationInterval", out setting) && long.TryParse(setting, out long value))
                PublicationInterval = value;
            else
                PublicationInterval = DefaultPublicationInterval;

            if (settings.TryGetValue("simulateTimestamp", out setting))
                SimulateTimestamp = setting.ParseBoolean();

            if (settings.TryGetValue("autoRepeat", out setting))
                AutoRepeat = setting.ParseBoolean();

            // Define output measurements this input adapter can support based on the instance name (if not already defined)
            OutputSourceIDs = new[] { InstanceName };
        }

        ///
        /// Gets a short one-line status of this <see cref="LocalInputAdapter"/>.
        ///
        /// <param name="maxLength">Maximum length of the status message.</param>
        /// <returns>Text of the status message.</returns>
        public override string GetShortStatus(int maxLength)
        {
            if (Enabled && m_publicationTime > 0)
                return $"Publishing data for {(Ticks)m_publicationTime:yyyy-MM-dd HH:mm:ss.fff}...".CenterText(maxLength);

            return "Not currently publishing data".CenterText(maxLength);
        }

        ///
        /// Attempts to connect to this <see cref="LocalInputAdapter"/>.
        ///
        protected override void AttemptConnection()
        {
            // This adapter is only engaged for history, so we don't process any data unless a temporal constraint is defined
            if (this.TemporalConstraintIsDefined())
            {
                // Turn off read timer if it's active
                m_readTimer.Enabled = false;

                if (m_archiveReader != null)
                {
                    m_archiveReader.Dispose();
                    m_archiveReader = null;
                }

                // Attempt to connect to historian
                m_archiveReader = new Connection(HistorianServer, InstanceName);
                ThreadPool.QueueUserWorkItem(StartDataReader);
            }
        }

        ///
        /// Attempts to disconnect from this <see cref="LocalInputAdapter"/>.
        ///
        protected override void AttemptDisconnection()
        {
            if (m_readTimer != null)
            {
                m_readTimer.Enabled = false;

                lock (m_readTimer)
                    m_dataReader = null;
            }

            if (m_archiveReader != null)
            {
                m_archiveReader.Dispose();
                m_archiveReader = null;
            }
        }

        // Kick start read process for historian
        private void StartDataReader(object state)
        {
            MeasurementKey[] requestedKeys = SupportsTemporalProcessing ? RequestedOutputMeasurementKeys : OutputMeasurements.MeasurementKeys().ToArray();

            if (Enabled && m_archiveReader != null && requestedKeys != null && requestedKeys.Length > 0)
            {
                m_historianIDs = requestedKeys.Select(key => unchecked((int)key.ID)).ToArray();
                m_publicationTime = 0;

                // Start data read from historian
                lock (m_readTimer)
                {
                    m_startTime = base.StartTimeConstraint < DateTime.MinValue ? DateTime.MinValue : base.StartTimeConstraint > DateTime.MaxValue ? DateTime.MaxValue : base.StartTimeConstraint;
                    m_stopTime = base.StopTimeConstraint < DateTime.MinValue ? DateTime.MinValue : base.StopTimeConstraint > DateTime.MaxValue ? DateTime.MaxValue : base.StopTimeConstraint;

                    m_currentTimeOffset = 0;
                    m_dataReader = MeasurementAPI.GetHistorianData(m_archiveReader, m_startTime, m_stopTime, m_historianIDs.ToDelimitedString(',')).GetEnumerator();
                    m_readTimer.Enabled = m_dataReader.MoveNext();

                    if (m_readTimer.Enabled)
                    {
                        OnStatusMessage(MessageLevel.Info, "Starting historical data read...");
                    }
                    else
                    {
                        OnStatusMessage(MessageLevel.Warning, "No historical data was available to read for given time-frame.");
                        OnProcessingComplete();
                    }
                }
            }
            else
            {
                m_readTimer.Enabled = false;
                OnStatusMessage(MessageLevel.Warning, "No measurement keys have been requested for reading, historian reader is idle.");
                OnProcessingComplete();
            }
        }

        // Process next data read
        private void m_readTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            List<IMeasurement> measurements = new List<IMeasurement>();

            if (Monitor.TryEnter(m_readTimer))
            {
                try
                {
                    IMeasurement currentPoint = m_dataReader.Current ?? new Measurement();
                    long timestamp = currentPoint.Timestamp;

                    if (m_publicationTime == 0)
                        m_publicationTime = timestamp;

                    // Set next reasonable publication time
                    while (m_publicationTime < timestamp)
                        m_publicationTime += PublicationInterval;

                    do
                    {
                        // Convert historical timestamp to simulated real-time, if requested
                        if (SimulateTimestamp)
                        {
                            if (m_currentTimeOffset == 0)
                            {
                                Ticks currentTime = new Ticks(DateTime.UtcNow.Ticks).BaselinedTimestamp(BaselineTimeInterval.Second);
                                Ticks historicalTime = currentPoint.Timestamp.BaselinedTimestamp(BaselineTimeInterval.Second);
                                m_currentTimeOffset = currentTime - historicalTime;
                            }

                            currentPoint.Timestamp += m_currentTimeOffset;
                        }

                        // Add current measurement to the collection for publication
                        measurements.Add(currentPoint);

                        // Attempt to move to next record
                        if (m_dataReader.MoveNext())
                        {
                            // Read record value
                            currentPoint = m_dataReader.Current ?? new Measurement();
                            timestamp = currentPoint.Timestamp;
                        }
                        else
                        {
                            if (timestamp < m_stopTime.Ticks && m_startTime.Ticks < timestamp)
                            {
                                // Could be attempting read with a future end time - in these cases attempt to re-read current data
                                // from now to end time in case any new data as been archived in the mean-time
                                m_startTime = new DateTime(timestamp + Ticks.PerMillisecond);
                                m_dataReader = MeasurementAPI.GetHistorianData(m_archiveReader, m_startTime, m_stopTime, m_historianIDs.ToDelimitedString(',')).GetEnumerator();

                                if (!m_dataReader.MoveNext())
                                {
                                    // Finished reading all available data
                                    m_readTimer.Enabled = false;

                                    if (AutoRepeat)
                                        ThreadPool.QueueUserWorkItem(StartDataReader);
                                    else
                                        OnProcessingComplete();
                                }
                            }
                            else
                            {
                                // Finished reading all available data
                                m_readTimer.Enabled = false;

                                if (AutoRepeat)
                                    ThreadPool.QueueUserWorkItem(StartDataReader);
                                else
                                    OnProcessingComplete();
                            }

                            break;
                        }
                    }
                    while (timestamp <= m_publicationTime);
                }
                catch (InvalidOperationException)
                {
                    // Pooled timer thread executed after last read, verify timer has stopped
                    m_readTimer.Enabled = false;
                }
                finally
                {
                    Monitor.Exit(m_readTimer);
                }
            }

            // Publish all measurements for this time interval
            if (measurements.Count > 0)
                OnNewMeasurements(measurements);
        }

        #endregion
    }
}