/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * 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.android.dialer.callintent;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.text.TextUtils;
import com.android.dialer.common.Assert;
import com.android.dialer.performancereport.PerformanceReport;
import com.android.dialer.util.CallUtil;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import com.google.protobuf.InvalidProtocolBufferException;

/** Creates an intent to start a new outgoing call. */
@AutoValue
public abstract class CallIntent implements Parcelable {
  private static int lightbringerButtonAppearInExpandedCallLogItemCount = 0;
  private static int lightbringerButtonAppearInCollapsedCallLogItemCount = 0;
  private static int lightbringerButtonAppearInSearchCount = 0;

  abstract Uri number();

  abstract CallSpecificAppData callSpecificAppData();

  @Nullable
  abstract PhoneAccountHandle phoneAccountHandle();

  abstract boolean isVideoCall();

  @Nullable
  abstract String callSubject();

  abstract boolean allowAssistedDial();

  abstract ImmutableMap<String, String> stringInCallUiIntentExtras();

  abstract ImmutableMap<String, Long> longInCallUiIntentExtras();

  abstract ImmutableMap<String, String> stringPlaceCallExtras();

  abstract ImmutableMap<String, Long> longPlaceCallExtras();

  public static Builder builder() {
    return new AutoValue_CallIntent.Builder().setIsVideoCall(false).setAllowAssistedDial(false);
  }

  public abstract Builder toBuilder();

  /** Builder class for CallIntent info. */
  @AutoValue.Builder
  public abstract static class Builder {
    public Builder setTelNumber(String number) {
      return setNumber(CallUtil.getCallUri(Assert.isNotNull(number)));
    }

    public Builder setVoicemailNumber(@Nullable PhoneAccountHandle phoneAccountHandle) {
      return setNumber(Uri.fromParts(PhoneAccount.SCHEME_VOICEMAIL, "", null))
          .setPhoneAccountHandle(phoneAccountHandle);
    }

    public abstract Builder setNumber(@NonNull Uri number);

    public Builder setCallInitiationType(CallInitiationType.Type callInitiationType) {
      return setCallSpecificAppData(
          CallSpecificAppData.newBuilder().setCallInitiationType(callInitiationType).build());
    }

    abstract CallSpecificAppData callSpecificAppData();

    public abstract Builder setCallSpecificAppData(
        @NonNull CallSpecificAppData callSpecificAppData);

    public abstract Builder setPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle);

    public abstract Builder setIsVideoCall(boolean isVideoCall);

    public abstract Builder setCallSubject(String callSubject);

    public abstract Builder setAllowAssistedDial(boolean allowAssistedDial);

    abstract ImmutableMap.Builder<String, String> stringInCallUiIntentExtrasBuilder();

    abstract ImmutableMap.Builder<String, Long> longInCallUiIntentExtrasBuilder();

    public Builder addInCallUiIntentExtra(String key, String value) {
      stringInCallUiIntentExtrasBuilder().put(key, value);
      return this;
    }

    public Builder addInCallUiIntentExtra(String key, Long value) {
      longInCallUiIntentExtrasBuilder().put(key, value);
      return this;
    }

    abstract ImmutableMap.Builder<String, String> stringPlaceCallExtrasBuilder();

    abstract ImmutableMap.Builder<String, Long> longPlaceCallExtrasBuilder();

    public Builder addPlaceCallExtra(String key, String value) {
      stringPlaceCallExtrasBuilder().put(key, value);
      return this;
    }

    public Builder addPlaceCallExtra(String key, Long value) {
      longPlaceCallExtrasBuilder().put(key, value);
      return this;
    }

    abstract CallIntent autoBuild();

