package cc.winboll.studio.libappbase;

import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.HorizontalScrollView;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.Spinner;
import android.widget.TextView;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

/**
 * @Author ZhanGSKen<zhangsken@qq.com>
 * @Date 2024/08/12 14:36:18
 * @Describe 日志可视化自定义 View（继承 RelativeLayout）
 * 核心功能：日志展示、日志级别筛选、TAG 过滤（启用/禁用）、TAG 搜索定位、日志清理/复制、视图交互控制
 * 依赖 LogUtils 进行日志读写，通过 LogViewThread 监听日志文件变化并自动刷新
 */
public class LogView extends RelativeLayout {

    /** 当前 View 的日志 TAG（用于调试输出） */
    public static final String TAG = "LogView";

    /** 日志处理中标志（避免并发刷新，volatile 保证多线程可见性） */
    private volatile boolean mIsHandling;
    /** 新日志添加标志（标记有未处理的新日志，volatile 保证多线程可见性） */
    private volatile boolean mIsAddNewLog;

    /** 上下文对象（用于布局加载、系统服务获取） */
    private Context mContext;
    /** 日志滚动视图（包裹日志文本，支持垂直滚动） */
    private ScrollView mLogScrollView;
    /** 日志文本展示控件（显示所有日志内容） */
    private TextView mLogTextView;
    /** TAG 搜索输入框（用于搜索并定位目标 TAG） */
    private EditText mTagSearchEt;
    /** 文本选择开关（控制是否允许选中日志文本） */
    private CheckBox mTextSelectableCb;
    /** 全选 TAG 开关（控制所有 TAG 的启用/禁用） */
    private CheckBox mSelectAllTagCb;
    /** TAG 列表适配器（绑定 TAG 数据与视图，处理勾选状态） */
    private TAGListAdapter mTagListAdapter;
    /** 日志监听线程（监听日志文件变化，触发视图刷新） */
    private LogViewThread mLogViewThread;
    /** 日志视图 Handler（主线程更新 UI，避免跨线程操作） */
    private LogViewHandler mLogViewHandler;
    /** 日志级别选择下拉框（用于切换全局日志输出级别） */
    private Spinner mLogLevelSpinner;
    /** 日志级别适配器（绑定 LogUtils.LOG_LEVEL 枚举与 Spinner） */
    private ArrayAdapter<CharSequence> mLogLevelAdapter;
    /** TAG 水平列表视图（横向展示所有 TAG，支持滚动） */
    private HorizontalListView mTagHorizontalListView;

    // ====================== 构造方法（初始化视图） ======================
    public LogView(Context context) {
        super(context);
        initView(context);
    }

