/*
 * Copyright (C) 2014 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.incallui.videosurface.impl;

import android.graphics.Point;
import android.graphics.SurfaceTexture;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import com.android.dialer.common.LogUtil;
import com.android.incallui.videosurface.protocol.VideoSurfaceDelegate;
import com.android.incallui.videosurface.protocol.VideoSurfaceTexture;
import java.util.Locale;
import java.util.Objects;

/**
 * Represents a {@link TextureView} and its associated {@link SurfaceTexture} and {@link Surface}.
 * Used to manage the lifecycle of these objects across device orientation changes.
 */
public class VideoSurfaceTextureImpl implements VideoSurfaceTexture {
  @SurfaceType private final int surfaceType;
  private final boolean isPixel2017;

  private VideoSurfaceDelegate delegate;
  private TextureView textureView;
  private Surface savedSurface;
  private SurfaceTexture savedSurfaceTexture;
  private Point surfaceDimensions;
  private Point sourceVideoDimensions;
  private boolean isDoneWithSurface;

  public VideoSurfaceTextureImpl(boolean isPixel2017, @SurfaceType int surfaceType) {
    this.isPixel2017 = isPixel2017;
    this.surfaceType = surfaceType;
  }

  @Override
  public void setDelegate(VideoSurfaceDelegate delegate) {
    LogUtil.i("VideoSurfaceTextureImpl.setDelegate", "delegate: " + delegate + " " + toString());
    this.delegate = delegate;
  }

  @Override
  public int getSurfaceType() {
    return surfaceType;
  }

  @Override
  public Surface getSavedSurface() {
    return savedSurface;
  }

  @Override
  public void setSurfaceDimensions(Point surfaceDimensions) {
    LogUtil.i(
        "VideoSurfaceTextureImpl.setSurfaceDimensions",
        "surfaceDimensions: " + surfaceDimensions + " " + toString());
    this.surfaceDimensions = surfaceDimensions;
    if (surfaceDimensions != null && savedSurfaceTexture != null) {
      // Only do this on O (not at least O) because we expect this issue to be fixed in OMR1
      if (VERSION.SDK_INT == VERSION_CODES.O && isPixel2017) {
        LogUtil.i(
            "VideoSurfaceTextureImpl.setSurfaceDimensions",
            "skip setting default buffer size on Pixel 2017 ODR");
        return;
      }
      savedSurfaceTexture.setDefaultBufferSize(surfaceDimensions.x, surfaceDimensions.y);
    }
  }

  @Override
  public Point getSurfaceDimensions() {
    return surfaceDimensions;
  }

  @Override
  public void setSourceVideoDimensions(Point sourceVideoDimensions) {
    this.sourceVideoDimensions = sourceVideoDimensions;
  }

  @Override
  public Point getSourceVideoDimensions() {
    return sourceVideoDimensions;
  }

  @Override
  public void attachToTextureView(TextureView textureView) {
    if (this.textureView == textureView) {
      return;
    }
    LogUtil.i("VideoSurfaceTextureImpl.attachToTextureView", toString());

    if (this.textureView != null) {
      this.textureView.setOnClickListener(null);
      this.textureView.setSurfaceTextureListener(null);
    }

    this.textureView = textureView;
    textureView.setSurfaceTextureListener(new SurfaceTextureListener());
    textureView.setOnClickListener(new OnClickListener());

    boolean areSameSurfaces = Objects.equals(savedSurfaceTexture, textureView.getSurfaceTexture());
    LogUtil.i("VideoSurfaceTextureImpl.attachToTextureView", "areSameSurfaces: " + areSameSurfaces);
    if (savedSurfaceTexture != null && !areSameSurfaces) {
      textureView.setSurfaceTexture(savedSurfaceTexture);
      if (surfaceDimensions != null && createSurface(surfaceDimensions.x, surfaceDimensions.y)) {
        onSurfaceCreated();
      }
    }
    isDoneWithSurface = false;
  }

