content: package com.alphawallet.app.widget;
  
  import static com.alphawallet.app.util.Utils.isIPFS;
  import static com.alphawallet.app.util.Utils.loadFile;
  import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
  
  import android.annotation.SuppressLint;
  import android.content.Context;
  import android.content.res.ColorStateList;
  import android.content.res.TypedArray;
  import android.graphics.Color;
  import android.graphics.drawable.Drawable;
  import android.media.MediaPlayer;
  import android.os.Handler;
  import android.os.Looper;
  import android.text.TextUtils;
  import android.util.AttributeSet;
  import android.util.Base64;
  import android.view.MotionEvent;
  import android.view.View;
  import android.view.ViewGroup;
  import android.webkit.MimeTypeMap;
  import android.webkit.WebChromeClient;
  import android.webkit.WebView;
  import android.webkit.WebViewClient;
  import android.widget.ImageView;
  import android.widget.ProgressBar;
  import android.widget.RelativeLayout;
  
  import androidx.annotation.Nullable;
  import androidx.constraintlayout.widget.ConstraintLayout;
  import androidx.core.content.ContextCompat;
  
  import com.alphawallet.app.C;
  import com.alphawallet.app.R;
  import com.alphawallet.app.entity.ContractType;
  import com.alphawallet.app.entity.nftassets.NFTAsset;
  import com.alphawallet.app.entity.tokens.Attestation;
  import com.alphawallet.app.entity.tokens.Token;
  import com.alphawallet.app.ui.widget.TokensAdapterCallback;
  import com.alphawallet.app.util.Utils;
  import com.bumptech.glide.Glide;
  import com.bumptech.glide.load.DataSource;
  import com.bumptech.glide.load.engine.GlideException;
  import com.bumptech.glide.request.Request;
  import com.bumptech.glide.request.RequestListener;
  import com.bumptech.glide.request.target.DrawableImageViewTarget;
  import com.bumptech.glide.request.target.Target;
  
  import java.math.BigInteger;
  import java.nio.charset.StandardCharsets;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  
  import timber.log.Timber;
  
  /**
   * Created by JB on 30/05/2021.
   */
  public class NFTImageView extends RelativeLayout implements View.OnTouchListener
  {
      private final ImageView image;
      private final RelativeLayout webLayout;
      private final WebView webView;
      private final ConstraintLayout holdingView;
      private final RelativeLayout fallbackLayout;
      private final TokenIcon fallbackIcon;
      private final ProgressBar progressBar;
      private final Handler handler = new Handler(Looper.getMainLooper());
      private MediaPlayer mediaPlayer;
      private int webViewHeight = 0;
      private int heightUpdates;
      private final static int STANDARD_THUMBNAIL_HEIGHT = 156; //standard height in dp of thumbnail icon; don't allow lower than this
  
      /**
       * Prevent glide dumping log errors - it is expected that load will fail
       */
      private final RequestListener<Drawable> requestListener = new RequestListener<>()
      {
          @Override
          public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource)
          {
              //couldn't load using glide
              String msg = e != null ? e.toString() : "";
              if (msg.contains(C.GLIDE_URL_INVALID)) //URL not valid: use the attribute name
              {
                  handler.post(() -> {
                      progressBar.setVisibility(GONE);
                      fallbackLayout.setVisibility(VISIBLE);
                  });
              }
              else if (model != null) //or fallback to webview if there was some other problem
              {
                  setWebView(model.toString(), ImageType.IMAGE);
                  fallbackLayout.setVisibility(GONE);
              }
              return false;
          }
  
          @Override
          public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource)
          {
              progressBar.setVisibility(GONE);
              fallbackLayout.setVisibility(GONE);
              return false;
          }
      };
      private Request loadRequest;
      private String imageUrl;
      private boolean isThumbnail;
  
      public NFTImageView(Context context, @Nullable AttributeSet attrs)
      {
          super(context, attrs);
          inflate(context, R.layout.item_asset_image, this);
          image = findViewById(R.id.image_asset);
          webLayout = findViewById(R.id.web_view_wrapper);
          webView = findViewById(R.id.image_web_view);
          holdingView = findViewById(R.id.layout_holder);
          fallbackLayout = findViewById(R.id.layout_fallback);
          fallbackIcon = findViewById(R.id.icon_fallback);
          progressBar = findViewById(R.id.avatar_progress_spinner);
          mediaPlayer = null;
  
          webLayout.setVisibility(GONE);
          webView.setVisibility(GONE);
  
          if (loadRequest != null && loadRequest.isRunning())
          {
              loadRequest.clear();
          }
  
          //setup view attributes
          setAttrs(context, attrs);
      }
  
      public void setupTokenImageThumbnail(NFTAsset asset)
      {
          setupTokenImageThumbnail(asset, false);
      }
  
      public void setupTokenImageThumbnail(NFTAsset asset, boolean onlyRoundTopCorners)
      {
          heightUpdates = 0;
          fallbackIcon.setupFallbackTextIcon(asset.getName());
          isThumbnail = true;
          loadImage(asset.getThumbnail(), asset.getBackgroundColor());
          if (onlyRoundTopCorners)
          {
              ((ImageView)findViewById(R.id.overlay_rect)).setImageResource(R.drawable.mask_rounded_corners_top_only);
              ((ImageView)findViewById(R.id.image_overlay_rect)).setImageResource(R.drawable.mask_rounded_corners_top_only);
          }
      }
  
      public void setupTokenImage(NFTAsset asset) throws IllegalArgumentException
      {
          heightUpdates = 0;
          isThumbnail = false;
          String anim = asset.getAnimation();
          fallbackIcon.setupFallbackTextIcon(asset.getName());
  
          if (anim != null && !isGlb(anim) && !isAudio(anim) && !isIPFS(anim)) //IPFS anims don't seem to render correctly
          {
              if (!shouldLoad(anim)) return;
              //attempt to load animation
              setWebView(anim, ImageType.ANIM);
          }
          else if (shouldLoad(asset.getImage()))
          {
              loadImage(asset.getImage(), asset.getBackgroundColor());
              playAudioIfAvailable(anim);
          }
      }
  
      public void setImageResource(int resourceId)
      {
          image.setImageResource(resourceId);
      }
  
      private void loadImage(String url, String backgroundColor) throws IllegalArgumentException
      {
          if (!Utils.stillAvailable(getContext())) return;
  
          this.imageUrl = url;
          image.setVisibility(View.VISIBLE);
          webLayout.setVisibility(GONE);
  
          try
          {
              int color = Color.parseColor("#" + backgroundColor);
              ColorStateList sl = ColorStateList.valueOf(color);
              holdingView.setBackgroundTintList(sl);
          }
          catch (Exception e)
          {
              holdingView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.transparent));
          }
  
          loadRequest = Glide.with(getContext())
                  .load(url)
                  .transition(withCrossFade())
                  .override(Target.SIZE_ORIGINAL)
                  .timeout(30 * 1000)
                  .listener(requestListener)
                  .into(new DrawableImageViewTarget(image)).getRequest();
  
          startImageListener();
      }
  
      @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
      private void setWebView(String imageUrl, ImageType hint)
      {
          progressBar.setVisibility(VISIBLE);
          webView.setVerticalScrollBarEnabled(false);
          webView.setHorizontalScrollBarEnabled(false);
          webView.setWebChromeClient(new WebChromeClient());
          startWebViewListener();
          webView.setWebViewClient(new WebViewClient()
          {
              @Override
              public void onPageFinished(WebView view, String url)
              {
                  super.onPageFinished(view, url);
                  progressBar.setVisibility(GONE);
              }
          });
  
          //determine how to display this URL
          final DisplayType useType = new DisplayType(imageUrl, hint);
  
          handler.post(() -> {
              this.imageUrl = imageUrl;
              image.setVisibility(GONE);
              webLayout.setVisibility(View.VISIBLE);
              webView.setVisibility(View.VISIBLE);
  
              if (useType.getImageType() == ImageType.WEB)
              {
                  webView.getSettings().setJavaScriptEnabled(true);
                  webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
                  webView.loadUrl(imageUrl);
              }
              else if (useType.getImageType() == ImageType.ANIM)
              {
                  String loaderAnim = loadFile(getContext(), R.raw.token_anim).replace("[URL]", imageUrl).replace("[MIME]", useType.getMimeType());
                  webView.setOnTouchListener(this);
                  webView.getSettings().setJavaScriptEnabled(true);
                  webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
                  webView.getSettings().setMediaPlaybackRequiresUserGesture(false);
                  webView.getSettings().setAllowContentAccess(true);
                  webView.getSettings().setBlockNetworkLoads(false);
                  webView.getSettings().setDomStorageEnabled(true);
                  String base64 = android.util.Base64.encodeToString(loaderAnim.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT);
                  webView.loadData(base64, "text/html; charset=utf-8", "base64");
              }
              else if (useType.getImageType() == ImageType.MODEL)
              {
                  webView.setOnTouchListener(this);
                  String loader = loadFile(getContext(), R.raw.token_model).replace("[URL]", imageUrl);
                  String base64 = android.util.Base64.encodeToString(loader.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT);
                  webView.loadData(base64, "text/html; charset=utf-8", "base64");
              }
              else if (useType.getImageType() == ImageType.RAW_SVG)
              {
                  //insert class="center-fit"
                  String svgClass = isThumbnail ? imageUrl : addClassToSvg(imageUrl, "center-fit");
                  String loaderSvg = loadFile(getContext(), R.raw.token_svg).replace("[SVG_IMAGE_CODE]", svgClass);
                  String base64 = android.util.Base64.encodeToString(loaderSvg.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT);
                  webView.loadData(base64, "text/html; charset=utf-8", "base64");
              }
              else
              {
                  String loader = loadFile(getContext(), R.raw.token_graphic).replace("[URL]", imageUrl);
                  String base64 = android.util.Base64.encodeToString(loader.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT);
                  webView.loadData(base64, "text/html; charset=utf-8", "base64");
                  if (isThumbnail)
                  {
                      setWebViewHeight(image.getHeight());
                  }
              }
          });
      }
  
      private String addClassToSvg(String svgString, String className)
      {
          if (svgString.contains("class=\"" + className + "\""))
          {
              return svgString;
          }
          else
          {
              int insertPosition = svgString.indexOf("<svg") + 4;
              return svgString.substring(0, insertPosition) + " class=\"" + className + "\"" + svgString.substring(insertPosition);
          }
      }
  
      private void setAttrs(Context context, AttributeSet attrs)
      {
          TypedArray a = context.getTheme().obtainStyledAttributes(
                  attrs,
                  R.styleable.ERC721ImageView,
                  0, 0);
  
          try
          {
              webViewHeight = Utils.dp2px(getContext(), a.getInteger(R.styleable.ERC721ImageView_webview_height, 0));
          }
          finally
          {
              a.recycle();
          }
      }
  
      // View Sizing
      private void startWebViewListener()
      {
          if (isThumbnail)
          {
              return;
          }
  
          webView.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
              if (heightUpdates < 3)
              {
                  int height = bottom - top;
                  heightUpdates++;
                  if (heightUpdates == 3) //3rd re-arrange would always be the final
                  {
                      updateWebView(height, 0);
                  }
                  else
                  {
                      updateWebView(height, 500);
                  }
              }
          });
      }
  
      private void startImageListener()
      {
          if (!isThumbnail)
          {
              return;
          }
  
          image.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
              if (heightUpdates == 0)
              {
                  int height = bottom - top;
                  if (height > 0)
                  {
                      updateImageView(height);
                  }
              }
          });
      }
  
      private void updateImageView(final int height)
      {
          handler.post(() -> {
              setImageViewHeight(height);
              heightUpdates = 3;
          });
      }
  
      private void updateWebView(final int height, long delay)
      {
          handler.removeCallbacksAndMessages(null);
          handler.postDelayed(() -> {
              setWebViewHeight(Math.max(height, webViewHeight));
              heightUpdates = 3;
          }, delay);
      }
  
      private void setWebViewHeight(int height)
      {
          if (height > 0)
          {
              ViewGroup.LayoutParams webLayoutParams = webLayout.getLayoutParams();
              webLayoutParams.height = height;
              webLayout.setLayoutParams(webLayoutParams);
          }
      }
  
      private void setImageViewHeight(int height)
      {
          final int defaultHeight = Utils.dp2px(getContext(), STANDARD_THUMBNAIL_HEIGHT);
          if (height < defaultHeight)
          {
              ViewGroup.LayoutParams imageLayoutParams = image.getLayoutParams();
              imageLayoutParams.height = defaultHeight;
              image.setLayoutParams(imageLayoutParams);
          }
      }
  
      public void showFallbackLayout(Token token)
      {
          fallbackLayout.setVisibility(View.VISIBLE);
          fallbackIcon.bindData(token);
          fallbackIcon.setOnTokenClickListener(new TokensAdapterCallback()
          {
              @Override
              public void onTokenClick(View view, Token token, List<BigInteger> tokenIds, boolean selected)
              {
                  performClick();
              }
  
              @Override
              public void onLongTokenClick(View view, Token token, List<BigInteger> tokenIds)
              {
  
              }
          });
      }
  
      private boolean shouldLoad(String url)
      {
          if (!TextUtils.isEmpty(url) && this.imageUrl == null)
          {
              return true;
          }
          else
          {
              if (TextUtils.isEmpty(url))
              {
                  return false;
              }
              else
              {
                  return !this.imageUrl.equals(url);
              }
          }
      }
  
      public void clearImage()
      {
          imageUrl = null;
      }
  
      public boolean isDisplayingImage()
      {
          return !TextUtils.isEmpty(imageUrl);
      }
  
      private boolean isGlb(String url)
      {
          return (url != null && MimeTypeMap.getFileExtensionFromUrl(url).equals("glb"));
      }
  
      private static final List<String> audioTypes = new ArrayList<>(Arrays.asList("mp3", "ogg", "wav", "flac", "aac", "opus", "weba"));
  
      private boolean isAudio(String url)
      {
          if (url == null)
          {
              return false;
          }
          else
          {
              return audioTypes.contains(MimeTypeMap.getFileExtensionFromUrl(url));
          }
      }
  
      private void playAudioIfAvailable(String url)
      {
          if (!isAudio(url))
          {
              return;
          }
  
          //set up MediaPlayer
          mediaPlayer = new MediaPlayer();
  
          try
          {
              mediaPlayer.setDataSource(url);
              mediaPlayer.prepare();
              mediaPlayer.start();
              mediaPlayer.setLooping(true);
          }
          catch (Exception e)
          {
              e.printStackTrace();
          }
      }
  
      public void onDestroy()
      {
          if (mediaPlayer != null)
          {
              try
              {
                  mediaPlayer.reset();
                  mediaPlayer.release();
                  mediaPlayer = null;
              }
              catch (Exception e)
              {
                  Timber.w(e);
              }
          }
      }
  
      public void onPause()
      {
          if (mediaPlayer != null && mediaPlayer.isPlaying())
          {
              mediaPlayer.pause();
          }
      }
  
      public void onResume()
      {
          if (mediaPlayer != null && !mediaPlayer.isPlaying())
          {
              mediaPlayer.start();
          }
      }
  
      @Override
      public boolean onTouch(View view, MotionEvent motionEvent)
      {
          switch (motionEvent.getAction())
          {
              case MotionEvent.ACTION_DOWN:
              {
                  break;
              }
              case MotionEvent.ACTION_UP:
              {
                  performClick();
                  break;
              }
              default:
                  break;
          }
          return true;
      }
  
      public void setAttestationImage(Token token)
      {
          if (token.getInterfaceSpec() == ContractType.ATTESTATION)
          {
              Attestation attn = (Attestation) token;
              if (attn.isSmartPass())
              {
                  setImageResource(R.drawable.smart_pass);
              }
              else
              {
                  setImageResource(R.drawable.zero_one_block);
              }
          }
      }
  
      private static class DisplayType
      {
          private final ImageType type;
          private final String mimeStr;
  
          // Should handle most cases; this is a handler for anim or drop through cases,
          // Previously these were not handled so this is a big improvement in display handling
          public DisplayType(String url, ImageType hint)
          {
              if (url == null || url.length() < 5)
              {
                  type = hint;
                  mimeStr = "";
                  return;
              }
              else if (url.startsWith("<svg") && url.endsWith("</svg>")) //ensure url is full SVG
              {
                  type = ImageType.RAW_SVG;
                  mimeStr = "";
                  return;
              }
  
              String extension = MimeTypeMap.getFileExtensionFromUrl(url);
  
              switch (extension)
              {
                  case "":
                      mimeStr = "";
                      if (url.contains("tokenscript.org"))
                      {
                          type = ImageType.WEB;
                      }
                      else if (hint == ImageType.IMAGE || hint == ImageType.ANIM)
                      {
                          type = hint;
                      }
                      else
                      {
                          type = ImageType.WEB;
                      }
                      break;
                  case "mp4":
                  case "webm":
                  case "avi":
                  case "mpeg":
                  case "mpg":
                  case "m2v":
                      type = ImageType.ANIM;
                      mimeStr = "video/" + extension;
                      break;
                  case "bmp":
                  case "png":
                  case "jpg":
                  case "svg":
                  case "glb": //currently avoid handling these
                  default:
                      type = ImageType.IMAGE;
                      mimeStr = "image/" + extension;
                      break;
              }
          }
  
          public String getMimeType()
          {
              return mimeStr;
          }
  
          public ImageType getImageType()
          {
              return type;
          }
      }
  
      private enum ImageType
      {
          IMAGE, ANIM, WEB, MODEL, AUDIO, RAW_SVG
      }
  }
  