    public LogView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public LogView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }

    public LogView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initView(context);
    }

    /**
     * 启动日志监听与展示
     * 1. 初始化并启动 LogViewThread（监听日志文件变化）；
     * 2. 初始加载并展示日志内容。
     */
    public void start() {
        mLogViewThread = new LogViewThread(this);
        mLogViewThread.start();
        showAndScrollLogView(); // 初始显示日志并滚动到底部
    }

    /**
     * 滚动日志到底部（确保最新日志可见）
     * 运行在主线程，通过 post 提交 Runnable 避免 UI 线程阻塞
     */
    private void scrollLogToBottom() {
        mLogScrollView.post(new Runnable() {
				@Override
				public void run() {
					// 滚动到 ScrollView 底部（FOCUS_DOWN 表示聚焦到底部）
					mLogScrollView.fullScroll(ScrollView.FOCUS_DOWN);
					// 标记日志处理完成
					mLogViewHandler.setIsHandling(false);
					// 检查是否有未处理的新日志，有则再次触发刷新
					if (mLogViewHandler.isAddNewLog()) {
						mLogViewHandler.setIsAddNewLog(false);
						Message refreshMsg = mLogViewHandler.obtainMessage(LogViewHandler.MSG_LOG_REFRESH);
						mLogViewHandler.sendMessage(refreshMsg);
					}
				}
			});
    }

    /**
     * 初始化视图组件（加载布局、绑定控件、设置监听）
     * @param context 上下文对象
     */
    private void initView(Context context) {
        mContext = context;
        mLogViewHandler = new LogViewHandler(); // 初始化主线程 Handler

        // 加载日志视图布局（R.layout.view_log 为自定义布局文件）
        View rootView = LayoutInflater.from(context).inflate(R.layout.view_log, this, true);
        // 绑定布局控件（通过 ID 找到对应组件）
        bindViews(rootView);

        // 设置 TAG 搜索输入框监听（实时搜索并定位 TAG）
        setupTagSearchListener();
        // 设置功能按钮监听（清理日志、复制日志）
        setupFunctionButtonListeners(rootView);
        // 设置文本选择开关监听（控制日志文本是否可选中）
        setupTextSelectableListener();
        // 初始化日志级别下拉框（绑定级别数据，设置默认值）
        initLogLevelSpinner();
        // 初始化 TAG 列表（加载所有 TAG，设置全选状态）
        initTagListView();
        // 设置默认交互模式（默认禁止子视图获取焦点，避免误触）
        setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
    }

    /**
     * 绑定布局控件（通过 ID 查找并初始化所有子组件）
     * @param rootView 根布局视图
     */
    private void bindViews(View rootView) {
        mLogScrollView = rootView.findViewById(R.id.viewlogScrollViewLog);
        mLogTextView = rootView.findViewById(R.id.viewlogTextViewLog);
        mTagSearchEt = rootView.findViewById(R.id.tagsearch_et);
        mLogLevelSpinner = rootView.findViewById(R.id.viewlogSpinner1);
        mTextSelectableCb = rootView.findViewById(R.id.viewlogCheckBoxSelectable);
        mSelectAllTagCb = rootView.findViewById(R.id.viewlogCheckBox1);
        mTagHorizontalListView = rootView.findViewById(R.id.tags_listview);
    }

    /**
     * 设置 TAG 搜索输入框监听（文本变化时触发 TAG 定位）
     */
    private void setupTagSearchListener() {
        mTagSearchEt.addTextChangedListener(new TextWatcher() {
				@Override
				public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

				@Override
				public void onTextChanged(CharSequence s, int start, int before, int count) {
					String searchText = s.toString().trim();
					LogUtils.d(TAG, "TAG 搜索内容：" + searchText);
					if (!searchText.isEmpty()) {
						// 搜索文本非空，定位匹配的 TAG
						scrollToTargetTag(searchText);
					} else {
						// 搜索文本为空，重置滚动位置
						HorizontalScrollView parentHs = findViewById(R.id.viewlogHorizontalScrollView1);
						parentHs.smoothScrollTo(0, 0);
						mTagHorizontalListView.resetScrollToStart();
					}
				}

				@Override
				public void afterTextChanged(Editable s) {}
			});
    }

    /**
     * 设置功能按钮监听（清理日志、复制日志）
     */
    private void setupFunctionButtonListeners(View rootView) {
        // 清理日志按钮（点击清空所有历史日志）
        rootView.findViewById(R.id.viewlogButtonClean).setOnClickListener(new View.OnClickListener() {
				@Override
				public void onClick(View v) {
					LogUtils.cleanLog();
					LogUtils.d(TAG, "日志已清理");
				}
			});

        // 复制日志按钮（点击复制所有日志到剪贴板）
        rootView.findViewById(R.id.viewlogButtonCopy).setOnClickListener(new View.OnClickListener() {
				@Override
				public void onClick(View v) {
					ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
					// 将日志内容复制到剪贴板（标签为应用包名）
					clipboard.setPrimaryClip(ClipData.newPlainText(mContext.getPackageName(), LogUtils.loadLog()));
					LogUtils.d(TAG, "日志已复制到剪贴板");
				}
			});
    }

    /**
     * 设置文本选择开关监听（控制日志文本是否可选中复制）
     */
    private void setupTextSelectableListener() {
        mTextSelectableCb.setOnClickListener(new View.OnClickListener() {
				@Override
				public void onClick(View v) {
					if (mTextSelectableCb.isChecked()) {
						// 允许文本选择：子视图优先获取焦点
						setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
					} else {
						// 禁止文本选择：阻止子视图获取焦点
						setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
					}
				}
			});
    }

    /**
     * 初始化日志级别下拉框（Spinner）
     * 1. 绑定 LogUtils.LOG_LEVEL 枚举数据；
     * 2. 设置默认选中当前全局日志级别；
     * 3. 监听级别变化，更新 LogUtils 全局配置。
     */
    private void initLogLevelSpinner() {
        // 从资源文件加载日志级别数组（R.array.enum_loglevel_array 与 LOG_LEVEL 枚举对应）
        mLogLevelAdapter = ArrayAdapter.createFromResource(
			mContext, R.array.enum_loglevel_array, android.R.layout.simple_spinner_item);
        // 设置下拉列表样式
        mLogLevelAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        mLogLevelSpinner.setAdapter(mLogLevelAdapter);

        // 监听下拉框选择变化
        mLogLevelSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
				@Override
				public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
					// 根据选择的位置设置全局日志级别（position 与 LOG_LEVEL 枚举索引对应）
					LogUtils.setLogLevel(LogUtils.LOG_LEVEL.values()[position]);
				}

				@Override
				public void onNothingSelected(AdapterView<?> parent) {}
			});

        // 设置默认选中当前日志级别
        int defaultLevelIndex = LogUtils.getLogLevel().ordinal();
        if (defaultLevelIndex >= 0) {
            mLogLevelSpinner.setSelection(defaultLevelIndex);
        }
    }

    /**
     * 初始化 TAG 水平列表
     * 1. 加载 LogUtils 中的所有 TAG 及其启用状态；
     * 2. 初始化 TAG 列表适配器；
     * 3. 设置全选 TAG 开关监听。
     */
    private void initTagListView() {
        // 获取 LogUtils 中的 TAG 启用状态映射表
        Map<String, Boolean> tagEnableMap = LogUtils.getTagEnableMap();
        // 判断是否所有 TAG 都已启用（初始化全选开关状态）
        boolean isAllTagEnabled = isAllTagsEnabled(tagEnableMap);
        mSelectAllTagCb.setChecked(isAllTagEnabled);

        // 初始化 TAG 水平列表（设置垂直偏移，绑定适配器）
        mTagHorizontalListView.setVerticalOffset(10);
        mTagListAdapter = new TAGListAdapter(mContext, tagEnableMap);
        mTagHorizontalListView.setAdapter(mTagListAdapter);
        mTagListAdapter.notifyDataSetChanged(); // 刷新列表数据

        // 全选 TAG 开关监听（点击时启用/禁用所有 TAG）
        mSelectAllTagCb.setOnClickListener(new View.OnClickListener() {
				@Override
				public void onClick(View v) {
					boolean isSelectAll = mSelectAllTagCb.isChecked();
					LogUtils.setAllTagsEnable(isSelectAll); // 批量更新所有 TAG 状态
					mTagListAdapter.reload(); // 重新加载 TAG 数据并刷新视图
				}
			});
    }

    /**
     * 判断是否所有 TAG 都已启用
     * @param tagEnableMap TAG 启用状态映射表
     * @return true：所有 TAG 均启用；false：存在未启用的 TAG
     */
    private boolean isAllTagsEnabled(Map<String, Boolean> tagEnableMap) {
        for (Map.Entry<String, Boolean> entry : tagEnableMap.entrySet()) {
            if (!entry.getValue()) {
                return false;
            }
        }
        return true;
    }

    /**
     * 更新日志视图（由 LogViewThread 触发，通知有新日志）
     * 避免并发刷新：正在处理时标记新日志，处理完成后再次刷新
     */
    public void updateLogView() {
        if (mLogViewHandler.isHandling()) {
            // 正在处理日志刷新，标记有新日志待处理
            mLogViewHandler.setIsAddNewLog(true);
        } else {
            // 发送刷新消息到主线程
            Message refreshMsg = mLogViewHandler.obtainMessage(LogViewHandler.MSG_LOG_REFRESH);
            mLogViewHandler.sendMessage(refreshMsg);
            mLogViewHandler.setIsAddNewLog(false);
        }
    }

    /**
     * 显示日志并滚动到底部
     * 1. 从 LogUtils 加载所有历史日志；
     * 2. 设置到文本控件并滚动到底部。
     */
    private void showAndScrollLogView() {
        mLogTextView.setText(LogUtils.loadLog()); // 加载并显示日志
        scrollLogToBottom(); // 滚动到底部，显示最新日志
    }

    /**
     * 滚动到目标 TAG（根据搜索文本定位匹配的 TAG 并滚动显示）
     * @param prefix 搜索文本（TAG 前缀）
     */
    private void scrollToTargetTag(final String prefix) {
        if (mTagListAdapter == null || prefix == null || prefix.isEmpty()) {
            LogUtils.d(TAG, "TAG 搜索参数为空，无法定位");
            return;
        }

        final List<TAGItemModel> tagItemList = mTagListAdapter.getItemList();
        mTagHorizontalListView.post(new Runnable() {
				@Override
				public void run() {
					int targetPosition = -1;
					// 遍历 TAG 列表，查找前缀匹配的 TAG（忽略大小写）
					for (int i = 0; i < tagItemList.size(); i++) {
						String tag = tagItemList.get(i).getTag();
						if (tag != null && tag.toLowerCase().startsWith(prefix.toLowerCase())) {
							targetPosition = i;
							break;
						}
					}

					if (targetPosition != -1) {
						final int targetPositionFinal = targetPosition;
						// 延迟滚动（确保布局完成，避免滚动失效）
						mTagHorizontalListView.postDelayed(new Runnable() {
								@Override
								public void run() {
									LogUtils.d(TAG, "定位到 TAG 位置：" + targetPositionFinal);
									mTagHorizontalListView.scrollToItem(targetPositionFinal);
								}
							}, 100);
					} else {
						LogUtils.d(TAG, "未找到匹配前缀的 TAG：" + prefix);
					}
				}
			});
    }

    // ====================== 内部类：日志视图 Handler（主线程更新 UI） ======================
    /**
     * 日志视图 Handler（运行在主线程，处理日志刷新消息）
     * 避免跨线程操作 UI，通过标志位控制并发刷新
     */
    private class LogViewHandler extends Handler {
        /** 日志刷新消息标识 */
        private static final int MSG_LOG_REFRESH = 0;
        /** 日志处理中标志（与外部 mIsHandling 同步） */
        private volatile boolean isHandling;
        /** 新日志添加标志（与外部 mIsAddNewLog 同步） */
        private volatile boolean isAddNewLog;

        public LogViewHandler() {
            setIsHandling(false);
            setIsAddNewLog(false);
        }

        public void setIsHandling(boolean isHandling) {
            this.isHandling = isHandling;
        }

        public boolean isHandling() {
            return isHandling;
        }

        public void setIsAddNewLog(boolean isAddNewLog) {
            this.isAddNewLog = isAddNewLog;
        }

        public boolean isAddNewLog() {
            return isAddNewLog;
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MSG_LOG_REFRESH:
                    // 未处理日志刷新时，标记为处理中并触发显示
                    if (!isHandling()) {
                        setIsHandling(true);
                        showAndScrollLogView();
                    }
                    break;
                default:
                    break;
            }
        }
    }

    // ====================== 内部类：TAG 数据模型（封装 TAG 名称与状态） ======================
    /**
     * TAG 列表项数据模型
     * 封装单个 TAG 的名称及其启用状态（用于 Adapter 数据绑定）
     */
    private class TAGItemModel {
        /** TAG 名称（如 "LogViewThread"、"LogUtils"） */
        private String tag;
        /** TAG 启用状态（true：启用；false：禁用） */
        private boolean isChecked;

        public TAGItemModel(String tag, boolean isChecked) {
            this.tag = tag;
            this.isChecked = isChecked;
        }

        public String getTag() {
            return tag;
        }

        public void setTag(String tag) {
            this.tag = tag;
        }

        public boolean isChecked() {
            return isChecked;
        }

        public void setChecked(boolean checked) {
            isChecked = checked;
        }

        /**
         * 重写 equals 方法（按 TAG 名称判断相等）
         * @param o 比较对象
         * @return true：TAG 名称相同；false：不同
         */
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            TAGItemModel that = (TAGItemModel) o;
            // 手动处理空值比较（兼容 Java 7，不依赖 Objects.equals）
            if (tag == null) {
                return that.tag == null;
            } else {
                return tag.equals(that.tag);
            }
        }

        /**
         * 重写 hashCode 方法（基于 TAG 名称生成哈希值）
         * @return 哈希值（空 TAG 返回 0）
         */
        @Override
        public int hashCode() {
            return tag == null ? 0 : tag.hashCode();
        }
    }

    // ====================== 内部类：TAG 列表适配器（绑定数据与视图） ======================
    /**
     * TAG 水平列表适配器（继承 BaseAdapter）
     * 负责 TAG 数据与列表项视图的绑定，处理勾选状态变化
     */
    private class TAGListAdapter extends BaseAdapter {
        /** 上下文对象（用于加载列表项布局） */
        private Context context;
        /** 原始 TAG 启用状态映射表（来自 LogUtils） */
        private Map<String, Boolean> originTagMap;
        /** TAG 列表项数据（转换为 TAGItemModel 列表，便于排序和绑定） */
        private List<TAGItemModel> tagItemList;

        /**
         * 构造方法（初始化数据并加载到列表）
         * @param context 上下文
         * @param tagMap TAG 启用状态映射表
         */
        public TAGListAdapter(Context context, Map<String, Boolean> tagMap) {
            this.context = context;
            this.originTagMap = tagMap;
            loadTagData(originTagMap); // 加载并转换数据
        }

        /**
         * 获取 TAG 列表项数据（供外部定位 TAG 使用）
         * @return TAGItemModel 列表
         */
        public List<TAGItemModel> getItemList() {
            return tagItemList;
        }

        // ====================== BaseAdapter 抽象方法实现 ======================
        @Override
        public int getCount() {
            return tagItemList == null ? 0 : tagItemList.size();
        }

        @Override
        public Object getItem(int position) {
            return tagItemList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        /**
         * 加载 TAG 数据（将 Map 转换为 List 并排序）
         * @param tagMap TAG 启用状态映射表
         */
        private void loadTagData(Map<String, Boolean> tagMap) {
            tagItemList = new ArrayList<>();
            // 遍历 Map，转换为 TAGItemModel 并添加到列表
            for (Map.Entry<String, Boolean> entry : tagMap.entrySet()) {
                tagItemList.add(new TAGItemModel(entry.getKey(), entry.getValue()));
            }
            // 按 TAG 名称升序排序（中文排序兼容）
            Collections.sort(tagItemList, new TagAscComparator(true));
        }

        /**
         * 重新加载 TAG 数据（用于全选/反选后刷新列表）
         */
        public void reload() {
            loadTagData(originTagMap); // 重新加载数据
            notifyDataSetChanged(); // 通知视图刷新
        }

        /**
         * 创建/复用列表项视图（优化性能，避免重复 inflate）
         * @param position 列表项位置
         * @param convertView 复用视图（可为 null）
         * @param parent 父容器
         * @return 列表项视图
         */
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            // 复用视图（减少布局加载开销）
            if (convertView == null) {
                // 加载列表项布局（R.layout.item_logtag 为 TAG 项自定义布局）
                convertView = LayoutInflater.from(context).inflate(R.layout.item_logtag, parent, false);
                holder = new ViewHolder();
                // 绑定列表项控件（TAG 文本和勾选框）
                holder.tagTv = convertView.findViewById(R.id.viewlogtagTextView1);
                holder.tagCb = convertView.findViewById(R.id.viewlogtagCheckBox1);
                convertView.setTag(holder); // 保存 ViewHolder 到视图
            } else {
                holder = (ViewHolder) convertView.getTag(); // 复用 ViewHolder
            }

            // 绑定数据到视图
            final TAGItemModel item = tagItemList.get(position);
            holder.tagTv.setText(item.getTag()); // 设置 TAG 名称
            holder.tagCb.setChecked(item.isChecked()); // 设置勾选状态

            // 勾选框点击监听（更新 TAG 启用状态）
            holder.tagCb.setOnClickListener(new View.OnClickListener() {
					@Override
					public void onClick(View v) {
						boolean isChecked = ((CheckBox) v).isChecked();
						// 调用 LogUtils 更新该 TAG 的启用状态
						LogUtils.setTagEnable(item.getTag(), isChecked);
						// 同步更新本地模型状态（避免刷新后状态不一致）
						item.setChecked(isChecked);
					}
				});

            return convertView;
        }

        /**
         * 列表项 ViewHolder（缓存控件，提升列表滑动性能）
         */
        private class ViewHolder {
            TextView tagTv; // TAG 名称文本控件
            CheckBox tagCb; // TAG 启用状态勾选框
        }
    }

    // ====================== 内部类：TAG 排序比较器（中文兼容） ======================
    /**
     * TAG 名称排序比较器（实现 Comparator）
     * 支持中文排序（基于系统默认中文 Locale），可选择升序/降序
     */
    private class TagAscComparator implements Comparator<TAGItemModel> {
        /** 排序方向（true：升序；false：降序） */
        private boolean isAsc;
        /** 中文排序器（兼容中文汉字排序） */
        private Collator chineseCollator = Collator.getInstance(java.util.Locale.CHINA);

        public TagAscComparator(boolean isAsc) {
            this.isAsc = isAsc;
        }

        /**
         * 比较两个 TAGItemModel（按 TAG 名称排序）
         * @param o1 第一个比较对象
         * @param o2 第二个比较对象
         * @return 比较结果（正数：o1 在 o2 后；负数：o1 在 o2 前；0：相等）
         */
        @Override
        public int compare(TAGItemModel o1, TAGItemModel o2) {
            String tag1 = o1.getTag();
            String tag2 = o2.getTag();
            // 处理空值（空 TAG 排在最前）
            if (tag1 == null) return -1;
            if (tag2 == null) return 1;

            // 根据排序方向返回比较结果
            if (isAsc) {
                return chineseCollator.compare(tag1, tag2); // 升序
            } else {
                return chineseCollator.compare(tag2, tag1); // 降序
            }
        }
    }
}

