package com.tongyi.videochatdemo;

import static com.tongyi.video_chat_sdk.Constant.TYAvatarRenderType.LOCAL_RENDER_AVATAR_ONLY;
import static com.tongyi.video_chat_sdk.Constant.TYAvatarRenderType.REMOTE_RENDER_AVATAR;
import static com.tongyi.video_chat_sdk.Constant.TYAvatarRenderType.REMOTE_RENDER_AVATAR_ONLY;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.RelativeLayout.LayoutParams;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.alivc.rtc.AliRtcEngine.AliRtcAudioProfile;
import com.tongyi.video_chat_sdk.Constant;
import com.tongyi.video_chat_sdk.Constant.ChatMessageType;
import com.tongyi.video_chat_sdk.Constant.TYVoiceChatMode;
import com.tongyi.video_chat_sdk.Constant.TYVolumeSourceType;
import com.tongyi.video_chat_sdk.TYVideoChat;
import com.tongyi.video_chat_sdk.clientrender.media.util.FileUtil;
import com.tongyi.video_chat_sdk.conv.ConvConstants;
import com.tongyi.video_chat_sdk.conv.ConvConstants.DialogState;
import com.tongyi.video_chat_sdk.data.IChatCallback;
import com.tongyi.video_chat_sdk.data.ILocalAvatarInitCallback;
import com.tongyi.video_chat_sdk.data.TYError;
import com.tongyi.video_chat_sdk.data.TYVoiceChatMessage;
import com.tongyi.video_chat_sdk.data.request.TYDialogConfig;
import com.tongyi.video_chat_sdk.data.response.TYAvatarInitData;
import com.tongyi.video_chat_sdk.util.DeviceUtil;
import com.tongyi.video_chat_sdk.util.ThreadPoolUtil;
import com.tongyi.videochatdemo.databinding.ActivityVideoChatBinding;
import com.tongyi.videochatdemo.databinding.PopupWindowTyBinding;
import com.tongyi.videochatdemo.util.LoadingBox;

import org.json.JSONException;
import org.json.JSONObject;

/**
 * @author flyingfish
 * @date 2024/08/15
 * description:
 */
public class VideoChatActivity extends AppCompatActivity {
    private static final String TAG = VideoChatActivity.class.getSimpleName();
    protected static final String KEY_AUTH_PARAM = "KEY_AVATAR_INIT_DATA", KEY_REQUEST_CONFIG = "KEY_DIALOG_CONFIG";

    private TYVideoChat tyVideoChat;
    private TYAvatarInitData tyAvatarInitData;
    private TYDialogConfig tyDialogConfig;
    private DialogState dialogState = DialogState.DIALOG_IDLE;
    private boolean isInForeground = false;

    protected TYPopupWindow tyPopupWindow;
    protected PopupWindowTyBinding binding;
    protected ActivityVideoChatBinding bindingActivity;

    /// //////////////////////////////////// 启动配置相关Begin ///////////////////////////////////////

    public static boolean launchVideoChat(AppCompatActivity activity, TYAvatarInitData avatarInitData,
                                          TYDialogConfig dialogConfig) {
        Intent launchIntent = new Intent(activity, VideoChatActivity.class);
        Log.d(TAG, "launchVideoChat:avatarInitData:" + avatarInitData + ";dialogConfig:" + dialogConfig);
        launchIntent.putExtra(KEY_AUTH_PARAM, avatarInitData);
        launchIntent.putExtra(KEY_REQUEST_CONFIG, dialogConfig);
        activity.startActivity(launchIntent);
        return true;
    }

    private void handleLaunchParams() {
        tyAvatarInitData = (TYAvatarInitData) getIntent().getSerializableExtra(KEY_AUTH_PARAM);
        tyDialogConfig = (TYDialogConfig) getIntent().getSerializableExtra(KEY_REQUEST_CONFIG);
        Log.d(TAG, "handleLaunchParams:authParams:" + tyAvatarInitData + ";requestConfig:" + tyDialogConfig);
    }

