package com.flipkart.android.proteus.value;

import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InflateException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.flipkart.android.proteus.ProteusContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import org.xmlpull.v1.XmlPullParserException;

public class VectorDrawable extends Drawable {

  private static final String LOGTAG = VectorDrawable.class.getSimpleName();
  private static final String SHAPE_SIZE = "size";
  private static final String SHAPE_VIEWPORT = "viewport";
  private static final String SHAPE_PATH = "path";
  private static final String SHAPE_VECTOR = "vector";
  private static final int LINECAP_BUTT = 0;
  private static final int LINECAP_ROUND = 1;
  private static final int LINECAP_SQUARE = 2;
  private static final int LINEJOIN_MITER = 0;
  private static final int LINEJOIN_ROUND = 1;
  private static final int LINEJOIN_BEVEL = 2;

  private final VectorDrawableState mVectorState;

  private int mAlpha = 0xFF;

  private ProteusContext mContext;

  public VectorDrawable() {
    mVectorState = new VectorDrawableState(null);
  }

  public VectorDrawable(ObjectValue value, ProteusContext context) {
    mVectorState = new VectorDrawableState(null);
    mContext = context;
  }

  @Override
  public void draw(@NonNull Canvas canvas) {
    canvas.drawColor(0xffFFFFFF);
    final int saveCount = canvas.save();
    final Rect bounds = getBounds();
    canvas.translate(bounds.left, bounds.top);
    mVectorState.mVPathRenderer.draw(canvas, bounds.width(), bounds.height());
    canvas.restoreToCount(saveCount);
  }

  @Override
  public void setAlpha(int i) {}

  @Override
  public void setColorFilter(@Nullable ColorFilter colorFilter) {}

  @Override
  public int getIntrinsicWidth() {
    return (int) mVectorState.mVPathRenderer.mBaseWidth;
  }

  @Override
  public int getIntrinsicHeight() {
    return (int) mVectorState.mVPathRenderer.mBaseHeight;
  }

  @Override
  public int getOpacity() {
    return PixelFormat.TRANSLUCENT;
  }

  @Nullable
  @Override
  public ConstantState getConstantState() {
    return mVectorState;
  }

  public void inflate(ObjectValue value) {
    VPathRenderer vPathRenderer = inflateInternal(value);
    setPathRenderer(vPathRenderer);
  }

  boolean noSizeTag = true;
  boolean noViewportTag = true;
  boolean noPathTag = true;

  private void parse(VGroup currentGroup, VPathRenderer renderer, ObjectValue value) {
    String name = value.getAsString("type");
    if (SHAPE_PATH.equals(name)) {
      final VPath path = new VPath();
      path.inflate(value);
      currentGroup.add(path);
      noPathTag = false;
    } else if (SHAPE_SIZE.equals(name)) {
      renderer.parseSize(value);
      noSizeTag = false;
    }

    Value children = value.get("children");
    if (children != null && children.isArray()) {
      for (int i = 0; i < children.getAsArray().size(); i++) {
        parse(currentGroup, renderer, children.getAsArray().get(i).getAsObject());
      }
    }
  }

  private VPathRenderer inflateInternal(ObjectValue value) {
    final VPathRenderer pathRenderer = new VPathRenderer();

    VGroup currentGroup = new VGroup();
    parse(currentGroup, pathRenderer, value);

    pathRenderer.mBaseWidth = Dimension.valueOf(value.get("width").getAsString()).apply(mContext);
    pathRenderer.mBaseHeight = Dimension.valueOf(value.get("height").getAsString()).apply(mContext);
    pathRenderer.mViewportWidth =
        Dimension.valueOf(value.get("viewportWidth").getAsString()).apply(mContext);
    pathRenderer.mViewportHeight =
        Dimension.valueOf(value.get("viewportHeight").getAsString()).apply(mContext);

    if (noPathTag) {
      final StringBuilder tag = new StringBuilder();
      if (noSizeTag) {
        tag.append(SHAPE_SIZE);
      }
      if (noViewportTag) {
        if (tag.length() > 0) {
          tag.append(" & ");
        }
        tag.append(SHAPE_SIZE);
      }
      if (noPathTag) {
        if (tag.length() > 0) {
          tag.append(" or ");
        }
        tag.append(SHAPE_PATH);
      }
      throw new InflateException("no " + tag + " defined");
    }

    pathRenderer.mCurrentGroup = currentGroup;
    pathRenderer.parseFinish();
    return pathRenderer;
  }

