/*
 * Copyright (c) 2013-2024 Hutool Team and hutool.cn
 *
 * 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 org.dromara.hutool.core.net.url;

import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.text.split.SplitUtil;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.ObjUtil;

import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;

/**
 * URL中Path部分的封装
 *
 * @author looly
 * @since 5.3.1
 */
public class UrlPath {

	private List<CharSequence> segments;
	private boolean withEngTag;

	/**
	 * 构建UrlPath
	 *
	 * @return UrlPath
	 * @since 6.0.0
	 */
	public static UrlPath of() {
		return new UrlPath();
	}

	/**
	 * 构建UrlPath
	 *
	 * @param pathStr 初始化的路径字符串
	 * @param charset decode用的编码，null表示不做decode
	 * @return UrlPath
	 */
	public static UrlPath of(final CharSequence pathStr, final Charset charset) {
		return of().parse(pathStr, charset);
	}

	/**
	 * 是否path的末尾加 /
	 *
	 * @param withEngTag 是否path的末尾加 /
	 * @return this
	 */
	public UrlPath setWithEndTag(final boolean withEngTag) {
		this.withEngTag = withEngTag;
		return this;
	}

	/**
	 * 获取path的节点列表，如果列表为空，返回{@link ListUtil#empty()}
	 *
	 * @return 节点列表
	 */
	public List<CharSequence> getSegments() {
		return ObjUtil.defaultIfNull(this.segments, ListUtil::empty);
	}

	/**
	 * 获得指定节点
	 *
	 * @param index 节点位置
	 * @return 节点，无节点或者越界返回null
	 */
	public CharSequence getSegment(final int index) {
		if (null == this.segments || index >= this.segments.size()) {
			return null;
		}
		return this.segments.get(index);
	}

	/**
	 * 添加到path最后面
	 *
	 * @param segment Path节点
	 * @return this
	 */
	public UrlPath add(final CharSequence segment) {
		addInternal(fixPath(segment), false);
		return this;
	}

	/**
	 * 添加到path最前面
	 *
	 * @param segment Path节点
	 * @return this
	 */
	public UrlPath addBefore(final CharSequence segment) {
		addInternal(fixPath(segment), true);
		return this;
	}

	/**
	 * 解析path
	 *
	 * @param path    路径，类似于aaa/bb/ccc或/aaa/bbb/ccc
	 * @param charset decode编码，null表示不解码
	 * @return this
	 */
	public UrlPath parse(CharSequence path, final Charset charset) {
		if (StrUtil.isNotEmpty(path)) {
			// 原URL中以/结尾，则这个规则需保留，issue#I1G44J@Gitee
			if (StrUtil.endWith(path, CharUtil.SLASH)) {
				this.withEngTag = true;
			}

			path = fixPath(path);
			if (StrUtil.isNotEmpty(path)) {
				final List<String> split = SplitUtil.split(path, StrUtil.SLASH);
				for (final String seg : split) {
					addInternal(UrlDecoder.decodeForPath(seg, charset), false);
				}
			}
		}

		return this;
	}

	/**
	 * 构建path，前面带'/'<br>
	 * <pre>
	 *     path = path-abempty / path-absolute / path-noscheme / path-rootless / path-empty
	 * </pre>
	 *
	 * @param charset       encode编码，null表示不做encode
	 * @return 如果没有任何内容，则返回空字符串""
	 * @since 5.8.0
	 */
	public String build(final Charset charset) {
		if (CollUtil.isEmpty(this.segments)) {
			// 没有节点的path取决于是否末尾追加/，如果不追加返回空串，否则返回/
			return withEngTag ? StrUtil.SLASH : StrUtil.EMPTY;
		}

		final StringBuilder builder = new StringBuilder();
		for (final CharSequence segment : segments) {
			// https://www.ietf.org/rfc/rfc3986.html#section-3.3
			// 此处Path中是允许有`:`的，之前理解有误，应该是相对URI的第一个segment中不允许有`:`
			builder.append(CharUtil.SLASH).append(RFC3986.SEGMENT.encode(segment, charset));
		}

		if (withEngTag) {
			if (StrUtil.isEmpty(builder)) {
				// 空白追加是保证以/开头
				builder.append(CharUtil.SLASH);
			} else if (!StrUtil.endWith(builder, CharUtil.SLASH)) {
				// 尾部没有/则追加，否则不追加
				builder.append(CharUtil.SLASH);
			}
		}

		return builder.toString();
	}

	@Override
	public String toString() {
		return build(null);
	}

	/**
	 * 增加节点
	 *
	 * @param segment 节点
	 * @param before  是否在前面添加
	 */
	private void addInternal(final CharSequence segment, final boolean before) {
		if (this.segments == null) {
			this.segments = new LinkedList<>();
		}

		if (before) {
			this.segments.add(0, segment);
		} else {
			this.segments.add(segment);
		}
	}

	/**
	 * 修正路径，包括去掉前后的/，去掉空白符
	 *
	 * @param path 节点或路径path
	 * @return 修正后的路径
	 */
	private static String fixPath(final CharSequence path) {
		Assert.notNull(path, "Path segment must be not null!");
		if ("/".contentEquals(path)) {
			return StrUtil.EMPTY;
		}

		String segmentStr = StrUtil.trim(path);
		segmentStr = StrUtil.removePrefix(segmentStr, StrUtil.SLASH);
		segmentStr = StrUtil.removeSuffix(segmentStr, StrUtil.SLASH);
		segmentStr = StrUtil.trim(segmentStr);
		return segmentStr;
	}
}