    /// //////////////////////////////////// UI相关Begin ///////////////////////////////////////

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DeviceUtil.setStatusBarColor(this);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        handleLaunchParams();

        bindingActivity = ActivityVideoChatBinding.inflate(getLayoutInflater());
        setContentView(bindingActivity.getRoot());

        tyPopupWindow = new TYPopupWindow(this);
        binding = tyPopupWindow.binding;
        bindingActivity.rlRoot.post(() -> tyPopupWindow.show(bindingActivity.rlRoot));

        binding.tvLogs.setMovementMethod(ScrollingMovementMethod.getInstance());


        binding.cbMute.setOnCheckedChangeListener((buttonView, isChecked) -> tyVideoChat.muteLocalMic(isChecked));
        binding.cbShowContent.setOnCheckedChangeListener(((buttonView, isChecked) -> binding.tvLogs.setVisibility(isChecked ? View.VISIBLE : View.GONE)));

        int[] heights = {16, 32, 12, 28, 18};
        binding.rpvAudio.setSingleColor(getColor(R.color.white));
        binding.rpvAudio.setBarMaxHeightsInDp(heights);
        binding.rpvAudio.setCircleRadiusInDp(4);
        binding.rpvAudio.setSpacingInDp(4);
        binding.rpvAudio.play();

        binding.tvState.setOnClickListener(v -> {
            if (sdkIsReady) {
                tyVideoChat.interrupt();
            }
        });

        binding.tvBack.setOnClickListener(v -> finish());