  @Override
  public void setDoneWithSurface() {
    LogUtil.i("VideoSurfaceTextureImpl.setDoneWithSurface", toString());
    isDoneWithSurface = true;
    if (textureView != null && textureView.isAvailable()) {
      return;
    }
    if (savedSurface != null) {
      onSurfaceReleased();
      savedSurface.release();
      savedSurface = null;
    }
    if (savedSurfaceTexture != null) {
      savedSurfaceTexture.release();
      savedSurfaceTexture = null;
    }
  }

  private boolean createSurface(int width, int height) {
    LogUtil.i(
        "VideoSurfaceTextureImpl.createSurface",
        "width: " + width + ", height: " + height + " " + toString());
    savedSurfaceTexture.setDefaultBufferSize(width, height);
    if (savedSurface != null) {
      savedSurface.release();
    }
    savedSurface = new Surface(savedSurfaceTexture);
    return true;
  }

  private void onSurfaceCreated() {
    if (delegate != null) {
      delegate.onSurfaceCreated(this);
    } else {
      LogUtil.e("VideoSurfaceTextureImpl.onSurfaceCreated", "delegate is null. " + toString());
    }
  }

  private void onSurfaceReleased() {
    if (delegate != null) {
      delegate.onSurfaceReleased(this);
    } else {
      LogUtil.e("VideoSurfaceTextureImpl.onSurfaceReleased", "delegate is null. " + toString());
    }
  }

  @Override
  public String toString() {
    return String.format(
        Locale.US,
        "VideoSurfaceTextureImpl<%s%s%s%s>",
        (surfaceType == SURFACE_TYPE_LOCAL ? "local, " : "remote, "),
        (savedSurface == null ? "no-surface, " : ""),
        (savedSurfaceTexture == null ? "no-texture, " : ""),
        (surfaceDimensions == null
            ? "(-1 x -1)"
            : (surfaceDimensions.x + " x " + surfaceDimensions.y)));
  }

  private class SurfaceTextureListener implements TextureView.SurfaceTextureListener {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture newSurfaceTexture, int width, int height) {
      LogUtil.i(
          "SurfaceTextureListener.onSurfaceTextureAvailable",
          "newSurfaceTexture: "
              + newSurfaceTexture
              + " "
              + VideoSurfaceTextureImpl.this.toString());

      // Where there is no saved {@link SurfaceTexture} available, use the newly created one.
      // If a saved {@link SurfaceTexture} is available, we are re-creating after an
      // orientation change.
      boolean surfaceCreated;
      if (savedSurfaceTexture == null) {
        savedSurfaceTexture = newSurfaceTexture;
        surfaceCreated = createSurface(width, height);
      } else {
        // A saved SurfaceTexture was found.
        LogUtil.i(
            "SurfaceTextureListener.onSurfaceTextureAvailable", "replacing with cached surface...");
        textureView.setSurfaceTexture(savedSurfaceTexture);
        surfaceCreated = true;
      }

      // Inform the delegate that the surface is available.
      if (surfaceCreated) {
        onSurfaceCreated();
      }
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture destroyedSurfaceTexture) {
      LogUtil.i(
          "SurfaceTextureListener.onSurfaceTextureDestroyed",
          "destroyedSurfaceTexture: %s, %s, isDoneWithSurface: %b",
          destroyedSurfaceTexture,
          VideoSurfaceTextureImpl.this.toString(),
          isDoneWithSurface);
      if (delegate != null) {
        delegate.onSurfaceDestroyed(VideoSurfaceTextureImpl.this);
      } else {
        LogUtil.e("SurfaceTextureListener.onSurfaceTextureDestroyed", "delegate is null");
      }

      if (isDoneWithSurface) {
        onSurfaceReleased();
        if (savedSurface != null) {
          savedSurface.release();
          savedSurface = null;
        }
      }
      return isDoneWithSurface;
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
  }

  private class OnClickListener implements View.OnClickListener {
    @Override
    public void onClick(View view) {
      if (delegate != null) {
        delegate.onSurfaceClick(VideoSurfaceTextureImpl.this);
      } else {
        LogUtil.e("OnClickListener.onClick", "delegate is null");
      }
    }
  }
}
