/*
 * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
 *
 * 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 net.micode.notes.ui;

 import android.content.Context;
 import android.graphics.Rect;
 import android.text.Layout;
 import android.text.Selection;
 import android.text.Spanned;
 import android.text.TextUtils;
 import android.text.style.URLSpan;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.KeyEvent;
 import android.view.MenuItem;
 import android.view.MenuItem.OnMenuItemClickListener;
 import android.view.MotionEvent;
 import android.widget.EditText;
 
 import net.micode.notes.R;
 
 import java.util.HashMap;
 import java.util.Map;
 
 /**
  * NoteEditText 类继承自 EditText,用于处理笔记编辑过程中的一些特殊逻辑,
  * 如处理删除、回车按键事件,处理链接点击事件等。
  */
 public class NoteEditText extends EditText {
     // 日志标签,用于在日志中标识该类的信息
     private static final String TAG = "NoteEditText";
     // 该编辑文本框的索引,用于在列表中定位
     private int mIndex;
     // 删除操作前的选择起始位置
     private int mSelectionStartBeforeDelete;
 
     // 电话链接的协议前缀
     private static final String SCHEME_TEL = "tel:" ;
     // HTTP 链接的协议前缀
     private static final String SCHEME_HTTP = "http:" ;
     // 邮件链接的协议前缀
     private static final String SCHEME_EMAIL = "mailto:" ;
 
     // 存储协议前缀和对应的菜单项资源 ID 的映射
     private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
     static {
         // 初始化映射,将电话协议前缀映射到对应的菜单项资源 ID
         sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
         // 初始化映射,将 HTTP 协议前缀映射到对应的菜单项资源 ID
         sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
         // 初始化映射,将邮件协议前缀映射到对应的菜单项资源 ID
         sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
     }
 
     /**
      * OnTextViewChangeListener 接口定义了在编辑文本框发生特定事件时的回调方法,
      * 由外部类实现,用于处理删除、回车和文本变化等事件。
      */
     public interface OnTextViewChangeListener {
         /**
          * 当按下删除键且文本为空时,调用此方法删除当前编辑文本框。
          * @param index 当前编辑文本框的索引
          * @param text 当前编辑文本框的文本内容
          */
         void onEditTextDelete(int index, String text);
 
         /**
          * 当按下回车键时,调用此方法在当前编辑文本框之后添加一个新的编辑文本框。
          * @param index 当前编辑文本框的下一个索引
          * @param text 当前编辑文本框中光标之后的文本内容
          */
         void onEditTextEnter(int index, String text);
 
         /**
          * 当文本内容发生变化时,调用此方法隐藏或显示菜单项。
          * @param index 当前编辑文本框的索引
          * @param hasText 当前编辑文本框是否有文本内容
          */
         void onTextChange(int index, boolean hasText);
     }
 
     // 文本变化监听器实例
     private OnTextViewChangeListener mOnTextViewChangeListener;
 
     /**
      * 构造函数,使用默认属性创建 NoteEditText 实例。
      * @param context 上下文对象
      */
     public NoteEditText(Context context) {
         super(context, null);
         // 初始化索引为 0
         mIndex = 0;
     }
 
     /**
      * 设置当前编辑文本框的索引。
      * @param index 要设置的索引值
      */
     public void setIndex(int index) {
         mIndex = index;
     }
 
     /**
      * 设置文本变化监听器。
      * @param listener 实现了 OnTextViewChangeListener 接口的监听器实例
      */
     public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
         mOnTextViewChangeListener = listener;
     }
 
     /**
      * 构造函数,使用指定的属性集创建 NoteEditText 实例。
      * @param context 上下文对象
      * @param attrs 属性集
      */
     public NoteEditText(Context context, AttributeSet attrs) {
         super(context, attrs, android.R.attr.editTextStyle);
     }
 
     /**
      * 构造函数,使用指定的属性集和默认样式创建 NoteEditText 实例。
      * @param context 上下文对象
      * @param attrs 属性集
      * @param defStyle 默认样式
      */
     public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         // TODO Auto-generated constructor stub
     }
 
     /**
      * 处理触摸事件,当手指按下时,根据触摸位置设置光标位置。
      * @param event 触摸事件对象
      * @return 是否处理该事件
      */
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
                 // 获取触摸点的 x 坐标
                 int x = (int) event.getX();
                 // 获取触摸点的 y 坐标
                 int y = (int) event.getY();
                 // 减去左内边距
                 x -= getTotalPaddingLeft();
                 // 减去上内边距
                 y -= getTotalPaddingTop();
                 // 加上水平滚动距离
                 x += getScrollX();
                 // 加上垂直滚动距离
                 y += getScrollY();
 
                 // 获取文本布局对象
                 Layout layout = getLayout();
                 // 根据垂直位置获取所在行
                 int line = layout.getLineForVertical(y);
                 // 根据行和水平位置获取偏移量
                 int off = layout.getOffsetForHorizontal(line, x);
                 // 设置光标位置
                 Selection.setSelection(getText(), off);
                 break;
         }
 
         return super.onTouchEvent(event);
     }
 
     /**
      * 处理按键按下事件,记录删除操作前的选择起始位置,处理回车键事件。
      * @param keyCode 按键码
      * @param event 按键事件对象
      * @return 是否处理该事件
      */
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         switch (keyCode) {
             case KeyEvent.KEYCODE_ENTER:
                 if (mOnTextViewChangeListener != null) {
                     // 如果有监听器,不处理回车键事件,交由监听器处理
                     return false;
                 }
                 break;
             case KeyEvent.KEYCODE_DEL:
                 // 记录删除操作前的选择起始位置
                 mSelectionStartBeforeDelete = getSelectionStart();
                 break;
             default:
                 break;
         }
         return super.onKeyDown(keyCode, event);
     }
 
     /**
      * 处理按键释放事件,根据按键码调用相应的监听器方法。
      * @param keyCode 按键码
      * @param event 按键事件对象
      * @return 是否处理该事件
      */
     @Override
     public boolean onKeyUp(int keyCode, KeyEvent event) {
         switch(keyCode) {
             case KeyEvent.KEYCODE_DEL:
                 if (mOnTextViewChangeListener != null) {
                     if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
                         // 当删除操作从文本开头开始且索引不为 0 时,调用删除回调方法
                         mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
                         return true;
                     }
                 } else {
                     // 记录日志,提示监听器未设置
                     Log.d(TAG, "OnTextViewChangeListener was not seted");
                 }
                 break;
             case KeyEvent.KEYCODE_ENTER:
                 if (mOnTextViewChangeListener != null) {
                     // 获取当前选择的起始位置
                     int selectionStart = getSelectionStart();
                     // 获取光标之后的文本内容
                     String text = getText().subSequence(selectionStart, length()).toString();
                     // 设置文本为光标之前的内容
                     setText(getText().subSequence(0, selectionStart));
                     // 调用回车回调方法
                     mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
                 } else {
                     // 记录日志,提示监听器未设置
                     Log.d(TAG, "OnTextViewChangeListener was not seted");
                 }
                 break;
             default:
                 break;
         }
         return super.onKeyUp(keyCode, event);
     }
 
     /**
      * 处理焦点变化事件,根据焦点状态和文本内容调用文本变化监听器方法。
      * @param focused 是否获得焦点
      * @param direction 焦点变化的方向
      * @param previouslyFocusedRect 之前焦点所在的矩形区域
      */
     @Override
     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
         if (mOnTextViewChangeListener != null) {
             if (!focused && TextUtils.isEmpty(getText())) {
                 // 失去焦点且文本为空时,调用文本变化回调方法,标记为无文本
                 mOnTextViewChangeListener.onTextChange(mIndex, false);
             } else {
                 // 其他情况,调用文本变化回调方法,标记为有文本
                 mOnTextViewChangeListener.onTextChange(mIndex, true);
             }
         }
         super.onFocusChanged(focused, direction, previouslyFocusedRect);
     }
 
     /**
      * 创建上下文菜单,当选中的文本包含链接时,添加相应的菜单项。
      * @param menu 上下文菜单对象
      */
     @Override
     protected void onCreateContextMenu(ContextMenu menu) {
         if (getText() instanceof Spanned) {
             // 获取选择的起始位置
             int selStart = getSelectionStart();
             // 获取选择的结束位置
             int selEnd = getSelectionEnd();
 
             // 获取选择范围的最小值
             int min = Math.min(selStart, selEnd);
             // 获取选择范围的最大值
             int max = Math.max(selStart, selEnd);
 
             // 获取选择范围内的所有 URLSpan 对象
             final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
             if (urls.length == 1) {
                 // 如果只有一个 URLSpan 对象
                 int defaultResId = 0;
                 for(String schema: sSchemaActionResMap.keySet()) {
                     if(urls[0].getURL().indexOf(schema) >= 0) {
                         // 根据链接协议前缀获取对应的菜单项资源 ID
                         defaultResId = sSchemaActionResMap.get(schema);
                         break;
                     }
                 }
 
                 if (defaultResId == 0) {
                     // 如果没有匹配的协议前缀,使用默认的菜单项资源 ID
                     defaultResId = R.string.note_link_other;
                 }
 
                 // 添加菜单项,并设置点击监听器
                 menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
                         new OnMenuItemClickListener() {
                             public boolean onMenuItemClick(MenuItem item) {
                                 // 点击菜单项时,触发链接的点击事件
                                 urls[0].onClick(NoteEditText.this);
                                 return true;
                             }
                         });
             }
         }
         super.onCreateContextMenu(menu);
     }
 }