    public Intent build() {
      CallSpecificAppData.Builder builder =
          CallSpecificAppData.newBuilder(callSpecificAppData())
              .setLightbringerButtonAppearInExpandedCallLogItemCount(
                  lightbringerButtonAppearInExpandedCallLogItemCount)
              .setLightbringerButtonAppearInCollapsedCallLogItemCount(
                  lightbringerButtonAppearInCollapsedCallLogItemCount)
              .setLightbringerButtonAppearInSearchCount(lightbringerButtonAppearInSearchCount);
      lightbringerButtonAppearInExpandedCallLogItemCount = 0;
      lightbringerButtonAppearInCollapsedCallLogItemCount = 0;
      lightbringerButtonAppearInSearchCount = 0;

      if (PerformanceReport.isRecording()) {
        builder
            .setTimeSinceAppLaunch(PerformanceReport.getTimeSinceAppLaunch())
            .setTimeSinceFirstClick(PerformanceReport.getTimeSinceFirstClick())
            .addAllUiActionsSinceAppLaunch(PerformanceReport.getActions())
            .addAllUiActionTimestampsSinceAppLaunch(PerformanceReport.getActionTimestamps())
            .setStartingTabIndex(PerformanceReport.getStartingTabIndex());
        PerformanceReport.stopRecording();
      }

      setCallSpecificAppData(builder.build());

      // Validate CallIntent.
      CallIntent callIntent = autoBuild();
      Assert.isNotNull(callIntent.number());
      Assert.isNotNull(callIntent.callSpecificAppData());
      Assert.checkArgument(
          callIntent.callSpecificAppData().getCallInitiationType()
              != CallInitiationType.Type.UNKNOWN_INITIATION);

      return autoBuild().newIntent();
    }
  }

  // Creates the intent which can start a call
  private Intent newIntent() {
    Intent intent = new Intent(Intent.ACTION_CALL, number());

    intent.putExtra(
        TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
        isVideoCall() ? VideoProfile.STATE_BIDIRECTIONAL : VideoProfile.STATE_AUDIO_ONLY);

    Bundle inCallUiIntentExtras = createInCallUiIntentExtras();
    inCallUiIntentExtras.putLong(
        Constants.EXTRA_CALL_CREATED_TIME_MILLIS, SystemClock.elapsedRealtime());

    intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, inCallUiIntentExtras);

    if (phoneAccountHandle() != null) {
      intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle());
    }

    if (!TextUtils.isEmpty(callSubject())) {
      intent.putExtra(TelecomManager.EXTRA_CALL_SUBJECT, callSubject());
    }

    intent.putExtras(createPlaceCallExtras());

    return intent;
  }

  private Bundle createInCallUiIntentExtras() {
    Bundle bundle = new Bundle();
    stringInCallUiIntentExtras().forEach(bundle::putString);
    longInCallUiIntentExtras().forEach(bundle::putLong);
    CallIntentParser.putCallSpecificAppData(bundle, callSpecificAppData());
    return bundle;
  }

  private Bundle createPlaceCallExtras() {
    Bundle bundle = new Bundle();
    stringPlaceCallExtras().forEach(bundle::putString);
    longPlaceCallExtras().forEach(bundle::putLong);
    CallIntentParser.putCallSpecificAppData(bundle, callSpecificAppData());
    return bundle;
  }

  public static void increaseLightbringerCallButtonAppearInExpandedCallLogItemCount() {
    CallIntent.lightbringerButtonAppearInExpandedCallLogItemCount++;
  }

  public static void increaseLightbringerCallButtonAppearInCollapsedCallLogItemCount() {
    CallIntent.lightbringerButtonAppearInCollapsedCallLogItemCount++;
  }

  public static void increaseLightbringerCallButtonAppearInSearchCount() {
    CallIntent.lightbringerButtonAppearInSearchCount++;
  }

  @VisibleForTesting
  public static int getLightbringerButtonAppearInExpandedCallLogItemCount() {
    return lightbringerButtonAppearInExpandedCallLogItemCount;
  }

  @VisibleForTesting
  public static int getLightbringerButtonAppearInCollapsedCallLogItemCount() {
    return lightbringerButtonAppearInCollapsedCallLogItemCount;
  }

  @VisibleForTesting
  public static int getLightbringerButtonAppearInSearchCount() {
    return lightbringerButtonAppearInSearchCount;
  }

  @VisibleForTesting(otherwise = VisibleForTesting.NONE)
  public static void clearLightbringerCounts() {
    lightbringerButtonAppearInCollapsedCallLogItemCount = 0;
    lightbringerButtonAppearInExpandedCallLogItemCount = 0;
    lightbringerButtonAppearInSearchCount = 0;
  }

  @Override
  public int describeContents() {
    return 0;
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeParcelable(number(), flags);
    dest.writeByteArray(callSpecificAppData().toByteArray());
    dest.writeParcelable(phoneAccountHandle(), flags);
    dest.writeInt(isVideoCall() ? 1 : 0);
    dest.writeString(callSubject());
    dest.writeInt(allowAssistedDial() ? 1 : 0);
    Bundle stringInCallUiIntentExtrasBundle = new Bundle();
    stringInCallUiIntentExtras().forEach(stringInCallUiIntentExtrasBundle::putString);
    dest.writeBundle(stringInCallUiIntentExtrasBundle);
    Bundle longInCallUiIntentExtrasBundle = new Bundle();
    longInCallUiIntentExtras().forEach(longInCallUiIntentExtrasBundle::putLong);
    dest.writeBundle(longInCallUiIntentExtrasBundle);
  }

  // @TODO(justinmcclain): Investigate deleting the parcelable logic and instead switching
  // to using an internal proto for serialization.
  public static final Creator<CallIntent> CREATOR =
      new Creator<CallIntent>() {
        @Override
        public CallIntent createFromParcel(Parcel source) {
          CallIntent.Builder callIntentBuilder = builder();
          ClassLoader classLoader = CallIntent.class.getClassLoader();
          callIntentBuilder.setNumber(source.readParcelable(classLoader));
          CallSpecificAppData data;
          try {
            data = CallSpecificAppData.parseFrom(source.createByteArray());
          } catch (InvalidProtocolBufferException e) {
            data = CallSpecificAppData.getDefaultInstance();
          }
          callIntentBuilder
              .setCallSpecificAppData(data)
              .setPhoneAccountHandle(source.readParcelable(classLoader))
              .setIsVideoCall(source.readInt() != 0)
              .setCallSubject(source.readString())
              .setAllowAssistedDial(source.readInt() != 0);
          Bundle stringInCallUiIntentExtrasBundle = source.readBundle(classLoader);
          for (String key : stringInCallUiIntentExtrasBundle.keySet()) {
            callIntentBuilder.addInCallUiIntentExtra(
                key, stringInCallUiIntentExtrasBundle.getString(key));
          }
          Bundle longInCallUiIntentExtrasBundle = source.readBundle(classLoader);
          for (String key : longInCallUiIntentExtrasBundle.keySet()) {
            callIntentBuilder.addInCallUiIntentExtra(
                key, longInCallUiIntentExtrasBundle.getLong(key));
          }
          return callIntentBuilder.autoBuild();
        }

        @Override
        public CallIntent[] newArray(int size) {
          return new CallIntent[0];
        }
      };
}
