package com.example.myapplication;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
class XmlReader {
@NonNull private static final String TAG = XmlReader.class.getSimpleName();
/**
* 各要素のタグ名称
/
@NonNull private final String TARGET = "target";
@NonNull private final String TARGET_LIST = "targetList";
@NonNull private final String TARGET_NUM = "targetNum";
@NonNull private final String TARGET_NAME = "targetName";
@NonNull private final String TARGET_ID = "targetId";
@NonNull private final String EXPIRATION_DATA = "expirationDate";
/*
* targetListの要素数
/
private final int TARGET_LIST_NUM = 1;
/*
* targetIdの要素数
/
private final int TARGET_ID_NUM = 1;
/*
* expirationDateの要素数
/
private final int TARGET_EXPIRATION_DATE_NUM = 1;
/*
* targetNumの要素数の最大値
/
private final int TARGET_NUM_MAX = 2;
/*
* targetNumの要素数の最小値
*/
private final int TARGET_NUM_MIN = 1;
/**
 * ファイル検索に使用するnameのリスト
 */
@Nullable
private List<Node> mNameElementList;
/**
 * name要素取得
 *
 * @return name要素のリスト
 */
@Nullable
public List<Node> getNameElementList() {
    Log.i(TAG, "getNameElementList()");
    return mNameElementList;
}
/**
 * DOMによるxml読み込み処理
 *
 * @param inputStream 読み込み対象XML
 * @return true:正常なXML, false:不正なXML
 * @throws SAXException
 * @throws IOException
 * @throws ParserConfigurationException
 */
public boolean domRead(InputStream inputStream) throws SAXException, IOException, ParserConfigurationException {
    Log.i(TAG, "domRead()");
    // 初期化
    mNameElementList = null;
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder documentBuilder = factory.newDocumentBuilder();
    Document document = documentBuilder.parse(inputStream);
    boolean result = false;
    // ルート要素取得
    Element root = document.getDocumentElement();
    if (null != root) {
        if (TARGET.equals(root.getTagName())) {
            Log.i(TAG, "root.getTagName() : " + root.getTagName());
            //ルートからexpirationDateを取得
            NodeList expirationDate = root.getElementsByTagName(EXPIRATION_DATA);
            if (checkExpirationDate(expirationDate)) {
                //ルートからtargetIdを取得
                NodeList targetId = root.getElementsByTagName(TARGET_ID);
                if (checkTargetId(targetId)) {
                    //ルートからtargetListを取得
                    NodeList targetList = root.getElementsByTagName(TARGET_LIST);
                    if (checkTargetList(targetList)) {
                        result = true;
                    } else {
                        Log.i(TAG, "Error : targetList is invalid");
                    }
                } else {
                    Log.i(TAG, "Error : targetId is invalid");
                }
            } else {
                Log.i(TAG, "Error : expirationDate is invalid");
            }
        } else {
            Log.i(TAG, "Error : target tag name is invalid");
        }
    } else {
        Log.i(TAG, "Error : Root is null");
    }
    return result;
}
/**
 * targetListのチェック
 *
 * @param targetList チェック対象のNodeList
 * @return true:正常なtargetList, false:不正なtargetList
 */
private boolean checkTargetList(@Nullable NodeList targetList) {
    Log.i(TAG, "checkTargetList()");
    boolean result = false;
    if (null != targetList) {
        if (TARGET_LIST_NUM == targetList.getLength()) {
            Element targetListElement = (Element) targetList.item(0);
            if (null != targetListElement) {
                NodeList targetListChildren = targetListElement.getChildNodes();
                if (null != targetListChildren) {
                    result = checkTargetListChildren(targetListChildren);
                } else {
                    Log.i(TAG, "Error : targetList children is null");
                }
            } else {
                Log.i(TAG, "Error : targetList is null");
            }
        } else {
            Log.i(TAG, "Error : targetList is invalid configuration");
        }
    } else {
        Log.i(TAG, "Error : targetList is null");
    }
    Log.i(TAG, "checkTargetList() result : " + result);
    return result;
}
/**
 * targetListの子要素のチェック
 *
 * @param targetListChildren チェック対象のNodeList
 * @return true:正常なtargetListの子要素, false:不正なtargetListの子要素
 */
private boolean checkTargetListChildren(@NonNull NodeList targetListChildren) {
    Log.i(TAG, "checkTargetListChildren()");
    List<Node> targetNumList = new ArrayList<>();
    List<Node> targetNameList = new ArrayList<>();
    boolean result = false;
    for (int i = 0; i < targetListChildren.getLength(); i++) {
        Node node = targetListChildren.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            String nodeName = node.getNodeName();
            Log.i(TAG, "nodeName : " + nodeName);
            if (TARGET_NUM.equals(nodeName)) {
                targetNumList.add(node);
            } else if (TARGET_NAME.equals(nodeName)) {
                targetNameList.add(node);
            } else {
                Log.i(TAG, "Error : element tag name is invalid");
                break;
            }
        }
    }
    result = targetNumWithTargetNameVerification(targetNumList, targetNameList);
    Log.i(TAG, "checkTargetListChildren() result : " + result);
    return result;
}
/**
 * targetNumとtargetNameの整合性確認
 *
 * @param targetNumList  確認対象のtargetNumList
 * @param targetNameList 確認対象のtargetNameList
 * @return true:整合OK, false:整合NG
 */
