/FileExtraction/Java_unseen/Kegbot_kegbot-android/kegtab/src/main/java/org/kegbot/core/TapManager.java
/*
 * Copyright 2003-2020 The Kegbot Project contributors <info@kegbot.org>
 *
 * This file is part of the Kegtab package from the Kegbot project. For
 * more information on Kegtab or Kegbot, see <http://kegbot.org/>.
 *
 * Kegtab is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 2.
 *
 * Kegtab is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with Kegtab. If not, see <http://www.gnu.org/licenses/>.
 */
package org.kegbot.core;

import android.util.Log;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.squareup.otto.Bus;
import com.squareup.otto.Produce;
import com.squareup.otto.Subscribe;

import org.kegbot.app.config.ConfigurationStore;
import org.kegbot.app.event.TapsChangedEvent;
import org.kegbot.app.event.VisibleTapsChangedEvent;
import org.kegbot.app.util.IndentingPrintWriter;
import org.kegbot.proto.Models.KegTap;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Tap manager.
 */
public class TapManager extends Manager {

  private static final String TAG = TapManager.class.getSimpleName();

  @VisibleForTesting
  protected static final String KEY_HIDDEN_TAP_IDS = "hidden_tap_ids";

  /** Sorts taps by (sort_order, id). */
  private static final Comparator<KegTap> TAP_COMPARATOR = new Comparator<KegTap>() {
    @Override
    public int compare(KegTap kegTap, KegTap kegTap2) {
      if (kegTap == kegTap2) {
        return 0;
      } else if (kegTap == null) {
        return -1;
      } else if (kegTap2 == null) {
        return 1;
      }
      int ret = kegTap.getSortOrder() - kegTap2.getSortOrder();
      if (ret == 0) {
        ret = kegTap.getId() - kegTap2.getId();
      }
      return ret;
    }
  };

  private final Map<Integer, KegTap> mTaps = Maps.newLinkedHashMap();

  private ConfigurationStore mLocalConfig;

  public TapManager(Bus bus, ConfigurationStore configStore) {
    super(bus);
    mLocalConfig = configStore;
  }

  @Override
  protected synchronized void start() {
    getBus().register(this);
  }

  @Override
  protected synchronized void stop() {
    getBus().unregister(this);
    mTaps.clear();
    super.stop();
  }

  /**
   * Adds a Tap to the system.
   *
   * @param newTap the new tap object
   * @return {@code true} if an existing tap was replaced, {@code false} otherwise.
   */
  synchronized boolean addTap(final KegTap newTap) {
    Log.i(TAG, "Adding/updating tap " + newTap.getId());
    final Integer tapId = Integer.valueOf(newTap.getId());
    return mTaps.put(Integer.valueOf(newTap.getId()), newTap) != null;
  }

  /**
   * Removes a tap from the system.
   *
   * @param tap the tap to remove
   * @return {@code true} if an existing tap was removed, {@code false} otherwise.
   */
  synchronized boolean removeTap(final KegTap tap) {
    Log.i(TAG, "Removing tap " + tap.getId());
    setTapVisibility(tap, true);  // clear invisibility
    return mTaps.remove(Integer.valueOf(tap.getId())) != null;
  }

  /**
   * Updates the set of installed taps to match {@code taps}.
   *
   * @param taps
   */
  public synchronized void updateTaps(final Collection<KegTap> taps) {
    boolean updated = false;
    final Set<Integer> tapsToRemove = Sets.newLinkedHashSet(mTaps.keySet());

    for (final KegTap tap : taps) {
      final Integer key = Integer.valueOf(tap.getId());
      tapsToRemove.remove(key);
      if (mTaps.containsKey(key) && getTap(key.intValue()).equals(tap)) {
        continue;
      }
      updated = true;
      addTap(tap);
    }

    for (final Integer tapId : tapsToRemove) {
      updated = true;
      removeTap(mTaps.get(tapId));
    }

    if (updated) {
      postUpdate();
    }
  }

  private void postUpdate() {
    postOnMainThread(produceTapsEvent());
    postOnMainThread(productVisibleTapsEvent());
  }