  private void setPathRenderer(VPathRenderer pathRenderer) {
    mVectorState.mVPathRenderer = pathRenderer;
  }

  private static class VectorDrawableState extends ConstantState {
    int mChangingConfigurations;
    VPathRenderer mVPathRenderer;
    Rect mPadding;

    public VectorDrawableState(VectorDrawableState copy) {
      if (copy != null) {
        mChangingConfigurations = copy.mChangingConfigurations;
        mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
        mPadding = new Rect(copy.mPadding);
      }
    }

    @Override
    public Drawable newDrawable() {
      return new VectorDrawable(null, null);
    }

    @Override
    public Drawable newDrawable(Resources res) {
      return new VectorDrawable(null, null);
    }

    @Override
    public Drawable newDrawable(Resources res, Theme theme) {
      return new VectorDrawable(null, null);
    }

    @Override
    public int getChangingConfigurations() {
      return mChangingConfigurations;
    }
  }

  private static class VPathRenderer {
    private final Path mPath = new Path();
    private final Path mRenderPath = new Path();
    private final Matrix mMatrix = new Matrix();
    private VPath[] mCurrentPaths;
    private Paint mStrokePaint;
    private Paint mFillPaint;
    private PathMeasure mPathMeasure;
    private VGroup mCurrentGroup = new VGroup();
    float mBaseWidth = 1;
    float mBaseHeight = 1;
    float mViewportWidth;
    float mViewportHeight;

    public VPathRenderer() {}

    public VPathRenderer(VPathRenderer copy) {
      mCurrentGroup = copy.mCurrentGroup;
      if (copy.mCurrentPaths != null) {
        mCurrentPaths = new VPath[copy.mCurrentPaths.length];
        for (int i = 0; i < mCurrentPaths.length; i++) {
          mCurrentPaths[i] = new VPath(copy.mCurrentPaths[i]);
        }
      }
      mBaseWidth = copy.mBaseWidth;
      mBaseHeight = copy.mBaseHeight;
      mViewportWidth = copy.mViewportHeight;
      mViewportHeight = copy.mViewportHeight;
    }

    public boolean canApplyTheme() {
      final ArrayList<VPath> paths = mCurrentGroup.mVGList;
      for (int j = paths.size() - 1; j >= 0; j--) {
        final VPath path = paths.get(j);
        if (path.canApplyTheme()) {
          return true;
        }
      }
      return false;
    }

    public void applyTheme(Theme t) {
      final ArrayList<VPath> paths = mCurrentGroup.mVGList;
      for (int j = paths.size() - 1; j >= 0; j--) {
        final VPath path = paths.get(j);
        if (path.canApplyTheme()) {
          path.applyTheme(t);
        }
      }
    }

    public void draw(Canvas canvas, int w, int h) {
      if (mCurrentPaths == null) {
        Log.e(LOGTAG, "mCurrentPaths == null");
        return;
      }
      for (int i = 0; i < mCurrentPaths.length; i++) {
        if (mCurrentPaths[i] != null) {
          drawPath(mCurrentPaths[i], canvas, w, h);
        }
      }
    }

