content: /**
   * Radius Networks, Inc.
   * http://www.radiusnetworks.com
   *
   * @author David G. Young
   *
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  package org.altbeacon.beacon;
  
  import android.os.Parcel;
  import android.os.Parcelable;
  
  import java.io.Serializable;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.regex.Pattern;
  
  /**
   * This class represents a criteria of fields used to match beacons.
   *
   * The uniqueId field is used to distinguish this Region in the system.  When you set up
   * monitoring or ranging based on a Region and later want to stop monitoring or ranging,
   * you must do so by passing a Region object that has the same uniqueId field value.  If it
   * doesn't match, you can't cancel the operation.  There is no other purpose to this field.
   *
   * The region can be constructed from a multi-part identifier.  The first identifier is the most
   * significant, the second the second most significant, etc.
   *
   * When constructing a range, any or all of these identifiers may be set to null,
   * which indicates that they are a wildcard and will match any value.
   *
   * @author dyoung
   *
   */
  public class Region implements Parcelable, Serializable {
      private static final String TAG = "Region";
      private static final Pattern MAC_PATTERN = Pattern.compile("^[0-9A-Fa-f]{2}\\:[0-9A-Fa-f]{2}\\:[0-9A-Fa-f]{2}\\:[0-9A-Fa-f]{2}\\:[0-9A-Fa-f]{2}\\:[0-9A-Fa-f]{2}$");
      /**
       * Required to make class Parcelable
       */
      public static final Parcelable.Creator<Region> CREATOR
              = new Parcelable.Creator<Region>() {
          public Region createFromParcel(Parcel in) {
              return new Region(in);
          }
  
          public Region[] newArray(int size) {
              return new Region[size];
          }
      };
      protected final List<Identifier> mIdentifiers;
      protected final String mBluetoothAddress;
      protected final String mUniqueId;
      protected final BeaconParser mBeaconParser;
      /**
       * Constructs a new Region object to be used for Ranging or Monitoring
       * @param uniqueId - A unique identifier used to later cancel Ranging and Monitoring, or change the region being Ranged/Monitored
       * @param id1 - most significant identifier (can be null)
       * @param id2 - second most significant identifier (can be null)
       * @param id3 - third most significant identifier (can be null)
       */
      public Region(String uniqueId, Identifier id1, Identifier id2, Identifier id3) {
          this.mIdentifiers = new ArrayList<Identifier>(3);
          this.mIdentifiers.add(id1);
          this.mIdentifiers.add(id2);
          this.mIdentifiers.add(id3);
          this.mUniqueId = uniqueId;
          this.mBluetoothAddress = null;
          this.mBeaconParser = null;
          if (uniqueId == null) {
              throw new NullPointerException("uniqueId may not be null");
          }
      }
  
      /**
       * Constructs a new Region object to be used for Ranging or Monitoring
       * @param uniqueId - A unique identifier used to later cancel Ranging and Monitoring, or change the region being Ranged/Monitored
       * @param identifiers - list of identifiers for this region
       */
      public Region(String uniqueId, List<Identifier> identifiers) {
         this(uniqueId, identifiers, null);
      }
  
      /**
       * Constructs a new Region object to be used for Ranging or Monitoring
       * @param uniqueId - A unique identifier used to later cancel Ranging and Monitoring, or change the region being Ranged/Monitored
       * @param identifiers - list of identifiers for this region
       * @param bluetoothAddress - mac address
       */
      public Region(String uniqueId, List<Identifier> identifiers, String bluetoothAddress) {
          validateMac(bluetoothAddress);
          this.mIdentifiers = new ArrayList<Identifier>(identifiers);
          this.mUniqueId = uniqueId;
          this.mBluetoothAddress = bluetoothAddress;
          this.mBeaconParser = null;
          if (uniqueId == null) {
              throw new NullPointerException("uniqueId may not be null");
          }
      }
  
      /**
       * Constructs a new Region object to be used for Ranging or Monitoring
       * @param uniqueId - A unique identifier used to later cancel Ranging and Monitoring, or change the region being Ranged/Monitored
       * @param beaconParser - The parser used to decode the beacon identifiers
       * @param identifiers - list of identifiers for this region
       * @param bluetoothAddress - mac address
       * @param apiVersion - future use
       */
      public Region(String uniqueId, BeaconParser beaconParser, List<Identifier> identifiers, String bluetoothAddress, int apiVersion) {
          validateMac(bluetoothAddress);
          if (identifiers == null) {
              this.mIdentifiers = new ArrayList<>();
          }
          else {
              this.mIdentifiers = new ArrayList<>(identifiers);
          }
          this.mUniqueId = uniqueId;
          this.mBluetoothAddress = bluetoothAddress;
          this.mBeaconParser = beaconParser;
          if (uniqueId == null) {
              throw new NullPointerException("uniqueId may not be null");
          }
      }
      public Region(String uniqueId, BeaconParser beaconParser, Identifier id1, Identifier id2, Identifier id3) {
          this(uniqueId, beaconParser, new ArrayList<Identifier>(3), null, 3);
          this.mIdentifiers.add(id1);
          this.mIdentifiers.add(id2);
          this.mIdentifiers.add(id3);
      }
  
      /**
       * Constructs a new Region object to be used for Ranging or Monitoring
       * @param uniqueId - A unique identifier used to later cancel Ranging and Monitoring, or change the region being Ranged/Monitored
       * @param bluetoothAddress - mac address used to match beacons
       */
      public Region(String uniqueId, String bluetoothAddress) {
          validateMac(bluetoothAddress);
          this.mBluetoothAddress = bluetoothAddress;
          this.mUniqueId = uniqueId;
          this.mIdentifiers = new ArrayList<Identifier>();
          this.mBeaconParser = null;
          if (uniqueId == null) {
              throw new NullPointerException("uniqueId may not be null");
          }
      }
  
      /**
       * Convenience method to get the first identifier
       * @return
       */
      public Identifier getId1() {
          return getIdentifier(0);
      }
  
      /**
       * Convenience method to get the second identifier
       * @return
       */
      public Identifier getId2() {
          return getIdentifier(1);
      }
  
      /**
       * Convenience method to get the third identifier
       * @return
       */
      public Identifier getId3() {
          return getIdentifier(2);
      }
  
      /**
       * Returns the 0-indexed identifier
       * Note:  IMPORTANT:  to get id1, you would call getIdentifier(0);
       * @param i
       * @return
       */
      public Identifier getIdentifier(int i) {
          return mIdentifiers.size() > i ? mIdentifiers.get(i) : null;
      }
  
      public List<Identifier> getIdentifiers() {
          return new ArrayList<>(mIdentifiers);
      }
  
      /**
       * Returns the identifier used to start or stop ranging/monitoring this region when calling
       * the <code>BeaconManager</code> methods.
       * @return
       */
      public String getUniqueId() {
          return mUniqueId;
      }
  
      /**
       * Returns the mac address used to filter for beacons
       */
      public String getBluetoothAddress() { return mBluetoothAddress; }
  
      /**
       * Checks to see if an Beacon object is included in the matching criteria of this Region
       * @param beacon the beacon to check to see if it is in the Region
       * @return true if is covered
       */
      public boolean matchesBeacon(Beacon beacon) {
          // If the region has a beacon parser type, it must match the beacon
          if (mBeaconParser != null && mBeaconParser.mIdentifier != null) {
              if (!mBeaconParser.mIdentifier.equals(beacon.getParserIdentifier())) {
                  return false;
              }
          }
          // All identifiers must match, or the corresponding region identifier must be null.
          for (int i = mIdentifiers.size(); --i >= 0; ) {
              final Identifier identifier = mIdentifiers.get(i);
              Identifier beaconIdentifier = null;
              if (i < beacon.mIdentifiers.size()) {
                  beaconIdentifier = beacon.getIdentifier(i);
              }
              if ((beaconIdentifier == null && identifier != null) ||
                      (beaconIdentifier != null  && identifier != null && !identifier.equals(beaconIdentifier))) {
                  return false;
              }
          }
          if (mBluetoothAddress != null && !mBluetoothAddress.equalsIgnoreCase(beacon.mBluetoothAddress)) {
              return false;
          }
          return true;
      }
  
      @Override
      public int hashCode() {
          return this.mUniqueId.hashCode();
      }
  
      @Override
      public boolean equals(Object other) {
          if (other instanceof Region) {
              return ((Region)other).mUniqueId.equals(this.mUniqueId);
          }
          return false;
      }
  
      public boolean hasSameIdentifiers(Region region) {
          if (region.mIdentifiers.size() == this.mIdentifiers.size()) {
              for (int i = 0 ; i < region.mIdentifiers.size(); i++) {
  
                  if (region.getIdentifier(i) == null && this.getIdentifier(i) != null) {
                      return false;
                  }
                  else if (region.getIdentifier(i) != null && this.getIdentifier(i) == null) {
                      return false;
                  }
                  else if (!(region.getIdentifier(i) == null && this.getIdentifier(i) == null)) {
                      if (!region.getIdentifier(i).equals(this.getIdentifier(i))) {
                          return false;
                      }
                  }
              }
          }
          else {
              return false;
          }
          return true;
      }
  
  
  
      public String toString() {
          StringBuilder sb = new StringBuilder();
          int i = 1;
          for (Identifier identifier: mIdentifiers) {
              if (i > 1) {
                  sb.append(" ");
              }
              sb.append("id");
              sb.append(i);
              sb.append(": ");
              sb.append(identifier == null ? "null" : identifier.toString());
              i++;
          }
          return sb.toString();
      }
  
      public int describeContents() {
          return 0;
      }
  
      public void writeToParcel(Parcel out, int flags) {
          out.writeString(mUniqueId);
          out.writeString(mBluetoothAddress);
          out.writeInt(mIdentifiers.size());
  
          for (Identifier identifier: mIdentifiers) {
              if (identifier != null) {
                  out.writeString(identifier.toString());
              }
              else {
                  out.writeString(null);
              }
          }
          if (mBeaconParser != null) {
              out.writeString(mBeaconParser.toString());
          }
          else {
              out.writeString(null);
          }
      }
  
      protected Region(Parcel in) {
          mUniqueId = in.readString();
          mBluetoothAddress = in.readString();
          int size = in.readInt();
          mIdentifiers = new ArrayList<Identifier>(size);
          for (int i = 0; i < size; i++) {
              String identifierString = in.readString();
              if (identifierString == null) {
                  mIdentifiers.add(null);
              } else {
                  Identifier identifier = Identifier.parse(identifierString);
                  mIdentifiers.add(identifier);
              }
          }
          String beaconParserString = in.readString();
          if (beaconParserString != null) {
              this.mBeaconParser = BeaconParser.fromString(beaconParserString);
          }
          else {
              this.mBeaconParser = null;
          }
      }
  
      private void validateMac(String mac) throws IllegalArgumentException {
          if (mac != null) {
              if(!MAC_PATTERN.matcher(mac).matches()) {
                  throw new IllegalArgumentException("Invalid mac address: '"+mac+"' Must be 6 hex bytes separated by colons.");
              }
          }
      }
  
      /**
       * Returns a clone of this instance.
       * @deprecated instances of this class are immutable and therefore don't have to be cloned when
       * used in concurrent code.
       * @return a new instance of this class with the same uniqueId and identifiers
       */
      @Override
      @Deprecated
      public Region clone() {
          return new Region(mUniqueId, mIdentifiers, mBluetoothAddress);
      }
  
  }
  