/*
 * Apache 2.0 License
 *
 * Copyright (c) Sebastian Katzer 2017
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apache License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://opensource.org/licenses/Apache-2.0/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 */

// codebeat:disable[TOO_MANY_FUNCTIONS]

package de.appplant.cordova.plugin.notification;

import static androidx.core.app.NotificationCompat.DEFAULT_LIGHTS;
import static androidx.core.app.NotificationCompat.DEFAULT_SOUND;
import static androidx.core.app.NotificationCompat.DEFAULT_VIBRATE;
import static androidx.core.app.NotificationCompat.PRIORITY_MAX;
import static androidx.core.app.NotificationCompat.PRIORITY_MIN;
import static androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC;
import static androidx.core.app.NotificationCompat.VISIBILITY_SECRET;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.support.v4.media.session.MediaSessionCompat;

import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationCompat.MessagingStyle.Message;

import org.json.JSONArray;
import org.json.JSONObject;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import de.appplant.cordova.plugin.notification.action.Action;
import de.appplant.cordova.plugin.notification.action.ActionGroup;
import de.appplant.cordova.plugin.notification.util.AssetUtil;

/**
 * Wrapper around the JSON object passed through JS which contains all
 * possible option values. Class provides simple readers and more advanced
 * methods to convert independent values into platform specific values.
 */
public final class Options {

  // Key name for bundled sound extra
  static final String EXTRA_SOUND = "NOTIFICATION_SOUND";

  // Key name for bundled launch extra
  public static final String EXTRA_LAUNCH = "NOTIFICATION_LAUNCH";

  // Default icon path
  private static final String DEFAULT_ICON = "res://icon";

  // Default icon type
  private static final String DEFAULT_ICON_TYPE = "square";

  // The original JSON object
  private final JSONObject options;

  // The application context
  private final Context context;

  // Asset util instance
  private final AssetUtil assets;

  /**
   * When creating without a context, various methods might not work well.
   *
   * @param options The options dict map.
   */
  public Options(JSONObject options) {
    this.options = options;
    this.context = null;
    this.assets = null;
  }

  /**
   * Constructor
   *
   * @param context The application context.
   * @param options The options dict map.
   */
  public Options(Context context, JSONObject options) {
    this.context = context;
    this.options = options;
    this.assets = AssetUtil.getInstance(context);
  }

  /**
   * Application context.
   */
  public Context getContext() {
    return context;
  }

  /**
   * Wrapped JSON object.
   */
  public JSONObject getDict() {
    return options;
  }

  /**
   * JSON object as string.
   */
  public String toString() {
    return options.toString();
  }

  /**
   * Gets the ID for the local notification.
   *
   * @return 0 if the user did not specify.
   */
  public Integer getId() {
    return options.optInt("id", 0);
  }

  /**
   * The identifier for the local notification.
   *
   * @return The notification ID as the string
   */
  String getIdentifier() {
    return getId().toString();
  }

  /**
   * Badge number for the local notification.
   */
  public int getBadgeNumber() {
    return options.optInt("badge", 0);
  }

  /**
   * Number for the local notification.
   */
  public int getNumber() {
    return options.optInt("number", 0);
  }

  /**
   * ongoing flag for local notifications.
   */
  public Boolean isSticky() {
    return options.optBoolean("sticky", false);
  }

  /**
   * autoClear flag for local notifications.
   */
  Boolean isAutoClear() {
    return options.optBoolean("autoClear", false);
  }

  /**
   * Gets the raw trigger spec as provided by the user.
   */
  public JSONObject getTrigger() {
    return options.optJSONObject("trigger");
  }

  /**
   * Gets the value of the silent flag.
   */
  boolean isSilent() {
    return options.optBoolean("silent", false);
  }

  /**
   * The group for that notification.
   */
  String getGroup() {
    return options.optString("group", null);
  }

  /**
   * launch flag for the notification.
   */
  boolean isLaunchingApp() {
    return options.optBoolean("launch", true);
  }

  /**
   * wakeup flag for the notification.
   */
  public boolean shallWakeUp() {
    return options.optBoolean("wakeup", true);
  }

  /**
   * Gets the value for the timeout flag.
   */
  long getTimeout() {
    return options.optLong("timeoutAfter");
  }

  /**
   * The channel id of that notification.
   */
  String getChannel() {
    return options.optString("channel", Manager.CHANNEL_ID);
  }

  /**
   * If the group shall show a summary.
   */
  boolean getGroupSummary() {
    return options.optBoolean("groupSummary", false);
  }

  /**
   * Text for the local notification.
   */
  public String getText() {
    Object text = options.opt("text");
    return text instanceof String ? (String) text : "";
  }

  /**
   * Title for the local notification.
   */
  public String getTitle() {
    String title = options.optString("title", "");

    if (title.isEmpty()) {
      title = context.getApplicationInfo().loadLabel(
          context.getPackageManager()).toString();
    }

    return title;
  }

