/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.views.text.frescosupport;

import com.facebook.react.uimanager.LayoutShadowNode;
import javax.annotation.Nullable;

import java.util.Locale;

import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;

import com.facebook.common.util.UriUtil;
import com.facebook.yoga.YogaConstants;
import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.text.ReactTextInlineImageShadowNode;
import com.facebook.react.views.text.TextInlineImageSpan;

/**
 * Shadow node that represents an inline image. Loading is done using Fresco.
 *
 */
public class FrescoBasedReactTextInlineImageShadowNode extends ReactTextInlineImageShadowNode {

  private @Nullable Uri mUri;
  private ReadableMap mHeaders;
  private final AbstractDraweeControllerBuilder mDraweeControllerBuilder;
  private final @Nullable Object mCallerContext;
  private float mWidth = YogaConstants.UNDEFINED;
  private float mHeight = YogaConstants.UNDEFINED;

  public FrescoBasedReactTextInlineImageShadowNode(
    AbstractDraweeControllerBuilder draweeControllerBuilder,
    @Nullable Object callerContext) {
    mDraweeControllerBuilder = draweeControllerBuilder;
    mCallerContext = callerContext;
  }

  private FrescoBasedReactTextInlineImageShadowNode(FrescoBasedReactTextInlineImageShadowNode node) {
    super(node);
    mHeaders = node.mHeaders; // mHeaders is immutable
    mWidth = node.mWidth;
    mHeight = node.mHeight;
    mDraweeControllerBuilder = node.mDraweeControllerBuilder;
    mCallerContext = node.mCallerContext;
    mUri = node.mUri;
  }

  @Override
  public FrescoBasedReactTextInlineImageShadowNode mutableCopy() {
    return new FrescoBasedReactTextInlineImageShadowNode(this);
  }

  @ReactProp(name = "src")
  public void setSource(@Nullable ReadableArray sources) {
    final String source =
      (sources == null || sources.size() == 0) ? null : sources.getMap(0).getString("uri");
    Uri uri = null;
    if (source != null) {
      try {
        uri = Uri.parse(source);
        // Verify scheme is set, so that relative uri (used by static resources) are not handled.
        if (uri.getScheme() == null) {
          uri = null;
        }
      } catch (Exception e) {
        // ignore malformed uri, then attempt to extract resource ID.
      }
      if (uri == null) {
        uri = getResourceDrawableUri(getThemedContext(), source);
      }
    }
    if (uri != mUri) {
      markUpdated();
    }
    mUri = uri;
  }

  @ReactProp(name = "headers")
  public void setHeaders(ReadableMap headers) {
    mHeaders = headers;
  }

  /**
   * Besides width/height, all other layout props on inline images are ignored
   */
  @Override
  public void setWidth(Dynamic width) {
    if (width.getType() == ReadableType.Number) {
      mWidth = (float) width.asDouble();
    } else {
      throw new JSApplicationIllegalArgumentException(
          "Inline images must not have percentage based width");
    }
  }

  @Override
  public void setHeight(Dynamic height) {
    if (height.getType() == ReadableType.Number) {
      mHeight = (float) height.asDouble();
    } else {
      throw new JSApplicationIllegalArgumentException(
          "Inline images must not have percentage based height");
    }
  }

  public @Nullable Uri getUri() {
    return mUri;
  }

  public ReadableMap getHeaders() {
    return mHeaders;
  }

  // TODO: t9053573 is tracking that this code should be shared
  private static @Nullable Uri getResourceDrawableUri(Context context, @Nullable String name) {
    if (name == null || name.isEmpty()) {
      return null;
    }
    name = name.toLowerCase(Locale.getDefault()).replace("-", "_");
    int resId = context.getResources().getIdentifier(
      name,
      "drawable",
      context.getPackageName());
    return new Uri.Builder()
      .scheme(UriUtil.LOCAL_RESOURCE_SCHEME)
      .path(String.valueOf(resId))
      .build();
  }

  @Override
  public boolean isVirtual() {
    return true;
  }

  @Override
  public TextInlineImageSpan buildInlineImageSpan() {
    Resources resources = getThemedContext().getResources();
    int width = (int) Math.ceil(mWidth);
    int height = (int) Math.ceil(mHeight);
    return new FrescoBasedReactTextInlineImageSpan(
      resources,
      height,
      width,
      getUri(),
      getHeaders(),
      getDraweeControllerBuilder(),
      getCallerContext());
  }

  public AbstractDraweeControllerBuilder getDraweeControllerBuilder() {
    return mDraweeControllerBuilder;
  }

  public @Nullable Object getCallerContext() {
    return mCallerContext;
  }
}
