content: /*
   * Copyright (C) 2015-2023 Emanuel Moecklin
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.onegravity.rteditor;
  
  import android.text.SpannableString;
  import android.text.Spanned;
  import android.text.style.CharacterStyle;
  import android.text.style.ParagraphStyle;
  
  import java.lang.reflect.Array;
  
  /**
   * Clones the Spannable part of an editor by copying the text, all
   * CharacterStyle, and all ParagraphStyle spans to a new Spannable object
   * (used for undo/redo).
   * <p>
   * The code is partly taken from the non-public class
   * android.text.SpannableStringInternal.
   */
  public class ClonedSpannableString extends SpannableString {
  
      private Object[] mSpans;
      private int[] mSpanData;
      private int mSpanCount;
  
      private static final int START = 0;
      private static final int END = 1;
      private static final int FLAGS = 2;
      private static final int COLUMNS = 3;
  
      public ClonedSpannableString(Spanned source) {
          this((CharSequence) source);
      }
  
      public ClonedSpannableString(CharSequence source) {
          super(source.toString());    // the toString is important to prevent the super class from copying the spans
          init(source, 0, source.length());
      }
  
      private void init(CharSequence source, int start, int end) {
          int initial = 20;
          mSpans = new Object[initial];
          mSpanData = new int[initial * 3];
  
          if (source instanceof Spanned) {
              Spanned sp = (Spanned) source;
              for (Object span : sp.getSpans(start, end, Object.class)) {
                  if (span instanceof CharacterStyle || span instanceof ParagraphStyle) {
                      int st = sp.getSpanStart(span);
                      int en = sp.getSpanEnd(span);
                      int fl = sp.getSpanFlags(span);
  
                      if (st < start) st = start;
                      if (en > end) en = end;
  
                      setSpan(span, st - start, en - start, fl);
                  }
              }
          }
      }
  
      // ****************************************** SpannableString Methods *******************************************
  
      @Override
      public void setSpan(Object what, int start, int end, int flags) {
          if (mSpanCount + 1 >= mSpans.length) {
              int newsize = mSpanCount + 10;
              Object[] newtags = new Object[newsize];
              int[] newdata = new int[newsize * 3];
  
              System.arraycopy(mSpans, 0, newtags, 0, mSpanCount);
              System.arraycopy(mSpanData, 0, newdata, 0, mSpanCount * 3);
  
              mSpans = newtags;
              mSpanData = newdata;
          }
  
          mSpans[mSpanCount] = what;
          mSpanData[mSpanCount * COLUMNS + START] = start;
          mSpanData[mSpanCount * COLUMNS + END] = end;
          mSpanData[mSpanCount * COLUMNS + FLAGS] = flags;
          mSpanCount++;
      }
  
      @Override
      public void removeSpan(Object what) {
          int count = mSpanCount;
          Object[] spans = mSpans;
          int[] data = mSpanData;
  
          for (int i = count - 1; i >= 0; i--) {
              if (spans[i] == what) {
                  int c = count - (i + 1);
  
                  System.arraycopy(spans, i + 1, spans, i, c);
                  System.arraycopy(data, (i + 1) * COLUMNS,
                          data, i * COLUMNS, c * COLUMNS);
  
                  mSpanCount--;
                  return;
              }
          }
      }
  
      public int getSpanStart(Object what) {
          int count = mSpanCount;
          Object[] spans = mSpans;
          int[] data = mSpanData;
  
          for (int i = count - 1; i >= 0; i--) {
              if (spans[i] == what) {
                  return data[i * COLUMNS + START];
              }
          }
  
          return -1;
      }
  
      public int getSpanEnd(Object what) {
          int count = mSpanCount;
          Object[] spans = mSpans;
          int[] data = mSpanData;
  
          for (int i = count - 1; i >= 0; i--) {
              if (spans[i] == what) {
                  return data[i * COLUMNS + END];
              }
          }
  
          return -1;
      }
  
      public int getSpanFlags(Object what) {
          int count = mSpanCount;
          Object[] spans = mSpans;
          int[] data = mSpanData;
  
          for (int i = count - 1; i >= 0; i--) {
              if (spans[i] == what) {
                  return data[i * COLUMNS + FLAGS];
              }
          }
  
          return 0;
      }
  
      @SuppressWarnings("unchecked")
      public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
          int count = 0;
  
          int spanCount = mSpanCount;
          Object[] spans = mSpans;
          int[] data = mSpanData;
          Object[] ret = null;
          Object ret1 = null;
  
          for (int i = 0; i < spanCount; i++) {
              if (kind != null && !kind.isInstance(spans[i])) {
                  continue;
              }
  
              int spanStart = data[i * COLUMNS + START];
              int spanEnd = data[i * COLUMNS + END];
  
              if (spanStart > queryEnd) {
                  continue;
              }
              if (spanEnd < queryStart) {
                  continue;
              }
  
              if (spanStart != spanEnd && queryStart != queryEnd) {
                  if (spanStart == queryEnd) {
                      continue;
                  }
                  if (spanEnd == queryStart) {
                      continue;
                  }
              }
  
              if (count == 0) {
                  ret1 = spans[i];
                  count++;
              } else {
                  if (count == 1) {
                      ret = (Object[]) Array.newInstance(kind, spanCount - i + 1);
                      ret[0] = ret1;
                  }
  
                  int prio = data[i * COLUMNS + FLAGS] & Spanned.SPAN_PRIORITY;
                  if (prio != 0) {
                      int j;
  
                      for (j = 0; j < count; j++) {
                          int p = getSpanFlags(ret[j]) & Spanned.SPAN_PRIORITY;
  
                          if (prio > p) {
                              break;
                          }
                      }
  
                      System.arraycopy(ret, j, ret, j + 1, count - j);
                      ret[j] = spans[i];
                      count++;
                  } else {
                      ret[count++] = spans[i];
                  }
              }
          }
  
          if (count == 0) {
              return (T[]) Array.newInstance(kind, 0);
          }
          if (count == 1) {
              ret = (Object[]) Array.newInstance(kind, 1);
              ret[0] = ret1;
              return (T[]) ret;
          }
          if (count == ret.length) {
              return (T[]) ret;
          }
  
          Object[] nret = (Object[]) Array.newInstance(kind, count);
          System.arraycopy(ret, 0, nret, 0, count);
          return (T[]) nret;
      }
  
      @SuppressWarnings("rawtypes")
      public int nextSpanTransition(int start, int limit, Class kind) {
          int count = mSpanCount;
          Object[] spans = mSpans;
          int[] data = mSpanData;
  
          if (kind == null) {
              kind = Object.class;
          }
  
          for (int i = 0; i < count; i++) {
              int st = data[i * COLUMNS + START];
              int en = data[i * COLUMNS + END];
  
              if (st > start && st < limit && kind.isInstance(spans[i]))
                  limit = st;
              if (en > start && en < limit && kind.isInstance(spans[i]))
                  limit = en;
          }
  
          return limit;
      }
  }