content: /*
   * Copyright 2019 ACINQ SAS
   *
   * 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 fr.acinq.eclair.wallet.models;
  
  import android.os.Parcel;
  import android.os.Parcelable;
  import fr.acinq.eclair.channel.ChannelException;
  import fr.acinq.eclair.payment.LocalFailure;
  import fr.acinq.eclair.payment.PaymentFailure;
  import fr.acinq.eclair.payment.RemoteFailure;
  import fr.acinq.eclair.payment.UnreadableRemoteFailure;
  import fr.acinq.eclair.router.RouteNotFound$;
  import fr.acinq.eclair.router.Router;
  import scala.collection.JavaConverters;
  
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * Wraps information about a failed lightning payment returned by eclair-core. Implements Parcelable
   * so that it can be passed between activities with intents.
   * <ul>
   * <li>The <b>type</b> field tells if this error is a remote or a local failure.
   * <li>The <b>cause</b> field contains a message detailing the reason of this failure.
   * <li>The <b>origin</b> field should be the id of the node from which this error comes from. May be null (for local failure).
   * <li>The <b>originChannelId</b> is the id of the channel which rejected the payment. May be null.
   * <li>The <b>hops</b> field is set only for the remote failures and describes the route used by the failed payment.
   */
  public class LightningPaymentError implements Parcelable {
  
    public static final Creator<LightningPaymentError> CREATOR = new Creator<LightningPaymentError>() {
      @Override
      public LightningPaymentError createFromParcel(Parcel in) {
        return new LightningPaymentError(in);
      }
  
      @Override
      public LightningPaymentError[] newArray(int size) {
        return new LightningPaymentError[size];
      }
    };
  
    private String type;
    private String cause;
    private String origin;
    private String originChannelId;
    private List<String> hops;
  
    public LightningPaymentError(String type, String cause, String origin, String originChannelId, List<String> hops) {
      this.type = type;
      this.cause = cause;
      this.origin = origin;
      this.originChannelId = originChannelId;
      this.hops = hops == null ? new ArrayList<String>() : hops;
    }
  
    public LightningPaymentError(Parcel in) {
      type = in.readString();
      cause = in.readString();
      origin = in.readString();
      originChannelId = in.readString();
      List<String> h = new ArrayList<>();
      in.readList(h, List.class.getClassLoader());
    }
  
    /**
     * Parses a {@link PaymentFailure} sent by eclair core and generates a {@link LightningPaymentError}.
     * According to the failure type, the resulting error may contain a list of the nodes in the failed route.
     * The type of the error is always set, as well as the cause, be it unknown.
     *
     * @param failure failure in the payment route
     * @return
     */
    public static LightningPaymentError generateDetailedErrorCause(final PaymentFailure failure) {
      if (failure instanceof RemoteFailure) {
        final RemoteFailure rf = (RemoteFailure) failure;
        final String type = rf.getClass().getSimpleName();
        final String cause = rf.e().failureMessage().message();
        final String origin = rf.e().originNode().toString();
        String originChannelId = null;
        final List<String> hopsNodesPK = new ArrayList<>();
        if (rf.route().size() > 0) {
          final List<Router.Hop> hops = JavaConverters.seqAsJavaListConverter(rf.route()).asJava();
          for (int hi = 0; hi < hops.size(); hi++) {
            Router.Hop h = hops.get(hi);
            if (hi == 0) {
              hopsNodesPK.add(h.nodeId().toString());
            }
            if (origin.equals(h.nodeId().toString()) && h instanceof Router.ChannelHop) {
              originChannelId = ((Router.ChannelHop) h).lastUpdate().shortChannelId().toString();
            }
            hopsNodesPK.add(h.nextNodeId().toString());
          }
        }
        return new LightningPaymentError(type, cause, origin, originChannelId, hopsNodesPK);
  
      } else if (failure instanceof LocalFailure) {
        final LocalFailure lf = (LocalFailure) failure;
        final String type = "Local";
        String cause;
        String originChannelId = null;
        Throwable t = lf.t();
        if (t instanceof RouteNotFound$) {
          cause = "Route not found.";
        } else if (t instanceof ChannelException){
          cause = t.getMessage();
          originChannelId = ((ChannelException) t).channelId().toString();
        } else {
          cause = t.getClass().getSimpleName();
        }
        return new LightningPaymentError(type, cause, null, originChannelId, null);
  
      } else if (failure instanceof UnreadableRemoteFailure) {
        final UnreadableRemoteFailure unreadable = (UnreadableRemoteFailure) failure;
        final String type = unreadable.getClass().getSimpleName();
        final String cause = "A peer on the route failed the payment with an non readable cause";
        final List<String> hopsNodesPK = new ArrayList<>();
        if (unreadable.route().size() > 0) {
          final List<Router.Hop> hops = JavaConverters.seqAsJavaListConverter(unreadable.route()).asJava();
          for (int hi = 0; hi < hops.size(); hi++) {
            Router.Hop h = hops.get(hi);
            if (hi == 0) {
              hopsNodesPK.add(h.nodeId().toString());
            }
            hopsNodesPK.add(h.nextNodeId().toString());
          }
        }
        return new LightningPaymentError(type, cause, null, null, hopsNodesPK);
  
      } else {
        return new LightningPaymentError("Unknown Error:" + failure.getClass().getSimpleName(), "Unknown Cause", null, null, null);
      }
    }
  
    public String getType() {
      return type;
    }
  
    public String getCause() {
      return cause;
    }
  
    public String getOrigin() {
      return origin;
    }
  
    public String getOriginChannelId() {
      return originChannelId;
    }
  
    public List<String> getHops() {
      return hops;
    }
  
    @Override
    public int describeContents() {
      return 0;
    }
  
    @Override
    public void writeToParcel(Parcel parcel, int flags) {
      parcel.writeString(type);
      parcel.writeString(cause);
      parcel.writeString(origin);
      parcel.writeString(originChannelId);
      parcel.writeList(hops);
    }
  }
  