private boolean targetNumWithTargetNameVerification(@NonNull List<Node> targetNumList, @NonNull List<Node> targetNameList) {
    Log.i(TAG, "targetNumWithTargetNameVerification()");
    boolean result = false;
    // targetNumの要素数確認
    if (TARGET_ID_NUM == targetNumList.size()) {
        int targetNum = 0;
        try {
            // targetNumの要素取得
            targetNum = Integer.parseInt(targetNumList.get(0).getTextContent());
        } catch (@NonNull NumberFormatException | DOMException | IndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        // targetNumの整合性を確認
        if (TARGET_NUM_MIN == targetNum || TARGET_NUM_MAX == targetNum) {
            // targetNumの値とtargetNameの数が一致しているか確認
            if (targetNum == targetNameList.size()) {
                Log.i(TAG, "Success : targetElement is valid");
                result = true;
                mNameElementList = targetNameList;
            } else {
                Log.i(TAG, "Error : The number of targetNum does not match the targetName");
            }
        } else {
            Log.i(TAG, "Error : targetNum is invalid");
        }
    } else {
        Log.i(TAG, "Error : targetNum is invalid");
    }
    Log.i(TAG, "targetNumWithTargetNameVerification() result : " + result);
    return result;
}
/**
 * expirationDateのチェック
 *
 * @param expirationDate チェック対象のNodeList
 * @return true:正常なexpirationDate, false:不正なexpirationDate
 */
private boolean checkExpirationDate(@Nullable NodeList expirationDate) {
    Log.i(TAG, "checkExpirationDate()");
    String expirationDateString = null;
    boolean result = false;
    if (null != expirationDate) {
        if (TARGET_EXPIRATION_DATE_NUM == expirationDate.getLength()) {
            Node node = expirationDate.item(0);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element element = (Element) node;
                if (EXPIRATION_DATA.equals(element.getNodeName())) {
                    try {
                        expirationDateString = element.getTextContent();
                    } catch (DOMException e) {
                        e.printStackTrace();
                    }
                    if (null != expirationDateString) {
                        result = expirationDateVerification(expirationDateString);
                    } else {
                        Log.i(TAG, "Error : expirationDateString is null");
                    }
                } else {
                    Log.i(TAG, "Error : element tag name is invalid");
                }
            } else {
                Log.i(TAG, "Error : expirationDate element is invalid");
            }
        } else {
            Log.i(TAG, "Error : expirationDate is invalid configuration");
        }
    } else {
        Log.i(TAG, "Error : expirationDate is null");
    }
    Log.i(TAG, "checkExpirationDate() result : " + result);
    return result;
}
/**
 * expirationDateの整合性確認
 *
 * @param expirationDateString チェック対象のexpirationDate
 * @return true:正常な要素, false:不正な要素
 */