  @Produce
  public TapsChangedEvent produceTapsEvent() {
    return new TapsChangedEvent(Lists.newArrayList(getTaps()));
  }

  @Produce
  public VisibleTapsChangedEvent productVisibleTapsEvent() {
    return new VisibleTapsChangedEvent(Lists.newArrayList(getVisibleTaps()));
  }

  public synchronized KegTap getTap(int tapId) {
    return mTaps.get(Integer.valueOf(tapId));
  }

  @Deprecated
  public synchronized KegTap getTapForMeterName(final String meterName) {
    for (final KegTap tap : mTaps.values()) {
      if (!tap.hasMeter()) {
        continue;
      }
      if (meterName.equals(tap.getMeter().getName())) {
        return tap;
      }
    }
    return null;
  }

  /** Returns all taps known to the backend, in sorted order. */
  public synchronized List<KegTap> getTaps() {
    final List<KegTap> result = Lists.newArrayList(mTaps.values());
    Collections.sort(result, TAP_COMPARATOR);
    return result;
  }

  /** Returns all locally-visible taps, in sorted order. */
  public synchronized List<KegTap> getVisibleTaps() {
    final Set<String> hiddenIds = mLocalConfig.getStringSet(KEY_HIDDEN_TAP_IDS,
        Collections.<String>emptySet());
    final List<KegTap> results = Lists.newArrayList();

    for (final KegTap tap : mTaps.values()) {
      final String tapId = String.valueOf(tap.getId());
      if (!hiddenIds.contains(tapId)) {
        results.add(tap);
      }
    }

    Collections.sort(results, TAP_COMPARATOR);
    return results;
  }

  public synchronized Collection<KegTap> getTapsWithActiveKeg() {
    final Set<KegTap> result = Sets.newLinkedHashSet();
    for (final KegTap tap : mTaps.values()) {
      if (tap.hasCurrentKeg()) {
        result.add(tap);
      }
    }
    return result;
  }

  @Subscribe
  public synchronized void onTapSyncResults(TapsChangedEvent event) {
    final List<KegTap> taps = event.getTaps();
    final Set<Integer> removedTaps = Sets.newLinkedHashSet(mTaps.keySet());

    for (final KegTap tap : taps) {
      final KegTap existingTap = getTap(tap.getId());
      removedTaps.remove(Integer.valueOf(tap.getId()));

      if (existingTap == null || !existingTap.equals(tap)) {
        addTap(tap);
      }
    }

    for (final Integer tapId : removedTaps) {
      Log.i(TAG, "Removing tap: " + tapId);
      removeTap(getTap(tapId.intValue()));
    }
  }

  public synchronized void setTapVisibility(KegTap tap, boolean isVisible) {
    final String tapId = String.valueOf(tap.getId());
    final Set<String> hiddenTaps = mLocalConfig.getStringSet(KEY_HIDDEN_TAP_IDS,
        Sets.<String>newLinkedHashSet());

    final boolean changed;
    if (isVisible) {
      changed = hiddenTaps.remove(tapId);
    } else {
      changed = hiddenTaps.add(tapId);
    }

    if (changed) {
      Log.d(TAG, "Setting tap " + tap.getId() + " visible=" + isVisible);
      mLocalConfig.putStringSet(KEY_HIDDEN_TAP_IDS, hiddenTaps);
      postUpdate();
    }
  }

  public synchronized boolean getTapVisibility(KegTap tap) {
    return !mLocalConfig.getStringSet(KEY_HIDDEN_TAP_IDS,
        Sets.<String>newLinkedHashSet()).contains(String.valueOf(tap.getId()));
  }

  @Override
  protected synchronized void dump(IndentingPrintWriter writer) {
    writer.printPair("numTaps", Integer.valueOf(mTaps.size())).println();

    if (mTaps.size() > 0) {
      writer.println();
      writer.println("All taps:");
      writer.println();
      writer.increaseIndent();
      for (final KegTap tap : mTaps.values()) {
        writer.printPair("tap", tap).println();
        writer.println();
      }
      writer.decreaseIndent();
    }
  }

}
