content: /*
   * Copyright (C) 2015 Actor LLC. <https://actor.im>
   */
  
  package im.actor.sdk.view.emoji;
  
  import android.app.Application;
  import android.graphics.Bitmap;
  import android.graphics.Canvas;
  import android.graphics.Paint;
  import android.graphics.Rect;
  import android.os.Build;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.Process;
  import android.text.Spannable;
  import android.text.TextPaint;
  import android.text.style.ReplacementSpan;
  import android.util.TypedValue;
  
  import java.io.File;
  import java.io.InputStream;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.concurrent.CopyOnWriteArrayList;
  
  import im.actor.core.utils.IOUtils;
  import im.actor.sdk.util.images.common.ImageMetadata;
  import im.actor.sdk.util.images.ops.ImageLoading;
  import im.actor.sdk.util.images.sources.FileSource;
  import im.actor.sdk.view.emoji.smiles.SmilesPack;
  import im.actor.sdk.view.emoji.smiles.SmilesRecentListener;
  import im.actor.sdk.view.emoji.smiles.SmilesRecentsController;
  import im.actor.sdk.view.emoji.smiles.SmileysPack;
  import im.actor.sdk.view.emoji.keyboard.emoji.smiles.SmilesListener;
  import im.actor.runtime.Log;
  
  
  public class SmileProcessor {
  
      private static final String TAG = "Emoji";
  
      private static final int COUNT_IN_ROW = 32;
      private static final int COUNT_IN_COL = 26;
      private static final int SECTION_SIDE = 8;
      private static final int SECTION_ROW_COUNT = COUNT_IN_ROW / SECTION_SIDE;
      private static final int SECTION_COL_COUNT = (int) Math.ceil((float) COUNT_IN_COL / SECTION_SIDE);
  
      public static final int CONFIGURATION_DIALOGS = 0;
      public static final int CONFIGURATION_BUBBLES = 1;
  
      private static final long[] EMOJI_MAP = {2302179L, 3154147L, 3219683L, 3285219L, 3350755L, 3416291L, 3481827L, 3547363L, 3612899L, 3678435L, 3743971L, 169L, 174L, 8252L, 8265L, 8482L, 8505L, 8596L, 8597L, 8598L, 8599L, 8600L, 8601L, 8617L, 8618L, 8986L, 8987L, 9193L, 9194L, 9195L, 9196L, 9200L, 9203L, 9410L, 9642L, 9643L, 9654L, 9664L, 9723L, 9724L, 9725L, 9726L, 9728L, 9729L, 9742L, 9745L, 9748L, 9749L, 9757L, 9786L, 9800L, 9801L, 9802L, 9803L, 9804L, 9805L, 9806L, 9807L, 9808L, 9809L, 9810L, 9811L, 9824L, 9827L, 9829L, 9830L, 9832L, 9851L, 9855L, 9875L, 9888L, 9889L, 9898L, 9899L, 9917L, 9918L, 9924L, 9925L, 9934L, 9940L, 9962L, 9970L, 9971L, 9973L, 9978L, 9981L, 9986L, 9989L, 9992L, 9993L, 9994L, 9995L, 9996L, 9999L, 10002L, 10004L, 10006L, 10024L, 10035L, 10036L, 10052L, 10055L, 10060L, 10062L, 10067L, 10068L, 10069L, 10071L, 10084L, 10133L, 10134L, 10135L, 10145L, 10160L, 10175L, 10548L, 10549L, 11013L, 11014L, 11015L, 11035L, 11036L, 11088L, 11093L, 12336L, 12349L, 12951L, 12953L, 3627867140L, 3627867343L, 3627867504L, 3627867505L, 3627867518L, 3627867519L, 3627867534L, 3627867537L, 3627867538L, 3627867539L, 3627867540L, 3627867541L, 3627867542L, 3627867543L, 3627867544L, 3627867545L, 3627867546L, -2865171270784459277L, -2865171266489491990L, -2865171262194524680L, -2865171257899557385L, -2865171253604590105L, -2865171245014655495L, -2865171240719688203L, -2865171236424720905L, -2865171206359949830L, -2865171193475047944L, 3627867649L, 3627867650L, 3627867674L, 3627867695L, 3627867699L, 3627867701L, 3627867702L, 3627867703L, 3627867704L, 3627867705L, 3627867706L, 3627867728L, 3627867904L, 3627867905L, 3627867906L, 3627867907L, 3627867908L, 3627867909L, 3627867910L, 3627867911L, 3627867912L, 3627867913L, 3627867914L, 3627867915L, 3627867916L, 3627867917L, 3627867918L, 3627867919L, 3627867920L, 3627867921L, 3627867922L, 3627867923L, 3627867924L, 3627867925L, 3627867926L, 3627867927L, 3627867928L, 3627867929L, 3627867930L, 3627867931L, 3627867932L, 3627867933L, 3627867934L, 3627867935L, 3627867936L, 3627867952L, 3627867953L, 3627867954L, 3627867955L, 3627867956L, 3627867957L, 3627867959L, 3627867960L, 3627867961L, 3627867962L, 3627867963L, 3627867964L, 3627867965L, 3627867966L, 3627867967L, 3627867968L, 3627867969L, 3627867970L, 3627867971L, 3627867972L, 3627867973L, 3627867974L, 3627867975L, 3627867976L, 3627867977L, 3627867978L, 3627867979L, 3627867980L, 3627867981L, 3627867982L, 3627867983L, 3627867984L, 3627867985L, 3627867986L, 3627867987L, 3627867988L, 3627867989L, 3627867990L, 3627867991L, 3627867992L, 3627867993L, 3627867994L, 3627867995L, 3627867996L, 3627867997L, 3627867998L, 3627867999L, 3627868000L, 3627868001L, 3627868002L, 3627868003L, 3627868004L, 3627868005L, 3627868006L, 3627868007L, 3627868008L, 3627868009L, 3627868010L, 3627868011L, 3627868012L, 3627868013L, 3627868014L, 3627868015L, 3627868016L, 3627868017L, 3627868018L, 3627868019L, 3627868020L, 3627868021L, 3627868022L, 3627868023L, 3627868024L, 3627868025L, 3627868026L, 3627868027L, 3627868028L, 3627868032L, 3627868033L, 3627868034L, 3627868035L, 3627868036L, 3627868037L, 3627868038L, 3627868039L, 3627868040L, 3627868041L, 3627868042L, 3627868043L, 3627868044L, 3627868045L, 3627868046L, 3627868047L, 3627868048L, 3627868049L, 3627868050L, 3627868051L, 3627868064L, 3627868065L, 3627868066L, 3627868067L, 3627868068L, 3627868069L, 3627868070L, 3627868071L, 3627868072L, 3627868073L, 3627868074L, 3627868075L, 3627868076L, 3627868077L, 3627868078L, 3627868079L, 3627868080L, 3627868081L, 3627868082L, 3627868083L, 3627868084L, 3627868085L, 3627868086L, 3627868087L, 3627868088L, 3627868089L, 3627868090L, 3627868091L, 3627868092L, 3627868093L, 3627868094L, 3627868095L, 3627868096L, 3627868097L, 3627868098L, 3627868099L, 3627868100L, 3627868102L, 3627868103L, 3627868104L, 3627868105L, 3627868106L, 3627868128L, 3627868129L, 3627868130L, 3627868131L, 3627868132L, 3627868133L, 3627868134L, 3627868135L, 3627868136L, 3627868137L, 3627868138L, 3627868139L, 3627868140L, 3627868141L, 3627868142L, 3627868143L, 3627868144L, 3627932672L, 3627932673L, 3627932674L, 3627932675L, 3627932676L, 3627932677L, 3627932678L, 3627932679L, 3627932680L, 3627932681L, 3627932682L, 3627932683L, 3627932684L, 3627932685L, 3627932686L, 3627932687L, 3627932688L, 3627932689L, 3627932690L, 3627932691L, 3627932692L, 3627932693L, 3627932694L, 3627932695L, 3627932696L, 3627932697L, 3627932698L, 3627932699L, 3627932700L, 3627932701L, 3627932702L, 3627932703L, 3627932704L, 3627932705L, 3627932706L, 3627932707L, 3627932708L, 3627932709L, 3627932710L, 3627932711L, 3627932712L, 3627932713L, 3627932714L, 3627932715L, 3627932716L, 3627932717L, 3627932718L, 3627932719L, 3627932720L, 3627932721L, 3627932722L, 3627932723L, 3627932724L, 3627932725L, 3627932726L, 3627932727L, 3627932728L, 3627932729L, 3627932730L, 3627932731L, 3627932732L, 3627932733L, 3627932734L, 3627932736L, 3627932738L, 3627932739L, 3627932740L, 3627932741L, 3627932742L, 3627932743L, 3627932744L, 3627932745L, 3627932746L, 3627932747L, 3627932748L, 3627932749L, 3627932750L, 3627932751L, 3627932752L, 3627932753L, 3627932754L, 3627932755L, 3627932756L, 3627932757L, 3627932758L, 3627932759L, 3627932760L, 3627932761L, 3627932762L, 3627932763L, 3627932764L, 3627932765L, 3627932766L, 3627932767L, 3627932768L, 3627932769L, 3627932770L, 3627932771L, 3627932772L, 3627932773L, 3627932774L, 3627932775L, 3627932776L, 3627932777L, 3627932778L, 3627932779L, 3627932780L, 3627932781L, 3627932782L, 3627932783L, 3627932784L, 3627932785L, 3627932786L, 3627932787L, 3627932788L, 3627932789L, 3627932790L, 3627932791L, 3627932792L, 3627932793L, 3627932794L, 3627932795L, 3627932796L, 3627932797L, 3627932798L, 3627932799L, 3627932800L, 3627932801L, 3627932802L, 3627932803L, 3627932804L, 3627932805L, 3627932806L, 3627932807L, 3627932808L, 3627932809L, 3627932810L, 3627932811L, 3627932812L, 3627932813L, 3627932814L, 3627932815L, 3627932816L, 3627932817L, 3627932818L, 3627932819L, 3627932820L, 3627932821L, 3627932822L, 3627932823L, 3627932824L, 3627932825L, 3627932826L, 3627932827L, 3627932828L, 3627932829L, 3627932830L, 3627932831L, 3627932832L, 3627932833L, 3627932834L, 3627932835L, 3627932836L, 3627932837L, 3627932838L, 3627932839L, 3627932840L, 3627932841L, 3627932842L, 3627932843L, 3627932844L, 3627932845L, 3627932846L, 3627932847L, 3627932848L, 3627932849L, 3627932850L, 3627932851L, 3627932852L, 3627932853L, 3627932854L, 3627932855L, 3627932856L, 3627932857L, 3627932858L, 3627932859L, 3627932860L, 3627932861L, 3627932862L, 3627932863L, 3627932864L, 3627932865L, 3627932866L, 3627932867L, 3627932868L, 3627932869L, 3627932870L, 3627932871L, 3627932872L, 3627932873L, 3627932874L, 3627932875L, 3627932876L, 3627932877L, 3627932878L, 3627932879L, 3627932880L, 3627932881L, 3627932882L, 3627932883L, 3627932884L, 3627932885L, 3627932886L, 3627932887L, 3627932888L, 3627932889L, 3627932890L, 3627932891L, 3627932892L, 3627932893L, 3627932894L, 3627932895L, 3627932896L, 3627932897L, 3627932898L, 3627932899L, 3627932900L, 3627932901L, 3627932902L, 3627932903L, 3627932904L, 3627932905L, 3627932906L, 3627932907L, 3627932908L, 3627932909L, 3627932910L, 3627932911L, 3627932912L, 3627932913L, 3627932914L, 3627932915L, 3627932916L, 3627932917L, 3627932918L, 3627932919L, 3627932921L, 3627932922L, 3627932923L, 3627932924L, 3627932928L, 3627932929L, 3627932930L, 3627932931L, 3627932932L, 3627932933L, 3627932934L, 3627932935L, 3627932936L, 3627932937L, 3627932938L, 3627932939L, 3627932940L, 3627932941L, 3627932942L, 3627932943L, 3627932944L, 3627932945L, 3627932946L, 3627932947L, 3627932948L, 3627932949L, 3627932950L, 3627932951L, 3627932952L, 3627932953L, 3627932954L, 3627932955L, 3627932956L, 3627932957L, 3627932958L, 3627932959L, 3627932960L, 3627932961L, 3627932962L, 3627932963L, 3627932964L, 3627932965L, 3627932966L, 3627932967L, 3627932968L, 3627932969L, 3627932970L, 3627932971L, 3627932972L, 3627932973L, 3627932974L, 3627932975L, 3627932976L, 3627932977L, 3627932978L, 3627932979L, 3627932980L, 3627932981L, 3627932982L, 3627932983L, 3627932984L, 3627932985L, 3627932986L, 3627932987L, 3627932988L, 3627932989L, 3627933008L, 3627933009L, 3627933010L, 3627933011L, 3627933012L, 3627933013L, 3627933014L, 3627933015L, 3627933016L, 3627933017L, 3627933018L, 3627933019L, 3627933179L, 3627933180L, 3627933181L, 3627933182L, 3627933183L, 3627933184L, 3627933185L, 3627933186L, 3627933187L, 3627933188L, 3627933189L, 3627933190L, 3627933191L, 3627933192L, 3627933193L, 3627933194L, 3627933195L, 3627933196L, 3627933197L, 3627933198L, 3627933199L, 3627933200L, 3627933201L, 3627933202L, 3627933203L, 3627933204L, 3627933205L, 3627933206L, 3627933207L, 3627933208L, 3627933209L, 3627933210L, 3627933211L, 3627933212L, 3627933213L, 3627933214L, 3627933215L, 3627933216L, 3627933217L, 3627933218L, 3627933219L, 3627933220L, 3627933221L, 3627933222L, 3627933223L, 3627933224L, 3627933225L, 3627933226L, 3627933227L, 3627933228L, 3627933229L, 3627933230L, 3627933231L, 3627933232L, 3627933233L, 3627933234L, 3627933235L, 3627933236L, 3627933237L, 3627933238L, 3627933239L, 3627933240L, 3627933241L, 3627933242L, 3627933243L, 3627933244L, 3627933245L, 3627933246L, 3627933247L, 3627933248L, 3627933253L, 3627933254L, 3627933255L, 3627933256L, 3627933257L, 3627933258L, 3627933259L, 3627933260L, 3627933261L, 3627933262L, 3627933263L, 3627933312L, 3627933313L, 3627933314L, 3627933315L, 3627933316L, 3627933317L, 3627933318L, 3627933319L, 3627933320L, 3627933321L, 3627933322L, 3627933323L, 3627933324L, 3627933325L, 3627933326L, 3627933327L, 3627933328L, 3627933329L, 3627933330L, 3627933331L, 3627933332L, 3627933333L, 3627933334L, 3627933335L, 3627933336L, 3627933337L, 3627933338L, 3627933339L, 3627933340L, 3627933341L, 3627933342L, 3627933343L, 3627933344L, 3627933345L, 3627933346L, 3627933347L, 3627933348L, 3627933349L, 3627933350L, 3627933351L, 3627933352L, 3627933353L, 3627933354L, 3627933355L, 3627933356L, 3627933357L, 3627933358L, 3627933359L, 3627933360L, 3627933361L, 3627933362L, 3627933363L, 3627933364L, 3627933365L, 3627933366L, 3627933367L, 3627933368L, 3627933369L, 3627933370L, 3627933371L, 3627933372L, 3627933373L, 3627933374L, 3627933375L, 3627933376L, 3627933377L, 3627933378L, 3627933379L, 3627933380L, 3627933381L};
      private static final long[] EMOJI_SORTED;
      private static final long minEmoji1;
      private static final long maxEmoji1;
      private static final long minEmoji2;
      private static final long maxEmoji2;
      private static final HashSet<Long> EMOJI_SET = new HashSet<>(EMOJI_MAP.length);
  
      static {
          for (int i = 0; i < EMOJI_MAP.length; i++) {
              EMOJI_SET.add(EMOJI_MAP[i]);
          }
          EMOJI_SORTED = new long[EMOJI_MAP.length];
          long min1 = 0xFFFF, max1 = 0;
          long min2 = 0xFFFFFFFF, max2 = 0;
          for (int i = 0; i < EMOJI_MAP.length; i++) {
              EMOJI_SORTED[i] = EMOJI_MAP[i];
              if ((EMOJI_SORTED[i] & 0xffff) == EMOJI_SORTED[i]) {
                  if (EMOJI_SORTED[i] < min1) {
                      min1 = EMOJI_SORTED[i];
                  }
                  if (EMOJI_SORTED[i] > max1) {
                      max1 = EMOJI_SORTED[i];
                  }
              } else if ((EMOJI_SORTED[i] & 0xffffffff) == EMOJI_SORTED[i]) {
                  if (EMOJI_SORTED[i] < min2) {
                      min2 = EMOJI_SORTED[i];
                  }
                  if (EMOJI_SORTED[i] > max2) {
                      max2 = EMOJI_SORTED[i];
                  }
              }
          }
  
          minEmoji1 = min1;
          maxEmoji1 = max1;
  
          minEmoji2 = min2;
          maxEmoji2 = max2;
          Arrays.sort(EMOJI_SORTED);
      }
  
      private static final int LAYOUT_1X = 1;
      private static final int LAYOUT_15X_1 = 2;
      private static final int LAYOUT_15X_2 = 3;
      private static final int LAYOUT_2X_1 = 4;
      private static final int LAYOUT_2X_2 = 5;
      private static SmileProcessor instance;
      private static SmileProcessor processor;
  
      // protected Bitmap emojiImages;
      protected HashMap<Integer, Bitmap> emojiMap;
  
      private static final Spannable.Factory spannableFactory = Spannable.Factory.getInstance();
  
      private Application application;
      private float density;
  
      private HashMap<Long, Integer> indexes;
      private HashMap<Integer, Paint.FontMetricsInt> originalMetrics;
  
      private int layoutType = LAYOUT_1X;
  
      private boolean isLoading = false;
      private boolean isLoaded = false;
  
      private Handler handler = new Handler(Looper.getMainLooper());
      private CopyOnWriteArrayList<SmilesListener> listeners = new CopyOnWriteArrayList<SmilesListener>();
  
      private int emojiSideSize;
  
      private int rectSize = 0;
      private SmilesRecentListener smilesRecentListener;
      private SmilesRecentsController recentController;
  
      public static final SmileProcessor emoji() {
          return processor;
      }
  
      public SmileProcessor(Application application) {
          long start = System.currentTimeMillis();
          this.application = application;
          processor = this;
          density = application.getResources().getDisplayMetrics().density;
  
          emojiSideSize = (int) (density * 20);
  
          Log.d(TAG, "Emoji phase 0 in " + (System.currentTimeMillis() - start) + " ms");
  
          if (density >= 2 || density == 1) {
              if (density >= 2) {
                  // XHDPI and more
                  if (SmileysPack.PACK_2) {
                      layoutType = LAYOUT_2X_1;
                  } else if (SmileysPack.PACK_15) {
                      layoutType = LAYOUT_15X_1;
                  } else if (SmileysPack.PACK_1) {
                      layoutType = LAYOUT_1X;
                  } else {
                      throw new RuntimeException("Unable to find smileys pack");
                  }
              } else {
                  // MDPI
                  if (SmileysPack.PACK_1) {
                      layoutType = LAYOUT_1X;
                  } else if (SmileysPack.PACK_15) {
                      layoutType = LAYOUT_15X_1;
                  } else if (SmileysPack.PACK_2) {
                      layoutType = LAYOUT_2X_2;
                  } else {
                      throw new RuntimeException("Unable to find smileys pack");
                  }
              }
          } else {
              if (density > 1) { // 1.3333 and 1.5
                  // HDPI & TVDPI
                  if (SmileysPack.PACK_15) {
                      layoutType = LAYOUT_15X_1;
                  } else if (SmileysPack.PACK_2) {
                      layoutType = LAYOUT_2X_1;
                  } else if (SmileysPack.PACK_1) {
                      layoutType = LAYOUT_1X;
                  } else {
                      throw new RuntimeException("Unable to find smileys pack");
                  }
              } else { // 0.75
                  // LDPI
                  if (SmileysPack.PACK_15) {
                      layoutType = LAYOUT_15X_2;
                  } else if (SmileysPack.PACK_1) {
                      layoutType = LAYOUT_1X;
                  } else if (SmileysPack.PACK_2) {
                      layoutType = LAYOUT_2X_2;
                  } else {
                      throw new RuntimeException("Unable to find smileys pack");
                  }
              }
          }
  
          Log.d(TAG, "Emoji phase 1 in " + (System.currentTimeMillis() - start) + " ms");
          start = System.currentTimeMillis();
  
          switch (layoutType) {
              default:
              case LAYOUT_1X:
                  rectSize = 28;
                  break;
              case LAYOUT_15X_1:
                  rectSize = 36;
                  break;
              case LAYOUT_15X_2:
                  rectSize = 18;
                  break;
              case LAYOUT_2X_1:
                  rectSize = 56;
                  break;
              case LAYOUT_2X_2:
                  rectSize = 28;
                  break;
          }
  
          indexes = new HashMap<Long, Integer>();
          emojiMap = new HashMap<Integer, Bitmap>();
          originalMetrics = new HashMap<Integer, Paint.FontMetricsInt>();
  
          TextPaint bodyPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.SUBPIXEL_TEXT_FLAG);
          // bodyPaint.setTypeface(FontController.loadTypeface(application, "normal"));
          bodyPaint.setTextSize(getSp(16));
          bodyPaint.setColor(0xff000000);
          originalMetrics.put(CONFIGURATION_BUBBLES, bodyPaint.getFontMetricsInt());
  
          bodyPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG | Paint.SUBPIXEL_TEXT_FLAG);
          // bodyPaint.setTypeface(FontController.loadTypeface(application, "light"));
          bodyPaint.setColor(0xff808080);
          bodyPaint.setTextSize(getSp(15.5f));
          originalMetrics.put(CONFIGURATION_DIALOGS, bodyPaint.getFontMetricsInt());
  
          Log.d(TAG, "Emoji phase 2 in " + (System.currentTimeMillis() - start) + " ms");
          start = System.currentTimeMillis();
  
          for (int i = 0; i < EMOJI_MAP.length; i++) {
              indexes.put(EMOJI_MAP[i], i);
          }
  
          Log.d(TAG, "Emoji phase 3 in " + (System.currentTimeMillis() - start) + " ms");
      }
  
      protected int getSp(float sp) {
          return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, application.getResources().getDisplayMetrics());
      }
  
      public boolean isLoaded() {
          return isLoaded;
      }
  
      public void registerListener(SmilesListener listener) {
          if (!listeners.contains(listener)) {
              listeners.add(listener);
          }
      }
  
      public void unregisterListener(SmilesListener listener) {
          listeners.remove(listener);
      }
  
      public void loadEmoji() {
          if (isLoaded) {
              return;
          }
          if (isLoading) {
              return;
          }
          isLoading = true;
          new Thread() {
              @Override
              public void run() {
                  Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
                  long start = System.currentTimeMillis();
                  Log.d(TAG, "emoji loading start");
                  try {
  
                      boolean useScale = false;
                      String fileName = null;
                      String fileNameAlpha = null;
  
  
                      switch (layoutType) {
                          default:
                          case LAYOUT_1X:
                              fileName = "emoji_c_1.jpg";
                              fileNameAlpha = "emoji_a_1.jpg";
                              useScale = false;
                              break;
                          case LAYOUT_15X_1:
                              fileName = "emoji_c_15.jpg";
                              fileNameAlpha = "emoji_a_15.jpg";
                              useScale = false;
                              break;
                          case LAYOUT_15X_2:
                              fileName = "emoji_c_15.jpg";
                              fileNameAlpha = "emoji_a_15.jpg";
                              useScale = true;
                              break;
                          case LAYOUT_2X_1:
                              fileName = "emoji_c_2.jpg";
                              fileNameAlpha = "emoji_a_2.jpg";
                              useScale = false;
                              break;
                          case LAYOUT_2X_2:
                              fileName = "emoji_c_2.jpg";
                              fileNameAlpha = "emoji_a_2.jpg";
                              useScale = true;
                              break;
                      }
  
                      File sourceFile = application.getFileStreamPath(fileName);
                      if (!sourceFile.exists()) {
                          InputStream colorsIs = SmileProcessor.this.application.getAssets().open(fileName);
                          IOUtils.copy(colorsIs, sourceFile);
                          colorsIs.close();
                      }
  
                      File sourceAlphaFile = application.getFileStreamPath(fileNameAlpha);
                      if (!sourceAlphaFile.exists()) {
                          InputStream colorsIs = SmileProcessor.this.application.getAssets().open(fileNameAlpha);
                          IOUtils.copy(colorsIs, sourceAlphaFile);
                          colorsIs.close();
                      }
  
                      ImageMetadata metadata = new FileSource(sourceFile.getAbsolutePath()).getImageMetadata();
                      int w = useScale ? metadata.getW() / 2 : metadata.getW();
                      int h = useScale ? metadata.getH() / 2 : metadata.getH();
  
                      Bitmap colorsBitmap;
                      Bitmap alphaBitmap;
  
                      if (useScale) {
                          colorsBitmap = ImageLoading.loadBitmap(sourceFile.getAbsolutePath(), 2);
                          alphaBitmap = ImageLoading.loadBitmap(sourceAlphaFile.getAbsolutePath(), 2);
                      } else {
                          colorsBitmap = ImageLoading.loadBitmap(sourceFile.getAbsolutePath(), 1);
                          alphaBitmap = ImageLoading.loadBitmap(sourceAlphaFile.getAbsolutePath(), 1);
                      }
  
                      // Bitmap colorsBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
                      // BitmapDecoderEx.decodeReuseBitmapScaled(sourceFile.getAbsolutePath(), colorsBitmap);
                      // colorsBitmap.setHasAlpha(true);
  
                      // BitmapDecoderEx.decodeReuseBitmapBlend(sourceAlphaFile.getAbsolutePath(), colorsBitmap, useScale);
  
                      Log.d(TAG, "emoji pre-loaded in " + (System.currentTimeMillis() - start) + " ms");
  
                      int[] resultColors = new int[rectSize * SECTION_SIDE * rectSize * SECTION_SIDE];
                      int[] tmpColors = new int[rectSize * SECTION_SIDE * rectSize * SECTION_SIDE];
  
                      int[] order = new int[]{8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3, 12, 13, 14, 15};
                      int stride = rectSize * SECTION_SIDE;
                      for (int ordinal : order) {
                          int col = ordinal % SECTION_COL_COUNT;
                          int row = ordinal / SECTION_COL_COUNT;
  
                          int leftOffset = col * stride;
                          int topOffset = row * stride;
                          int width = stride;
                          int height = stride;
                          if (row == SECTION_ROW_COUNT - 1) {
                              height = colorsBitmap.getHeight() - topOffset;
                          }
  
                          colorsBitmap.getPixels(tmpColors, 0, stride, leftOffset, topOffset, width, height);
                          for (int ind = 0; ind < resultColors.length; ind++) {
                              resultColors[ind] = 0xFFFFFF & tmpColors[ind];
                          }
                          alphaBitmap.getPixels(tmpColors, 0, stride, leftOffset, topOffset, width, height);
                          for (int ind = 0; ind < resultColors.length; ind++) {
                              resultColors[ind] = resultColors[ind] | ((tmpColors[ind] & 0xFF) << 24);
                          }
  
                          Bitmap section = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                          /*Canvas canvas = new Canvas(section);
                          canvas.drawBitmap(colorsBitmap, new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height),
                                  new Rect(0, 0, width, height), new Paint());*/
                          section.setPixels(resultColors, 0, stride, 0, 0, width, height);
                          emojiMap.put(ordinal, section);
  
                          Log.d(TAG, "emoji region loaded in " + (System.currentTimeMillis() - start) + " ms");
                      }
  
                      recentController = SmilesRecentsController.getInstance(application);
  
                      isLoaded = true;
                      notifyEmojiUpdated(true);
                      Log.d(TAG, "emoji loaded in " + (System.currentTimeMillis() - start) + " ms");
                  } catch (Throwable t) {
                      t.printStackTrace();
                      Log.d(TAG, "emoji loading error");
                      isLoaded = false;
                      isLoading = false;
                  }
              }
          }.start();
      }
  
      private void notifyEmojiUpdated(final boolean completed) {
          handler.post(new Runnable() {
              @Override
              public void run() {
                  Log.d(TAG, "notify");
                  for (SmilesListener listener : listeners) {
                      listener.onSmilesUpdated(completed);
                  }
              }
          });
      }
  
      public void waitForEmoji() {
          if (isLoaded) {
              return;
          }
  
          final Object lock = new Object();
          synchronized (lock) {
              listeners.add(new SmilesListener() {
                  @Override
                  public void onSmilesUpdated(boolean completed) {
                      synchronized (lock) {
                          lock.notify();
                      }
  
                  }
              });
              try {
                  lock.wait();
              } catch (InterruptedException e) {
                  e.printStackTrace();
                  return;
              }
          }
      }
  
      private long getId(String val) {
          long id = 0;
          if (val.length() == 1) {
              id = val.charAt(0);
          } else {
              id = ((long) val.charAt(0) << 16) + (long) val.charAt(1);
          }
  
          return id;
      }
  
  
      public Bitmap getBitmap(String emojiString) {
          return getSection(emojiString.charAt(0));
      }
  
      public void upRecent(long smileId) {
          SmilesPack.upRecent(smileId);
          if (smilesRecentListener != null) {
              smilesRecentListener.onSmilesUpdated();
          }
  
      }
  
      public void setRecentUpdateListener(SmilesRecentListener smilesRecentListener) {
          this.smilesRecentListener = smilesRecentListener;
      }
  
      public SmilesRecentsController getRecentController() {
          return recentController;
      }
  
      private class SpanDescription {
  
          private SpanDescription(long id, int start, int end) {
              this.id = id;
              this.start = start;
              this.end = end;
          }
  
          public long id;
          public int start, end;
      }
  
      public String cutEmoji(String s) {
          StringBuilder stringBuilder = new StringBuilder();
  
          long prev = 0;
          long prevLong = 0;
          int prevLongCount = 0;
          int lastTextPos = 0;
  
          ArrayList<SpanDescription> list = new ArrayList<SpanDescription>();
  
          for (int i = 0; i < s.length(); i++) {
              long current = s.charAt(i);
  
              if (prevLongCount == 3) {
                  long prevId = ((prevLong & 0xFFFFFFFF) << 16) + current;
                  if (EMOJI_SET.contains(prevId)) {
                      if (lastTextPos < i - 3) {
                          stringBuilder.append(s.substring(lastTextPos, i - 3));
                          lastTextPos = i - 3;
                      }
                      stringBuilder.append(":smile:");
                      lastTextPos += 4;
  
                      list.add(new SpanDescription(prevId, i - 3, i + 1));
  
                      prev = 0;
                      prevLong = 0;
                      prevLongCount = 0;
                      continue;
                  }
              }
  
              if (prev != 0) {
                  long prevId = ((prev & 0xFFFF) << 16) + current;
  
                  if (EMOJI_SET.contains(prevId)) {
  
                      if (lastTextPos < i - 1) {
                          stringBuilder.append(s.substring(lastTextPos, i - 1));
                          lastTextPos = i - 1;
                      }
                      stringBuilder.append(":smile:");
                      lastTextPos += 2;
  
                      list.add(new SpanDescription(prevId, i - 1, i + 1));
  
                      prev = 0;
                      prevLong = 0;
                      prevLongCount = 0;
                      continue;
                  }
              }
  
              if (EMOJI_SET.contains(current)) {
  
                  if (lastTextPos < i) {
                      stringBuilder.append(s.substring(lastTextPos, i));
                      lastTextPos = i;
                  }
                  stringBuilder.append(":smile:");
                  lastTextPos += 1;
  
                  list.add(new SpanDescription(current, i, i + 1));
  
                  prev = 0;
                  prevLong = 0;
                  prevLongCount = 0;
              } else {
                  prev = current;
                  prevLong = ((prevLong & 0xFFFFFFFF) << 16) + current;
                  if (prevLongCount < 3) {
                      prevLongCount++;
                  }
              }
          }
  
          if (lastTextPos < s.length()) {
              stringBuilder.append(s.substring(lastTextPos, s.length()));
          }
  
          return stringBuilder.toString();
      }
  
      public String fixStringCompat(String src) {
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
              return src;
          } else {
              return cutEmoji(src);
          }
      }
  
      public Spannable processEmojiCutMutable(String s, int mode) {
          StringBuilder stringBuilder = new StringBuilder();
  
          long prev = 0;
          long prevLong = 0;
          int prevLongCount = 0;
          int lastTextPos = 0;
  
          ArrayList<SpanDescription> list = new ArrayList<SpanDescription>();
  
          for (int i = 0; i < s.length(); i++) {
              long current = s.charAt(i);
  
              if (prevLongCount == 3) {
                  long prevId = ((prevLong & 0xFFFFFFFF) << 16) + current;
                  if (EMOJI_SET.contains(prevId)) {
                      if (lastTextPos < i - 3) {
                          stringBuilder.append(s.substring(lastTextPos, i - 3));
                          lastTextPos = i - 3;
                      }
                      stringBuilder.append("++++");
                      lastTextPos += 4;
  
                      list.add(new SpanDescription(prevId, i - 3, i + 1));
  
                      prev = 0;
                      prevLong = 0;
                      prevLongCount = 0;
                      continue;
                  }
              }
  
              if (prev != 0) {
                  long prevId = ((prev & 0xFFFF) << 16) + current;
  
                  if (EMOJI_SET.contains(prevId)) {
  
                      if (lastTextPos < i - 1) {
                          stringBuilder.append(s.substring(lastTextPos, i - 1));
                          lastTextPos = i - 1;
                      }
                      stringBuilder.append("++");
                      lastTextPos += 2;
  
                      list.add(new SpanDescription(prevId, i - 1, i + 1));
  
                      prev = 0;
                      prevLong = 0;
                      prevLongCount = 0;
                      continue;
                  }
              }
  
              if (EMOJI_SET.contains(current)) {
  
                  if (lastTextPos < i) {
                      stringBuilder.append(s.substring(lastTextPos, i));
                      lastTextPos = i;
                  }
                  stringBuilder.append("+");
                  lastTextPos += 1;
  
                  list.add(new SpanDescription(current, i, i + 1));
                  /*stringBuilder.setSpan(new ImageSpan(res, isAlignBottom ? ImageSpan.ALIGN_BOTTOM : ImageSpan.ALIGN_BASELINE),
                          i, i + 1,
                          Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);*/
  
                  prev = 0;
                  prevLong = 0;
                  prevLongCount = 0;
              } else {
                  prev = current;
                  prevLong = ((prevLong & 0xFFFFFFFF) << 16) + current;
                  if (prevLongCount < 3) {
                      prevLongCount++;
                  }
              }
          }
  
          if (lastTextPos < s.length()) {
              stringBuilder.append(s.substring(lastTextPos, s.length()));
          }
  
          Spannable spannable = spannableFactory.newSpannable(stringBuilder.toString());
          for (SpanDescription description : list) {
              spannable.setSpan(new EmojiSpan(this, indexes.get(description.id), emojiSideSize, originalMetrics.get(mode)), description.start, description.end,
                      Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
          }
          return spannable;
      }
  
      public Spannable processEmojiMutable(CharSequence s, int mode) {
  
          long prev = 0;
          long prevLong = 0;
          int prevLongCount = 0;
  
          ArrayList<SpanDescription> list = new ArrayList<SpanDescription>();
  
          for (int i = 0; i < s.length(); i++) {
              long current = s.charAt(i);
  
              if (prevLongCount == 3) {
                  long prevId = ((prevLong & 0xFFFFFFFF) << 16) + current;
                  if (Arrays.binarySearch(EMOJI_SORTED, prevId) >= 0) {
                      list.add(new SpanDescription(prevId, i - 3, i + 1));
                      prev = 0;
                      prevLong = 0;
                      prevLongCount = 0;
                      continue;
                  }
              }
  
              if (prev != 0) {
                  long prevId = ((prev & 0xFFFF) << 16) + current;
                  if (Arrays.binarySearch(EMOJI_SORTED, prevId) >= 0) {
                      list.add(new SpanDescription(prevId, i - 1, i + 1));
                      prev = 0;
                      prevLong = 0;
                      prevLongCount = 0;
                      continue;
                  }
              }
  
              if (Arrays.binarySearch(EMOJI_SORTED, current) >= 0) {
                  list.add(new SpanDescription(current, i, i + 1));
                  prev = 0;
                  prevLong = 0;
                  prevLongCount = 0;
              } else {
                  prev = current;
                  prevLong = ((prevLong & 0xFFFFFFFF) << 16) + current;
                  if (prevLongCount < 3) {
                      prevLongCount++;
                  }
              }
          }
  
          Spannable spannable = spannableFactory.newSpannable(s);
          for (SpanDescription description : list) {
              spannable.setSpan(new EmojiSpan(this, indexes.get(description.id),
                              emojiSideSize, originalMetrics.get(mode)), description.start, description.end,
                      Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
          }
          return spannable;
      }
  
      public Spannable processEmojiCompatMutable(CharSequence s, int mode) {
          return processEmojiMutable(s, mode);
      }
  
      public static long[] findFirstUniqEmoji(String s, int count) {
          long prev = 0;
          long prevLong = 0;
          int prevLongCount = 0;
  
          long[] res = new long[count];
          int index = 0;
          HashSet<Long> founded = new HashSet<Long>();
  
          for (int i = 0; i < s.length(); i++) {
              long current = s.charAt(i);
  
              if (prevLongCount == 3) {
                  long prevId = ((prevLong & 0xFFFFFFFF) << 16) + current;
                  if (EMOJI_SET.contains(prevId) && !founded.contains(prevId)) {
                      founded.add(prevId);
                      res[index++] = prevId;
                      if (index >= count)
                          break;
                  }
              }
  
              if (prev != 0) {
                  long prevId = ((prev & 0xFFFF) << 16) + current;
  
                  if (EMOJI_SET.contains(prevId) && !founded.contains(prevId)) {
                      founded.add(prevId);
                      res[index++] = prevId;
                      if (index >= count)
                          break;
                  }
              }
  
              if (EMOJI_SET.contains(current) && !founded.contains(current)) {
                  founded.add(current);
                  res[index++] = current;
                  if (index >= count)
                      break;
              } else {
                  prev = current;
                  prevLong = ((prevLong & 0xFFFFFFFF) << 16) + current;
                  if (prevLongCount < 3) {
                      prevLongCount++;
                  }
              }
          }
  
          if (index == count) {
              return res;
          } else {
              long[] res2 = new long[index];
              for (int i = 0; i < index; i++) {
                  res2[i] = res[i];
              }
              return res2;
          }
      }
  
      public static boolean containsEmoji(CharSequence s) {
          if (s == null) {
              return false;
          }
          long prev = 0;
          long prevLong = 0;
          int prevLongCount = 0;
  
          for (int i = 0; i < s.length(); i++) {
              long current = s.charAt(i);
  
  //            if (prevLongCount == 3) {
  //                long prevId = ((prevLong & 0xFFFFFFFF) << 16) + current;
  //                if (Arrays.binarySearch(EMOJI_SORTED, prevId) > 0) {
  //                    return true;
  //                }
  //            }
  
              if (prev != 0) {
                  long prevId = ((prev & 0xFFFF) << 16) + current;
  
                  if ((current >= minEmoji2) && (current <= maxEmoji2) && Arrays.binarySearch(EMOJI_SORTED, prevId) > 0) {
                      return true;
                  }
              }
  
              if ((current >= minEmoji1) && (current <= maxEmoji1) && Arrays.binarySearch(EMOJI_SORTED, current) > 0) {
                  return true;
              } else {
                  prev = current;
                  prevLong = ((prevLong & 0xFFFFFFFF) << 16) + current;
                  if (prevLongCount < 3) {
                      prevLongCount++;
                  }
              }
          }
  
          return false;
      }
  
      public int getRectSize() {
          return rectSize;
      }
  
      public int getSectionIndex(long emoji) {
          int globalIndex = indexes.get(emoji);
          int x = globalIndex / COUNT_IN_ROW;
          int y = globalIndex % COUNT_IN_ROW;
  
          return (y / SECTION_SIDE) + (x / SECTION_SIDE) * SECTION_ROW_COUNT;
      }
  
      public int getSectionX(long emoji) {
          int globalIndex = indexes.get(emoji);
          return (globalIndex % COUNT_IN_ROW) % SECTION_SIDE;
      }
  
      public int getSectionY(long emoji) {
          int globalIndex = indexes.get(emoji);
          return (globalIndex / COUNT_IN_ROW) % SECTION_SIDE;
      }
  
      public Bitmap getSection(int index) {
          return emojiMap.get(index);
      }
  
      private static Paint bitmapPaint = new Paint();
  
      static {
          bitmapPaint.setAntiAlias(true);
          bitmapPaint.setFlags(Paint.FILTER_BITMAP_FLAG);
      }
  
      private static Rect bitmapRect = new Rect();
      private static Rect srcRect = new Rect();
  
      private class EmojiSpan extends ReplacementSpan {
  
          private SmileProcessor processor;
          private int offset;
          private int size;
          private int padding;
          private int section;
          private int sectionX;
          private int sectionY;
          private Paint.FontMetricsInt originalMetrics;
  
          public EmojiSpan(SmileProcessor processor, int index, int size, Paint.FontMetricsInt original) {
              this.processor = processor;
              this.size = size;
              this.originalMetrics = original;
  
              int x = index / COUNT_IN_ROW;
              int y = index % COUNT_IN_ROW;
  
              section = (y / SECTION_SIDE) + (x / SECTION_SIDE) * SECTION_ROW_COUNT;
              sectionX = y % SECTION_SIDE;
              sectionY = x % SECTION_SIDE;
          }
  
          @Override
          public int getSize(Paint paint, CharSequence charSequence, int start, int end, Paint.FontMetricsInt fm) {
              padding = (int) paint.measureText(" ") / 3;
  
              if (fm != null) {
                  fm.ascent = originalMetrics.ascent;
                  fm.descent = originalMetrics.descent;
  
                  fm.top = originalMetrics.top;
                  fm.bottom = originalMetrics.bottom;
              }
              return size + padding * 2;
          }
  
          @Override
          public void draw(Canvas canvas, CharSequence text, int start, int end,
                           float x, int top, int y, int bottom, Paint paint) {
              Bitmap srcEmoji = processor.emojiMap.get(section);
              if (srcEmoji != null) {
                  if (paint.getFontMetrics() != null) {
                      offset = (int) (paint.getFontMetrics().descent);
                  }
                  x += padding;
                  bitmapRect.set((int) x, y - size + offset, (int) (x + size), y + offset);
                  srcRect.set(sectionX * rectSize, sectionY * rectSize, (sectionX + 1) * rectSize, (sectionY + 1) * rectSize);
                  canvas.drawBitmap(srcEmoji, srcRect, bitmapRect, bitmapPaint);
              }
          }
      }
  }