private boolean expirationDateVerification(@NonNull String expirationDateString) {
    Log.i(TAG, "expirationDateVerification()");
    // 現在時刻取得
    LocalDateTime nowDate = LocalDateTime.now();
    LocalDateTime expirationDate = null;
    try {
        // expirationDateをLocalDateTimeに変換
        expirationDate = expirationDateConversion(expirationDateString);
    } catch (@NonNull DateTimeParseException | IllegalArgumentException e) {
        e.printStackTrace();
    }
    // 現在時刻とexpirationDateの比較
    Log.i(TAG, "nowDate : " + nowDate + ", expirationDate : " + expirationDate);
    boolean result = false;
    if (null != nowDate && null != expirationDate) {
        // 有効期限内
        if (nowDate.isBefore(expirationDate)) {
            Log.i(TAG, "Success : Within the expiration date");
            result = true;
        // 有効期限と同時刻
        } else if (nowDate.isEqual(expirationDate)) {
            Log.i(TAG, "Success : Expiration date is just");
            result = true;
        // 有効期限切れ
        } else {
            Log.i(TAG, "Error : Expired");
        }
    } else {
        Log.i(TAG, "Error : Verification failure");
    }
    Log.i(TAG, "expirationDateVerification() result : " + result);
    return result;
}
/**
 * String(yyyyMMddHHmmss)からLocalDateTimeに変換
 *
 * @return LocalDateTime型の日付(変換対象がnullの場合 、 null)
 */
@Nullable
private LocalDateTime expirationDateConversion(@NonNull String expirationDateString) {
    Log.i(TAG, "expirationDateConversion() expirationDateString : " + expirationDateString);
    LocalDateTime dateTime = null;
    if (expirationDateString.length() == 14) {
        dateTime = LocalDateTime.of(
                Integer.parseInt(expirationDateString.substring(0, 4)),    // 年
                Integer.parseInt(expirationDateString.substring(4, 6)),    // 月
                Integer.parseInt(expirationDateString.substring(6, 8)),    // 日
                Integer.parseInt(expirationDateString.substring(8, 10)),   // 時
                Integer.parseInt(expirationDateString.substring(10, 12)),  // 分
                Integer.parseInt(expirationDateString.substring(12, 14))); // 秒
    } else {
        Log.i(TAG, "Error : ExpirationDate is invalid");
    }
    return dateTime;
}
/**
 * targetIdのチェック
 *
 * @param targetId チェック対象のNodeList
 * @return true:正常なtargetId, false:不正なtargetId
 */
private boolean checkTargetId(@Nullable NodeList targetId) {
    Log.i(TAG, "checkTargetId()");
    String targetIdString = null;
    boolean result = false;
    if (null != targetId) {
        if (TARGET_ID_NUM == targetId.getLength()) {
            Node node = targetId.item(0);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element element = (Element) node;
                if (TARGET_ID.equals(element.getNodeName())) {
                    try {
                        targetIdString = element.getTextContent();
                    } catch (DOMException e) {
                        e.printStackTrace();
                    }
                    if (null != targetIdString) {
                        Log.i(TAG, "Success : checkTargetId() targetIdString : " + targetIdString);
                        result = true;
                    } else {
                        Log.i(TAG, "Error : targetIdString is null");
                    }
                } else {
                    Log.i(TAG, "Error : element tag name is invalid");
                }
            } else {
                Log.i(TAG, "Error : targetId element is invalid");
            }
        } else {
            Log.i(TAG, "Error : targetId is invalid configuration");
        }
    } else {
        Log.i(TAG, "Error : targetId is null");
    }
    Log.i(TAG, "checkTargetId() result : " + result);
    return result;
}
}