    private void drawPath(VPath vPath, Canvas canvas, int w, int h) {
      final float scale = Math.min(h / mViewportHeight, w / mViewportWidth);
      vPath.toPath(mPath);
      final Path path = mPath;
      if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) {
        float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f;
        float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f;
        if (mPathMeasure == null) {
          mPathMeasure = new PathMeasure();
        }
        mPathMeasure.setPath(mPath, false);
        float len = mPathMeasure.getLength();
        start = start * len;
        end = end * len;
        path.reset();
        if (start > end) {
          mPathMeasure.getSegment(start, len, path, true);
          mPathMeasure.getSegment(0f, end, path, true);
        } else {
          mPathMeasure.getSegment(start, end, path, true);
        }
        path.rLineTo(0, 0); // fix bug in measure
      }
      mRenderPath.reset();
      mMatrix.reset();
      mMatrix.postRotate(vPath.mRotate, vPath.mPivotX, vPath.mPivotY);
      mMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f);
      mMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f);
      mRenderPath.addPath(path, mMatrix);
      if (vPath.mClip) {
        canvas.clipPath(mRenderPath, Region.Op.REPLACE);
      }
      if (vPath.mFillColor != 0) {
        if (mFillPaint == null) {
          mFillPaint = new Paint();
          mFillPaint.setStyle(Paint.Style.FILL);
          mFillPaint.setAntiAlias(true);
        }
        mFillPaint.setColor(vPath.mFillColor);
        canvas.drawPath(mRenderPath, mFillPaint);
      }
      if (vPath.mStrokeColor != 0) {
        if (mStrokePaint == null) {
          mStrokePaint = new Paint();
          mStrokePaint.setStyle(Paint.Style.STROKE);
          mStrokePaint.setAntiAlias(true);
        }
        final Paint strokePaint = mStrokePaint;
        if (vPath.mStrokeLineJoin != null) {
          strokePaint.setStrokeJoin(vPath.mStrokeLineJoin);
        }
        if (vPath.mStrokeLineCap != null) {
          strokePaint.setStrokeCap(vPath.mStrokeLineCap);
        }
        strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale);
        strokePaint.setColor(vPath.mStrokeColor);
        strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale);
        canvas.drawPath(mRenderPath, strokePaint);
      }
    }

    /**
     * Build the "current" path based on the current group TODO: improve memory use & performance or
     * move to C++
     */
    public void parseFinish() {
      final Collection<VPath> paths = mCurrentGroup.getPaths();
      mCurrentPaths = paths.toArray(new VPath[paths.size()]);
      for (int i = 0; i < mCurrentPaths.length; i++) {
        mCurrentPaths[i] = new VPath(mCurrentPaths[i]);
      }
    }

    private void parseViewport(Resources r, AttributeSet attrs) throws XmlPullParserException {
      //            final TypedArray a = r.obtainAttributes(attrs,
      // R.styleable.VectorDrawableViewport);
      //            mViewportWidth = a.getFloat(R.styleable.VectorDrawableViewport_viewportWidth,
      // 0);
      //            mViewportHeight = a.getFloat(R.styleable.VectorDrawableViewport_viewportHeight,
      // 0);
      //            if (mViewportWidth == 0 || mViewportHeight == 0) {
      //                throw new XmlPullParserException(a.getPositionDescription()+
      //                        "<viewport> tag requires viewportWidth & viewportHeight to be set");
      //            }
      //            a.recycle();
    }

    private void parseSize(Resources r, AttributeSet attrs) throws XmlPullParserException {
      //            final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize);
      //            mBaseWidth = a.getDimension(R.styleable.VectorDrawableSize_width, 0);
      //            mBaseHeight = a.getDimension(R.styleable.VectorDrawableSize_height, 0);
      //            if (mBaseWidth == 0 || mBaseHeight == 0) {
      //                throw new XmlPullParserException(a.getPositionDescription()+
      //                        "<size> tag requires width & height to be set");
      //            }
      //            a.recycle();
    }

    private void parseSize(ObjectValue value) {}
  }

  private static class VPath {
    private static final int MAX_STATES = 10;
    private int[] mThemeAttrs;
    int mStrokeColor = 0;
    float mStrokeWidth = 0;
    float mStrokeOpacity = Float.NaN;
    int mFillColor = 0;
    int mFillRule;
    float mFillOpacity = Float.NaN;
    float mRotate = 0;
    float mPivotX = 0;
    float mPivotY = 0;
    float mTrimPathStart = 0;
    float mTrimPathEnd = 1;
    float mTrimPathOffset = 0;
    boolean mClip = false;
    Paint.Cap mStrokeLineCap = Paint.Cap.BUTT;
    Paint.Join mStrokeLineJoin = Paint.Join.MITER;
    float mStrokeMiterlimit = 4;
    private VNode[] mNode = null;
    private String mId;
    private int[] mCheckState = new int[MAX_STATES];
    private boolean[] mCheckValue = new boolean[MAX_STATES];
    private int mNumberOfStates = 0;

    public VPath() {
      // Empty constructor.
    }

    public VPath(VPath p) {
      copyFrom(p);
    }

    public void toPath(Path path) {
      path.reset();
      if (mNode != null) {
        VNode.createPath(mNode, path);
      }
    }

    public String getID() {
      return mId;
    }

    private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) {
      switch (id) {
        case LINECAP_BUTT:
          return Paint.Cap.BUTT;
        case LINECAP_ROUND:
          return Paint.Cap.ROUND;
        case LINECAP_SQUARE:
          return Paint.Cap.SQUARE;
        default:
          return defValue;
      }
    }

    private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) {
      switch (id) {
        case LINEJOIN_MITER:
          return Paint.Join.MITER;
        case LINEJOIN_ROUND:
          return Paint.Join.ROUND;
        case LINEJOIN_BEVEL:
          return Paint.Join.BEVEL;
        default:
          return defValue;
      }
    }

    public void inflate(ObjectValue values) {
      mClip = values.getAsBoolean("clipToPath", false);

      mId = "nameasdasd";

      mNode = parsePath(values.getAsString("pathData"));

      mFillColor = 0xffFE6262;
      mStrokeColor = 0xffFE6262;
      mStrokeWidth = 20;

      updateColorAlphas();
    }

    public void inflate(Resources r, AttributeSet attrs, Theme theme) {
      final TypedArray a =
          null; // obtainAttributes(r, theme, attrs, R.styleable.VectorDrawablePath);
      // final int[] themeAttrs = a.extractThemeAttrs();
      // mThemeAttrs = themeAttrs;
      // NOTE: The set of attributes loaded here MUST match the
      // set of attributes loaded in applyTheme.
      //            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_clipToPath]
      // == 0) {
      //                mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip);
      //            }
      //            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_name] == 0)
      // {
      //                mId = a.getString(R.styleable.VectorDrawablePath_name);
      //            }
      //            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pathData] ==
      // 0) {
      //                mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData));
      //            }
      //            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fill] == 0)
      // {
      //                mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
      //            }
      //            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fillOpacity]
      // == 0) {
      //                mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity,
      // mFillOpacity);
      //            }
      //            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_rotation] ==
      // 0) {
      //                mRotate = a.getFloat(R.styleable.VectorDrawablePath_rotation, mRotate);
      //            }
      //            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pivotX] ==
      // 0) {
      //                mPivotX = a.getFloat(R.styleable.VectorDrawablePath_pivotX, mPivotX);
      //            }
      //            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pivotY] ==
      // 0) {
      //                mPivotY = a.getFloat(R.styleable.VectorDrawablePath_pivotY, mPivotY);
      //            }
      //            if (themeAttrs == null
      //                    || themeAttrs[R.styleable.VectorDrawablePath_strokeLineCap] == 0) {
      //                mStrokeLineCap = getStrokeLineCap(
      //                        a.getInt(R.styleable.VectorDrawablePath_strokeLineCap, -1),
      // mStrokeLineCap);
      //            }
      //            if (themeAttrs == null
      //                    || themeAttrs[R.styleable.VectorDrawablePath_strokeLineJoin] == 0) {
      //                mStrokeLineJoin = getStrokeLineJoin(
      //                        a.getInt(R.styleable.VectorDrawablePath_strokeLineJoin, -1),
      // mStrokeLineJoin);
      //            }
      //            if (themeAttrs == null
      //                    || themeAttrs[R.styleable.VectorDrawablePath_strokeMiterLimit] == 0) {
      //                mStrokeMiterlimit = a.getFloat(
      //                        R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
      //            }
      //            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_stroke] ==
      // 0) {
      //                mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke,
      // mStrokeColor);
      //            }
      //            if (themeAttrs == null
      //                    || themeAttrs[R.styleable.VectorDrawablePath_strokeOpacity] == 0) {
      //                mStrokeOpacity = a.getFloat(
      //                        R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity);
      //            }
      //            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_strokeWidth]
      // == 0) {
      //                mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth,
      // mStrokeWidth);
      //            }
      //            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_trimPathEnd]
      // == 0) {
      //                mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd,
      // mTrimPathEnd);
      //            }
      //            if (themeAttrs == null
      //                    || themeAttrs[R.styleable.VectorDrawablePath_trimPathOffset] == 0) {
      //                mTrimPathOffset = a.getFloat(
      //                        R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
      //            }
      //            if (themeAttrs == null
      //                    || themeAttrs[R.styleable.VectorDrawablePath_trimPathStart] == 0) {
      //                mTrimPathStart = a.getFloat(
      //                        R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
      //            }
      updateColorAlphas();
      a.recycle();
    }

    public boolean canApplyTheme() {
      return mThemeAttrs != null;
    }

    public void applyTheme(Theme t) {
      if (mThemeAttrs == null) {
        return;
      }
      //            final TypedArray a = t.resolveAttributes(
      //                    mThemeAttrs, R.styleable.VectorDrawablePath);
      //            mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip);
      //            if (a.hasValue(R.styleable.VectorDrawablePath_name)) {
      //                mId = a.getString(R.styleable.VectorDrawablePath_name);
      //            }
      //            if (a.hasValue(R.styleable.VectorDrawablePath_pathData)) {
      //                mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData));
      //            }
      //            mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
      //            mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity,
      // mFillOpacity);
      //            mRotate = a.getFloat(R.styleable.VectorDrawablePath_rotation, mRotate);
      //            mPivotX = a.getFloat(R.styleable.VectorDrawablePath_pivotX, mPivotX);
      //            mPivotY = a.getFloat(R.styleable.VectorDrawablePath_pivotY, mPivotY);
      //            mStrokeLineCap = getStrokeLineCap(a.getInt(
      //                    R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
      //            mStrokeLineJoin = getStrokeLineJoin(a.getInt(
      //                    R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin);
      //            mStrokeMiterlimit = a.getFloat(
      //                    R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
      //            mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor);
      //            mStrokeOpacity = a.getFloat(
      //                    R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity);
      //            mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth,
      // mStrokeWidth);
      //            mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd,
      // mTrimPathEnd);
      //            mTrimPathOffset = a.getFloat(
      //                    R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
      //            mTrimPathStart = a.getFloat(
      //                    R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
      updateColorAlphas();
    }

    private void updateColorAlphas() {
      if (!Float.isNaN(mFillOpacity)) {
        mFillColor &= 0x00FFFFFF;
        mFillColor |= ((int) (0xFF * mFillOpacity)) << 24;
      }
      if (!Float.isNaN(mStrokeOpacity)) {
        mStrokeColor &= 0x00FFFFFF;
        mStrokeColor |= ((int) (0xFF * mStrokeOpacity)) << 24;
      }
    }

    private static int nextStart(String s, int end) {
      char c;
      while (end < s.length()) {
        c = s.charAt(end);
        if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) {
          return end;
        }
        end++;
      }
      return end;
    }

    private void addNode(ArrayList<VNode> list, char cmd, float[] val) {
      list.add(new VectorDrawable.VNode(cmd, val));
    }

    /**
     * parse the floats in the string this is an optimized version of parseFloat(s.split(",|\\s"));
     *
     * @param s the string containing a command and list of floats
     * @return array of floats
     */
    private static float[] getFloats(String s) {
      if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') {
        return new float[0];
      }
      try {
        float[] tmp = new float[s.length()];
        int count = 0;
        int pos = 1, end;
        while ((end = extract(s, pos)) >= 0) {
          if (pos < end) {
            tmp[count++] = Float.parseFloat(s.substring(pos, end));
          }
          pos = end + 1;
        }
        // handle the final float if there is one
        if (pos < s.length()) {
          tmp[count++] = Float.parseFloat(s.substring(pos, s.length()));
        }
        return Arrays.copyOf(tmp, count);
      } catch (NumberFormatException e) {
        Log.e(LOGTAG, "error in parsing \"" + s + "\"");
        throw e;
      }
    }

    /**
     * calculate the position of the next comma or space
     *
     * @param s the string to search
     * @param start the position to start searching
     * @return the position of the next comma or space or -1 if none found
     */
    private static int extract(String s, int start) {
      int space = s.indexOf(' ', start);
      int comma = s.indexOf(',', start);
      if (space == -1) {
        return comma;
      }
      if (comma == -1) {
        return space;
      }
      return (comma > space) ? space : comma;
    }

    private VectorDrawable.VNode[] parsePath(String value) {
      int start = 0;
      int end = 1;
      ArrayList<VectorDrawable.VNode> list = new ArrayList<>();
      while (end < value.length()) {
        end = nextStart(value, end);
        String s = value.substring(start, end);
        float[] val = getFloats(s);
        addNode(list, s.charAt(0), val);
        start = end;
        end++;
      }
      if ((end - start) == 1 && start < value.length()) {
        addNode(list, value.charAt(start), new float[0]);
      }
      return list.toArray(new VectorDrawable.VNode[list.size()]);
    }

    public void copyFrom(VPath p1) {
      mNode = new VNode[p1.mNode.length];
      for (int i = 0; i < mNode.length; i++) {
        mNode[i] = new VNode(p1.mNode[i]);
      }
      mId = p1.mId;
      mStrokeColor = p1.mStrokeColor;
      mFillColor = p1.mFillColor;
      mStrokeWidth = p1.mStrokeWidth;
      mRotate = p1.mRotate;
      mPivotX = p1.mPivotX;
      mPivotY = p1.mPivotY;
      mTrimPathStart = p1.mTrimPathStart;
      mTrimPathEnd = p1.mTrimPathEnd;
      mTrimPathOffset = p1.mTrimPathOffset;
      mStrokeLineCap = p1.mStrokeLineCap;
      mStrokeLineJoin = p1.mStrokeLineJoin;
      mStrokeMiterlimit = p1.mStrokeMiterlimit;
      mNumberOfStates = p1.mNumberOfStates;
      for (int i = 0; i < mNumberOfStates; i++) {
        mCheckState[i] = p1.mCheckState[i];
        mCheckValue[i] = p1.mCheckValue[i];
      }
      mFillRule = p1.mFillRule;
    }
  }

  private static class VGroup {
    private final HashMap<String, VPath> mVGPathMap = new HashMap<String, VPath>();
    private final ArrayList<VPath> mVGList = new ArrayList<VPath>();

    public void add(VPath path) {
      String id = path.getID();
      mVGPathMap.put(id, path);
      mVGList.add(path);
    }

    /**
     * Must return in order of adding
     *
     * @return ordered list of paths
     */
    public Collection<VPath> getPaths() {
      return mVGList;
    }
  }

  private static class VNode {
    private char mType;
    private float[] mParams;

    public VNode(char type, float[] params) {
      mType = type;
      mParams = params;
    }

    public VNode(VNode n) {
      mType = n.mType;
      mParams = Arrays.copyOf(n.mParams, n.mParams.length);
    }

    public static void createPath(VNode[] node, Path path) {
      float[] current = new float[4];
      char previousCommand = 'm';
      for (int i = 0; i < node.length; i++) {
        addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
        previousCommand = node[i].mType;
      }
    }

    private static void addCommand(
        Path path, float[] current, char previousCmd, char cmd, float[] val) {
      int incr = 2;
      float currentX = current[0];
      float currentY = current[1];
      float ctrlPointX = current[2];
      float ctrlPointY = current[3];
      float reflectiveCtrlPointX;
      float reflectiveCtrlPointY;
      switch (cmd) {
        case 'z':
        case 'Z':
          path.close();
          return;
        case 'm':
        case 'M':
        case 'l':
        case 'L':
        case 't':
        case 'T':
          incr = 2;
          break;
        case 'h':
        case 'H':
        case 'v':
        case 'V':
          incr = 1;
          break;
        case 'c':
        case 'C':
          incr = 6;
          break;
        case 's':
        case 'S':
        case 'q':
        case 'Q':
          incr = 4;
          break;
        case 'a':
        case 'A':
          incr = 7;
          break;
      }
      for (int k = 0; k < val.length; k += incr) {
        switch (cmd) {
          case 'm': // moveto - Start a new sub-path (relative)
            path.rMoveTo(val[k + 0], val[k + 1]);
            currentX += val[k + 0];
            currentY += val[k + 1];
            break;
          case 'M': // moveto - Start a new sub-path
            path.moveTo(val[k + 0], val[k + 1]);
            currentX = val[k + 0];
            currentY = val[k + 1];
            break;
          case 'l': // lineto - Draw a line from the current point (relative)
            path.rLineTo(val[k + 0], val[k + 1]);
            currentX += val[k + 0];
            currentY += val[k + 1];
            break;
          case 'L': // lineto - Draw a line from the current point
            path.lineTo(val[k + 0], val[k + 1]);
            currentX = val[k + 0];
            currentY = val[k + 1];
            break;
          case 'z': // closepath - Close the current subpath
          case 'Z': // closepath - Close the current subpath
            path.close();
            break;
          case 'h': // horizontal lineto - Draws a horizontal line (relative)
            path.rLineTo(val[k + 0], 0);
            currentX += val[k + 0];
            break;
          case 'H': // horizontal lineto - Draws a horizontal line
            path.lineTo(val[k + 0], currentY);
            currentX = val[k + 0];
            break;
          case 'v': // vertical lineto - Draws a vertical line from the current point (r)
            path.rLineTo(0, val[k + 0]);
            currentY += val[k + 0];
            break;
          case 'V': // vertical lineto - Draws a vertical line from the current point
            path.lineTo(currentX, val[k + 0]);
            currentY = val[k + 0];
            break;
          case 'c': // curveto - Draws a cubic Bézier curve (relative)
            path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], val[k + 4], val[k + 5]);
            ctrlPointX = currentX + val[k + 2];
            ctrlPointY = currentY + val[k + 3];
            currentX += val[k + 4];
            currentY += val[k + 5];
            break;
          case 'C': // curveto - Draws a cubic Bézier curve
            path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], val[k + 4], val[k + 5]);
            currentX = val[k + 4];
            currentY = val[k + 5];
            ctrlPointX = val[k + 2];
            ctrlPointY = val[k + 3];
            break;
          case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
            reflectiveCtrlPointX = 0;
            reflectiveCtrlPointY = 0;
            if (previousCmd == 'c'
                || previousCmd == 's'
                || previousCmd == 'C'
                || previousCmd == 'S') {
              reflectiveCtrlPointX = currentX - ctrlPointX;
              reflectiveCtrlPointY = currentY - ctrlPointY;
            }
            path.rCubicTo(
                reflectiveCtrlPointX,
                reflectiveCtrlPointY,
                val[k + 0],
                val[k + 1],
                val[k + 2],
                val[k + 3]);
            ctrlPointX = currentX + val[k + 0];
            ctrlPointY = currentY + val[k + 1];
            currentX += val[k + 2];
            currentY += val[k + 3];
            break;
          case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
            reflectiveCtrlPointX = currentX;
            reflectiveCtrlPointY = currentY;
            if (previousCmd == 'c'
                || previousCmd == 's'
                || previousCmd == 'C'
                || previousCmd == 'S') {
              reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
              reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
            }
            path.cubicTo(
                reflectiveCtrlPointX,
                reflectiveCtrlPointY,
                val[k + 0],
                val[k + 1],
                val[k + 2],
                val[k + 3]);
            ctrlPointX = val[k + 0];
            ctrlPointY = val[k + 1];
            currentX = val[k + 2];
            currentY = val[k + 3];
            break;
          case 'q': // Draws a quadratic Bézier (relative)
            path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
            ctrlPointX = currentX + val[k + 0];
            ctrlPointY = currentY + val[k + 1];
            currentX += val[k + 2];
            currentY += val[k + 3];
            break;
          case 'Q': // Draws a quadratic Bézier
            path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
            ctrlPointX = val[k + 0];
            ctrlPointY = val[k + 1];
            currentX = val[k + 2];
            currentY = val[k + 3];
            break;
          case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
            reflectiveCtrlPointX = 0;
            reflectiveCtrlPointY = 0;
            if (previousCmd == 'q'
                || previousCmd == 't'
                || previousCmd == 'Q'
                || previousCmd == 'T') {
              reflectiveCtrlPointX = currentX - ctrlPointX;
              reflectiveCtrlPointY = currentY - ctrlPointY;
            }
            path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, val[k + 0], val[k + 1]);
            ctrlPointX = currentX + reflectiveCtrlPointX;
            ctrlPointY = currentY + reflectiveCtrlPointY;
            currentX += val[k + 0];
            currentY += val[k + 1];
            break;
          case 'T': // Draws a quadratic Bézier curve (reflective control point)
            reflectiveCtrlPointX = currentX;
            reflectiveCtrlPointY = currentY;
            if (previousCmd == 'q'
                || previousCmd == 't'
                || previousCmd == 'Q'
                || previousCmd == 'T') {
              reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
              reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
            }
            path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, val[k + 0], val[k + 1]);
            ctrlPointX = reflectiveCtrlPointX;
            ctrlPointY = reflectiveCtrlPointY;
            currentX = val[k + 0];
            currentY = val[k + 1];
            break;
          case 'a': // Draws an elliptical arc
            // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
            drawArc(
                path,
                currentX,
                currentY,
                val[k + 5] + currentX,
                val[k + 6] + currentY,
                val[k + 0],
                val[k + 1],
                val[k + 2],
                val[k + 3] != 0,
                val[k + 4] != 0);
            currentX += val[k + 5];
            currentY += val[k + 6];
            ctrlPointX = currentX;
            ctrlPointY = currentY;
            break;
          case 'A': // Draws an elliptical arc
            drawArc(
                path,
                currentX,
                currentY,
                val[k + 5],
                val[k + 6],
                val[k + 0],
                val[k + 1],
                val[k + 2],
                val[k + 3] != 0,
                val[k + 4] != 0);
            currentX = val[k + 5];
            currentY = val[k + 6];
            ctrlPointX = currentX;
            ctrlPointY = currentY;
            break;
        }
        previousCmd = cmd;
      }
      current[0] = currentX;
      current[1] = currentY;
      current[2] = ctrlPointX;
      current[3] = ctrlPointY;
    }

    private static void drawArc(
        Path p,
        float x0,
        float y0,
        float x1,
        float y1,
        float a,
        float b,
        float theta,
        boolean isMoreThanHalf,
        boolean isPositiveArc) {
      /* Convert rotation angle from degrees to radians */
      double thetaD = Math.toRadians(theta);
      /* Pre-compute rotation matrix entries */
      double cosTheta = Math.cos(thetaD);
      double sinTheta = Math.sin(thetaD);
      /* Transform (x0, y0) and (x1, y1) into unit space */
      /* using (inverse) rotation, followed by (inverse) scale */
      double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
      double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
      double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
      double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
      /* Compute differences and averages */
      double dx = x0p - x1p;
      double dy = y0p - y1p;
      double xm = (x0p + x1p) / 2;
      double ym = (y0p + y1p) / 2;
      /* Solve for intersecting unit circles */
      double dsq = dx * dx + dy * dy;
      if (dsq == 0.0) {
        Log.w("VectorDrawable", " Points are coincident");
        return; /* Points are coincident */
      }
      double disc = 1.0 / dsq - 1.0 / 4.0;
      if (disc < 0.0) {
        Log.w("VectorDrawable", "Points are too far apart " + dsq);
        float adjust = (float) (Math.sqrt(dsq) / 1.99999);
        drawArc(p, x0, y0, x1, y1, a * adjust, b * adjust, theta, isMoreThanHalf, isPositiveArc);
        return; /* Points are too far apart */
      }
      double s = Math.sqrt(disc);
      double sdx = s * dx;
      double sdy = s * dy;
      double cx;
      double cy;
      if (isMoreThanHalf == isPositiveArc) {
        cx = xm - sdy;
        cy = ym + sdx;
      } else {
        cx = xm + sdy;
        cy = ym - sdx;
      }
      double eta0 = Math.atan2((y0p - cy), (x0p - cx));
      double eta1 = Math.atan2((y1p - cy), (x1p - cx));
      double sweep = (eta1 - eta0);
      if (isPositiveArc != (sweep >= 0)) {
        if (sweep > 0) {
          sweep -= 2 * Math.PI;
        } else {
          sweep += 2 * Math.PI;
        }
      }
      cx *= a;
      cy *= b;
      double tcx = cx;
      cx = cx * cosTheta - cy * sinTheta;
      cy = tcx * sinTheta + cy * cosTheta;
      arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
    }

    /**
     * Converts an arc to cubic Bezier segments and records them in p.
     *
     * @param p The target for the cubic Bezier segments
     * @param cx The x coordinate center of the ellipse
     * @param cy The y coordinate center of the ellipse
     * @param a The radius of the ellipse in the horizontal direction
     * @param b The radius of the ellipse in the vertical direction
     * @param e1x E(eta1) x coordinate of the starting point of the arc
     * @param e1y E(eta2) y coordinate of the starting point of the arc
     * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
     * @param start The start angle of the arc on the ellipse
     * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
     */
    private static void arcToBezier(
        Path p,
        double cx,
        double cy,
        double a,
        double b,
        double e1x,
        double e1y,
        double theta,
        double start,
        double sweep) {
      // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
      // and http://www.spaceroots.org/documents/ellipse/node22.html
      // Maximum of 45 degrees per cubic Bezier segment
      int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
      double eta1 = start;
      double cosTheta = Math.cos(theta);
      double sinTheta = Math.sin(theta);
      double cosEta1 = Math.cos(eta1);
      double sinEta1 = Math.sin(eta1);
      double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
      double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
      double anglePerSegment = sweep / numSegments;
      for (int i = 0; i < numSegments; i++) {
        double eta2 = eta1 + anglePerSegment;
        double sinEta2 = Math.sin(eta2);
        double cosEta2 = Math.cos(eta2);
        double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
        double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
        double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
        double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
        double tanDiff2 = Math.tan((eta2 - eta1) / 2);
        double alpha = Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
        double q1x = e1x + alpha * ep1x;
        double q1y = e1y + alpha * ep1y;
        double q2x = e2x - alpha * ep2x;
        double q2y = e2y - alpha * ep2y;
        p.cubicTo((float) q1x, (float) q1y, (float) q2x, (float) q2y, (float) e2x, (float) e2y);
        eta1 = eta2;
        e1x = e2x;
        e1y = e2y;
        ep1x = ep2x;
        ep1y = ep2y;
      }
    }
  }
}