  /**
   * The notification color for LED.
   */
  int getLedColor() {
    Object cfg = options.opt("led");
    String hex = null;

    if (cfg instanceof String) {
      hex = options.optString("led");
    } else if (cfg instanceof JSONArray) {
      hex = options.optJSONArray("led").optString(0);
    } else if (cfg instanceof JSONObject) {
      hex = options.optJSONObject("led").optString("color");
    }

    if (hex == null)
      return 0;

    try {
      hex = stripHex(hex);
      int aRGB = Integer.parseInt(hex, 16);

      return aRGB + 0xFF000000;
    } catch (NumberFormatException e) {
      e.printStackTrace();
    }

    return 0;
  }

  /**
   * The notification color for LED.
   */
  int getLedOn() {
    Object cfg = options.opt("led");
    int defVal = 1000;

    if (cfg instanceof JSONArray)
      return options.optJSONArray("led").optInt(1, defVal);

    if (cfg instanceof JSONObject)
      return options.optJSONObject("led").optInt("on", defVal);

    return defVal;
  }

  /**
   * The notification color for LED.
   */
  int getLedOff() {
    Object cfg = options.opt("led");
    int defVal = 1000;

    if (cfg instanceof JSONArray)
      return options.optJSONArray("led").optInt(2, defVal);

    if (cfg instanceof JSONObject)
      return options.optJSONObject("led").optInt("off", defVal);

    return defVal;
  }

  /**
   * The notification background color for the small icon.
   *
   * @return null, if no color is given.
   */
  public int getColor() {
    String hex = options.optString("color", null);

    if (hex == null)
      return NotificationCompat.COLOR_DEFAULT;

    try {
      hex = stripHex(hex);

      if (hex.matches("[^0-9]*")) {
        return Color.class
            .getDeclaredField(hex.toUpperCase())
            .getInt(null);
      }

      int aRGB = Integer.parseInt(hex, 16);
      return aRGB + 0xFF000000;
    } catch (NumberFormatException e) {
      e.printStackTrace();
    } catch (NoSuchFieldException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    }

    return NotificationCompat.COLOR_DEFAULT;
  }

  /**
   * Sound file path for the local notification.
   */
  Uri getSound() {
    return assets.parse(options.optString("sound", null));
  }

  /**
   * Icon resource ID for the local notification.
   */
  boolean hasLargeIcon() {
    String icon = options.optString("icon", null);
    return icon != null;
  }

  /**
   * Icon bitmap for the local notification.
   */
  Bitmap getLargeIcon() {
    String icon = options.optString("icon", null);
    Uri uri = assets.parse(icon);
    Bitmap bmp = null;

    try {
      bmp = assets.getIconFromUri(uri);
    } catch (Exception e) {
      e.printStackTrace();
    }

    return bmp;
  }

  /**
   * Type of the large icon.
   */
  String getLargeIconType() {
    return options.optString("iconType", DEFAULT_ICON_TYPE);
  }

  /**
   * Small icon resource ID for the local notification.
   */
  int getSmallIcon() {
    String icon = options.optString("smallIcon", DEFAULT_ICON);
    int resId = assets.getResId(icon);

    if (resId == 0) {
      resId = assets.getResId(DEFAULT_ICON);
    }

    if (resId == 0) {
      resId = android.R.drawable.ic_popup_reminder;
    }

    return resId;
  }

  /**
   * If the phone should vibrate.
   */
  private boolean isWithVibration() {
    return options.optBoolean("vibrate", true);
  }

  /**
   * If the phone should play no sound.
   */
  private boolean isWithoutSound() {
    Object value = options.opt("sound");
    return value == null || value.equals(false);
  }

  /**
   * If the phone should play the default sound.
   */
  private boolean isWithDefaultSound() {
    Object value = options.opt("sound");
    return value != null && value.equals(true);
  }

  /**
   * If the phone should show no LED light.
   */
  private boolean isWithoutLights() {
    Object value = options.opt("led");
    return value == null || value.equals(false);
  }

  /**
   * If the phone should show the default LED lights.
   */
  private boolean isWithDefaultLights() {
    Object value = options.opt("led");
    return value != null && value.equals(true);
  }

  /**
   * Set the default notification options that will be used.
   * The value should be one or more of the following fields combined with
   * bitwise-or: DEFAULT_SOUND, DEFAULT_VIBRATE, DEFAULT_LIGHTS.
   */
  int getDefaults() {
    int defaults = options.optInt("defaults", 0);

    if (isWithVibration()) {
      defaults |= DEFAULT_VIBRATE;
    } else {
      defaults &= DEFAULT_VIBRATE;
    }

    if (isWithDefaultSound()) {
      defaults |= DEFAULT_SOUND;
    } else if (isWithoutSound()) {
      defaults &= DEFAULT_SOUND;
    }

    if (isWithDefaultLights()) {
      defaults |= DEFAULT_LIGHTS;
    } else if (isWithoutLights()) {
      defaults &= DEFAULT_LIGHTS;
    }

    return defaults;
  }