        //push2TalkView.setVisibility(isPush2TalkMode() ? View.VISIBLE : View.INVISIBLE);
        binding.llPush2Talk.setOnTouchListener((v, event) -> {
            binding.llPush2Talk.performClick();
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.d(TAG, "onActionDown:eventX:" + event.getX());
                    tyVideoChat.startSpeech();
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    boolean xInViewRegion = event.getX() > 0 && event.getX() < v.getWidth();
                    boolean yInViewRegion = event.getY() > 0 && event.getY() < v.getHeight();
                    boolean isCanceled = !xInViewRegion || !yInViewRegion;
                    Log.d(TAG, "onActionUp:eventX:" + event.getX() + ";eventY:" + event.getY()
                            + ";isCanceled:" + isCanceled);

                    // 松开手指时，离开按键区域，取消发送，在按键区域内，发送音频
                    if (isCanceled) {
                        tyVideoChat.cancelSpeech();
                    } else {
                        tyVideoChat.stopSpeech();
                    }
                    break;
                default:
                    break;
            }
            return false;
        });

        // startSdk
        tyVideoChat = TYVideoChat.getInstance();
        // config audio profile added by flyingfish 2025.04.16
        tyVideoChat.getRtcConfig().setAudioProfile(AliRtcAudioProfile.AliRtcEngineBasicQualityMode);
        // config audio volume value in [0,400]
        tyVideoChat.getRtcConfig().setPlayOutAudioVolume(100);
        tyVideoChat.getRtcConfig().setRecordAudioVolumeBeforeVAD(100);

        if (isRemoteRenderType()) {
            // 云渲染模式，直接走init->start流程即可
            tyVideoChat.init(this, tyAvatarInitData, tyDialogConfig);
            tyVideoChat.start(chatCallback);
            binding.tvState.setText("连接中...");
        } else {
            // 端渲染模式，先走init，资源准备完成后，再走start
            tyVideoChat.init(this, tyDialogConfig.getLicense(), tyAvatarInitData, tyDialogConfig, localAvatarInitCallback);
        }

        binding.tvRequestToRespondPrompt.setOnClickListener(v -> {
            if (sdkIsReady) {
                String content = "请说个故事";
                tyVideoChat.requestToRespond("prompt", content);
                Toast.makeText(VideoChatActivity.this, "RequestToRespond:" + content, Toast.LENGTH_SHORT).show();
            }
        });

        binding.tvRequestToRespondTranscript.setOnClickListener(v -> {
            if (sdkIsReady) {
                String content = "请说个故事";
                tyVideoChat.requestToRespond("transcript", content);
                Toast.makeText(VideoChatActivity.this, "RequestToRespond:" + content, Toast.LENGTH_SHORT).show();
            }
        });

        binding.getRoot().setOnClickListener(v -> {
            if (sdkIsReady) {
                tyVideoChat.interrupt();
            }
        });

        binding.tvDriveAvatarByAudio.setOnClickListener(v -> ThreadPoolUtil.runOnSubThread(() -> {
            if (!isAvatarOnlyRenderType()) {
                Log.e(TAG, "onDriveAvatarByAudio but not avatar only render type");
                return;
            }
            if (dialogState != ConvConstants.DialogState.DIALOG_LISTENING) {
                Log.e(TAG, "onDriveAvatarByAudio but not in listening state");
                return;
            }
            byte[] audioDataInBundle = FileUtil.readFromAsset(this, "test/Audio_16K.pcm");
            tyVideoChat.pushAudioData(audioDataInBundle, true);
        }));
    }

    @Override
    protected void onPause() {
        super.onPause();
        isInForeground = false;
    }

    @Override
    protected void onResume() {
        super.onResume();
        isInForeground = true;
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.e("TYVideoChat", "call onStop");
        if (avatarAssetPreparing) {
            Log.d(TAG, "call onStop with avatar asset preparing");
            return;
        }
        tyPopupWindow.dismiss();
        if (isFinishing()) {
            Log.e("TYVideoChat", "call onStop isFinishing release sdk");
            // exit是同步方法，放到子线程调用
            ThreadPoolUtil.runOnSubThread(() -> {
                tyVideoChat.exit();
                tyVideoChat = null;
            });
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        finish();
    }

    private void appendLogMessage(String message) {
        appendLogMessage(binding.tvLogs, message);
    }

    private void appendLogMessage(EditText tv, String message) {
        runOnUiThread(() -> {
            if ((tv == binding.tvLogs || tv == binding.tvPerformanceMonitor) && binding.cbShowContent.isChecked()) {
                tv.setVisibility(View.VISIBLE);
            }
            tv.setText(message);
            tv.setSelection(tv.getText().length());
        });
    }

    private boolean isPush2TalkMode() {
        return TYVoiceChatMode.PUSH2TALK.equals(tyDialogConfig.getMode());
    }

    protected boolean isAvatarOnlyRenderType() {
        return REMOTE_RENDER_AVATAR_ONLY.equals(tyDialogConfig.getRenderType())
                || LOCAL_RENDER_AVATAR_ONLY.equals(tyDialogConfig.getRenderType());
    }

    protected boolean isRemoteRenderType() {
        return REMOTE_RENDER_AVATAR.equals(tyDialogConfig.getRenderType())
                || REMOTE_RENDER_AVATAR_ONLY.equals(tyDialogConfig.getRenderType());
    }

    /// //////////////////////////////////// 资源准备相关 ///////////////////////////////////////

    private boolean avatarAssetPreparing = false;
    private LoadingBox loadingBox = new LoadingBox();

    /**
     * 端渲染模式，SDK需要在init中做一些资源加载操作
     */
    private final ILocalAvatarInitCallback localAvatarInitCallback = new ILocalAvatarInitCallback() {
        @Override
        public void onLocalAvatarInitPreparing(@NonNull String message) {
            avatarAssetPreparing = true;
            Log.d(TAG, "assetPreparing:" + message);
            runOnUiThread(() -> {
                loadingBox.showLoadingBox(VideoChatActivity.this, message);
                appendLogMessage(binding.tvLogs, message);
            });
        }

        @Override
        public void onLocalAvatarInitComplete(@NonNull SurfaceView renderView) {
            // 资源准备完成，开启端渲染流程
            Log.d(TAG, "assetPreparedComplete start init client render");
            avatarAssetPreparing = false;
            if (isFinishing() || !isInForeground) {
                Log.e(TAG, "assetPreparedComplete but activity is not in foreground:finish:" + isFinishing()
                        + ";isInForeground:" + isInForeground);
                runOnUiThread(() -> loadingBox.closeLoadingBox());
                return;
            }

            runOnUiThread(() -> {
                loadingBox.closeLoadingBox();
                chatCallback.onGotRenderView(renderView);
                binding.tvState.setText("连接中...");
                tyVideoChat.start(chatCallback);
            });
        }

        @Override
        public void onLocalAvatarInitError(String errorMessage) {
            runOnUiThread(() -> {
                Log.d(TAG, "onLocalAvatarInitError:" + errorMessage);
                avatarAssetPreparing = false;
                loadingBox.closeLoadingBox();
                appendLogMessage("onAvatarInitError:" + errorMessage);
            });
        }
    };

    /////////////////////////////////////// 启动逻辑相关 ///////////////////////////////////////

    /**
     * sdk start后需要做一些初始化和准备工作，完成后将此标志置为true，此时可以调用业务接口，false时调用业务接口会失败
     */
    private boolean sdkIsReady = false;

    /**
     * 对话过程回调
     */
    private final IChatCallback chatCallback = new IChatCallback() {

        @Override
        public void onStartResult(boolean isSuccess, TYError errorInfo) {
            Log.d(TAG, "onStartResult:isSuccess:" + isSuccess + ";errorInfo:" + errorInfo);
            appendLogMessage(isSuccess ? "鉴权、加入RTC通道成功" : ("鉴权失败:" + errorInfo));
            runOnUiThread(() -> {
                binding.llPush2Talk.setVisibility(isPush2TalkMode() ? View.VISIBLE : View.INVISIBLE);
                binding.tvDriveAvatarByAudio.setVisibility(isAvatarOnlyRenderType() ? View.VISIBLE : View.GONE);
                binding.llRequestToRespond.setVisibility(isAvatarOnlyRenderType() ? View.GONE : View.VISIBLE);

                // Push2Talk或者单独数字人模式，不让用户mute麦克风，没有意义
                binding.cbMute.setVisibility(isAvatarOnlyRenderType() || isPush2TalkMode() ? View.GONE : View.VISIBLE);
                // Push2Talk or 单独数字人，默认隐藏配置，用户可手动打开各个选项
                if (isPush2TalkMode() || isAvatarOnlyRenderType()) {
                    binding.cbShowContent.setChecked(false);
                }
            });
        }

        @Override
        public void onInterruptResult(boolean isSuccess, @Nullable TYError errorInfo) {
            Log.d(TAG, "onInterruptResult:isSuccess:" + isSuccess + ";errorInfo:" + errorInfo);
        }

        @Override
        public void onReadyToSpeech() {
            Log.d(TAG, "onReadyToSpeech:isPush2Talk:" + isPush2TalkMode());
            sdkIsReady = true;
        }

        @Override
        public void onStateChanged(@NonNull DialogState state) {
            runOnUiThread(() -> {
                dialogState = state;
                String stateMsg = "idle";
                binding.rpvAudio.setVisibility(
                        state == DialogState.DIALOG_LISTENING ? View.VISIBLE : View.INVISIBLE);
                binding.tvLogs.setVisibility(View.GONE);

                // @TODO push2talk 暂时不支持打断，在思考和说的状态时，主动隐藏
                boolean stateOfRespondingOrThinking =
                        state == DialogState.DIALOG_RESPONDING || state == DialogState.DIALOG_THINKING;
                binding.llPush2Talk.setVisibility(
                        isPush2TalkMode() && !stateOfRespondingOrThinking ? View.VISIBLE : View.INVISIBLE);

                if (state == DialogState.DIALOG_IDLE) {
                    stateMsg = "Idle";
                } else if (state == DialogState.DIALOG_LISTENING) {
                    stateMsg = "你说，我正在听";
                } else if (state == DialogState.DIALOG_RESPONDING) {
                    stateMsg = isPush2TalkMode() ? "我正在说，暂不支持打断" : "我正在说，轻点可打断我";
                    if (binding.cbShowContent.isChecked())
                        binding.tvLogs.setVisibility(View.VISIBLE);
                } else {
                    stateMsg = "我正在想";
                }
                binding.tvState.setText(stateMsg);
            });
        }

        @Override
        public void onVolumeChanged(float audioLevel, @NonNull TYVolumeSourceType audioType) {
            runOnUiThread(() -> {
                // 音量条的波动效果，可自行调整
                //Log.d(TAG, "onConvDataSendCallback level:" + audioLevel + ";audioType:" + audioType);
                if (audioType == TYVolumeSourceType.MIC) {
                    binding.rpvAudio.onRmsChanged(audioLevel * 5f);
                }
            });
        }

        @Override
        public void onMessageReceived(@NonNull TYVoiceChatMessage chatMessage) {
            Log.d(TAG, "onMessageReceived:" + chatMessage);
            String messageFrom = chatMessage.getChatMessageType() == ChatMessageType.SPEAKING ? "Me" : "AI";
            appendLogMessage(messageFrom + ":" + chatMessage.getChatMessageText());
        }

        @Override
        public void onGotRenderView(@NonNull SurfaceView renderView) {
            runOnUiThread(() -> {
                binding.tvBack.setVisibility(View.VISIBLE);
                binding.tvLogs.setVisibility(View.GONE);

                if (renderView == bindingActivity.rlRoot.getChildAt(0)) {
                    Log.e(TAG, "render view already added");
                    return;
                }

                LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT);
                renderView.setOnClickListener(v -> {
                    if (sdkIsReady) {
                        tyVideoChat.interrupt();
                    }
                });
                bindingActivity.rlRoot.addView(renderView, 0, layoutParams);
            });
        }

        @Override
        public void onErrorReceived(@NonNull TYError errorInfo) {
            Log.e(TAG, "onErrorReceived:" + errorInfo);
            appendLogMessage("onErrorReceived:" + errorInfo);
        }

        @Override
        public void onPerformanceInfoTrack(@NonNull Constant.TYPerformanceInfoType performanceInfoType, @NonNull String performanceInfo) {
            Log.d(TAG, "onPerformanceInfoTrack:" + performanceInfo);
            if (performanceInfoType == Constant.TYPerformanceInfoType.TIMESTAMP_STATISTICS) {
                // 耗时统计
                try {
                    JSONObject debugInfoObject = new JSONObject(performanceInfo);
                    if (!debugInfoObject.has("totalDelay")) {
                        return;
                    }
                    long ttsDelay = debugInfoObject.optLong("ttsDelay");
                    long llmDelay = debugInfoObject.optLong("llmDelay");
                    long avatarDelay = debugInfoObject.optLong("avatarDelay");
                    long totalDelay = debugInfoObject.optLong("totalDelay");

                    String builder = "LLM:" + llmDelay + "\n" +
                            "TTS:" + ttsDelay + "\n" +
                            "Avatar:" + avatarDelay;

                    appendLogMessage(binding.tvPerformanceMonitor, builder);
                } catch (JSONException e) {
                    Log.e(TAG, "Failed to parse JSON", e);
                }
            }
        }

        @Override
        public void onLocalAvatarDidAudioLag() {
            Log.d(TAG, "onLocalAvatarDidAudioLag");
        }

        @Override
        public void onLocalAvatarRealtimeFps(float realtimeFps) {
            Log.d(TAG, "onLocalAvatarRealtimeFps:" + realtimeFps);
        }

        @Override
        public void onLocalAvatarRealtimeBSInfo(@NonNull String s) {
            Log.d(TAG, "onLocalAvatarRealtimeBSInfo:" + s);
        }

        /**
         * 端渲染数字人待播放的音频数据
         * @param audioData 音频数据
         * @param sampleRate 音频采样率
         * @return true，标识音频播放由上层调用者处理，false，标识音频播放由SDK内部处理
         */
        @Override
        public boolean onLocalAvatarAudioDataToPlay(@NonNull byte[] audioData, int sampleRate) {
            return false;
        }
    };
}

