class GeoIpOperation implements EventOperation {

  @Override
  public InternalEvent perform(InternalEvent ievent) {
    String ipStr = null;

    /*
     * Get field containing an IP address
     */
    try {
      ipStr = ievent.getEventObj().getFieldAsString(this.pathToIpAddress);
    } catch (FieldNotFoundException e) {
      if (!this.required) {
        return ievent;
      }
      throw new OperationException("ip address field " + this.pathToIpAddress + " does not exist");
    }

    if (ipStr == null) {
      if (!this.required) {
        return ievent;
      }
      throw new OperationException("ip address field " + this.pathToIpAddress + " was null");
    }

    /*
     * Sometimes the field contains comma separated ip addresses (ie forwarded web requests). In
     * this case pick the first value in the list which is typically the user.
     */
    if (!ipStr.isEmpty() && ipStr.contains(",")) {
      ipStr = ipStr.split(",")[0];
    }

    InetAddress ipAddress = null;
    try {
      ipAddress = InetAddress.getByName(ipStr);
    } catch (UnknownHostException e) {
      if (!this.required) {
        return ievent;
      }
      throw new OperationException(e);
    }

    if (ipAddress == null) {
      if (!this.required) {
        return ievent;
      }
      throw new OperationException("ip address " + ipStr + " did not resolve");
    }

    CityResponse response = null;
    try {
      response = this.databaseReader.city(ipAddress);
    } catch (IOException | GeoIp2Exception e) {
      if (!this.required) {
        return ievent;
      }
      throw new OperationException(e);
    }

    HashMap<String, Object> geo = new HashMap<String, Object>(1);
    for (GeoProperty property : this.geoProperties) {
      switch (property) {
        case COUNTRY_NAME:
          if (response.getCountry() == null) {
            if (!this.required) {
              return ievent;
            }
            throw new OperationException("country returned null");
          }

          geo.put("country_name", response.getCountry().getName());
          break;
        case COUNTRY_ISO_CODE:
          if (response.getCountry() == null) {
            if (!this.required) {
              return ievent;
            }
            throw new OperationException("country returned null");
          }

          geo.put("country_iso_code", response.getCountry().getIsoCode());
          break;
        case SUBDIVISION_NAME:
          if (response.getMostSpecificSubdivision() == null) {
            if (!this.required) {
              return ievent;
            }
            throw new OperationException("MostSpecificSubdivision returned null");
          }

          geo.put("subdivision_name", response.getMostSpecificSubdivision().getName());
          break;
        case SUBDIVISION_ISO_CODE:
          if (response.getMostSpecificSubdivision() == null) {
            if (!this.required) {
              return ievent;
            }
            throw new OperationException("MostSpecificSubdivision returned null");
          }

          geo.put("subdivision_iso_code", response.getMostSpecificSubdivision().getIsoCode());
          break;
        case CITY_NAME:
          if (response.getCity() == null) {
            if (!this.required) {
              return ievent;
            }
            throw new OperationException("city returned null");
          }

          geo.put("city_name", response.getCity().getName());
          break;
        case POSTAL_CODE:
          if (response.getPostal() == null) {
            if (!this.required) {
              return ievent;
            }
            throw new OperationException("postal returned null");
          }

          geo.put("postal_code", response.getPostal().getCode());
          break;
        case LOCATION:
          if (response.getLocation() == null) {
            if (!this.required) {
              return ievent;
            }
            throw new OperationException("location returned null");
          }

          Double lat = response.getLocation().getLatitude();
          Double lon = response.getLocation().getLongitude();

          if (lat == null || lon == null) {
            if (!this.required) {
              return ievent;
            }
            throw new OperationException("error getting lat/lon");
          }

          HashMap<String, Object> location = new HashMap<String, Object>(2);
          location.put("lat", lat);
          location.put("lon", lon);
          geo.put("location", location);
          break;
      }
    }

    try {
      ievent.getEventObj().setField(this.destFieldName, geo);
    } catch (FieldNotFoundException e) {
      throw new OperationException(e);
    }

    return ievent;
  }

  public  GeoIpOperation(String pathToIpAddress, String destFieldName, DatabaseReader databaseReader,
      List<GeoProperty> geoProperties, boolean required);

}