  /**
   * Gets the visibility for the notification.
   *
   * @return VISIBILITY_PUBLIC or VISIBILITY_SECRET
   */
  int getVisibility() {
    if (options.optBoolean("lockscreen", true)) {
      return VISIBILITY_PUBLIC;
    } else {
      return VISIBILITY_SECRET;
    }
  }

  /**
   * Gets the notifications priority.
   */
  int getPrio() {
    int prio = options.optInt("priority");

    return Math.min(Math.max(prio, PRIORITY_MIN), PRIORITY_MAX);
  }

  /**
   * If the notification shall show the when date.
   */
  boolean showClock() {
    Object clock = options.opt("clock");

    return (clock instanceof Boolean) ? (Boolean) clock : true;
  }

  /**
   * If the notification shall show the when date.
   */
  boolean showChronometer() {
    Object clock = options.opt("clock");

    return (clock instanceof String) && clock.equals("chronometer");
  }

  /**
   * If the notification shall display a progress bar.
   */
  boolean isWithProgressBar() {
    return options
        .optJSONObject("progressBar")
        .optBoolean("enabled", false);
  }

  /**
   * Gets the progress value.
   *
   * @return 0 by default.
   */
  int getProgressValue() {
    return options
        .optJSONObject("progressBar")
        .optInt("value", 0);
  }

  /**
   * Gets the progress value.
   *
   * @return 100 by default.
   */
  int getProgressMaxValue() {
    return options
        .optJSONObject("progressBar")
        .optInt("maxValue", 100);
  }

  /**
   * Gets the progress indeterminate value.
   *
   * @return false by default.
   */
  boolean isIndeterminateProgress() {
    return options
        .optJSONObject("progressBar")
        .optBoolean("indeterminate", false);
  }

  /**
   * If the trigger shall be infinite.
   */
  public boolean isInfiniteTrigger() {
    JSONObject trigger = options.optJSONObject("trigger");

    return trigger.has("every") && trigger.optInt("count", -1) < 0;
  }

  /**
   * The summary for inbox style notifications.
   */
  String getSummary() {
    return options.optString("summary", null);
  }

  /**
   * Image attachments for image style notifications.
   *
   * @return For now it only returns the first item as Android does not
   *         support multiple attachments like iOS.
   */
  List<Bitmap> getAttachments() {
    JSONArray paths = options.optJSONArray("attachments");
    List<Bitmap> pics = new ArrayList<Bitmap>();

    if (paths == null)
      return pics;

    for (int i = 0; i < paths.length(); i++) {
      Uri uri = assets.parse(paths.optString(i));

      if (uri == Uri.EMPTY)
        continue;

      try {
        Bitmap pic = assets.getIconFromUri(uri);
        pics.add(pic);
        break;
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

    return pics;
  }

  /**
   * Gets the list of actions to display.
   */
  Action[] getActions() {
    Object value = options.opt("actions");
    String groupId = null;
    JSONArray actions = null;
    ActionGroup group = null;

    if (value instanceof String) {
      groupId = (String) value;
    } else if (value instanceof JSONArray) {
      actions = (JSONArray) value;
    }

    if (groupId != null) {
      group = ActionGroup.lookup(groupId);
    } else if (actions != null && actions.length() > 0) {
      group = ActionGroup.parse(context, actions);
    }

    return (group != null) ? group.getActions() : null;
  }

  /**
   * Gets the list of messages to display.
   *
   * @return null if there are no messages.
   */
  Message[] getMessages() {
    Object text = options.opt("text");

    if (text == null || text instanceof String)
      return null;

    JSONArray list = (JSONArray) text;

    if (list.length() == 0)
      return null;

    Message[] messages = new Message[list.length()];
    long now = new Date().getTime();

    for (int i = 0; i < messages.length; i++) {
      JSONObject msg = list.optJSONObject(i);
      String message = msg.optString("message");
      long timestamp = msg.optLong("date", now);
      String person = msg.optString("person", null);

      messages[i] = new Message(message, timestamp, person);
    }

    return messages;
  }

  /**
   * Gets the token for the specified media session.
   *
   * @return null if there no session.
   */
  MediaSessionCompat.Token getMediaSessionToken() {
    String tag = options.optString("mediaSession", null);

    if (tag == null)
      return null;

    MediaSessionCompat session = new MediaSessionCompat(context, tag);

    return session.getSessionToken();
  }

  /**
   * Strips the hex code #FF00FF => FF00FF
   *
   * @param hex The hex code to strip.
   * @return The stripped hex code without a leading #
   */
  private String stripHex(String hex) {
    return (hex.charAt(0) == '#') ? hex.substring(1) : hex;
  }

}

// codebeat:enable[TOO_MANY_FUNCTIONS]
