// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
// * to you 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.apache.juneau.utils;

import static java.util.Collections.*;
import static org.apache.juneau.internal.StringUtils.*;

import java.util.*;

import org.apache.juneau.*;
import org.apache.juneau.internal.*;

/**
 * Encapsulates arguments for basic search/view/sort/position/limit functionality.
 */
public class SearchArgs {

	/**
	 * Default search args.
	 */
	public static SearchArgs DEFAULT = SearchArgs.builder().build();

	private final Map<String,String> search;
	private final List<String> view;
	private final Map<String,Boolean> sort;
	private final int position, limit;
	private final boolean ignoreCase;

	SearchArgs(Builder b) {
		this.search = unmodifiableMap(new LinkedHashMap<>(b.search));
		this.view = unmodifiableList(new ArrayList<>(b.view));
		this.sort = unmodifiableMap(new LinkedHashMap<>(b.sort));
		this.position = b.position;
		this.limit = b.limit;
		this.ignoreCase = b.ignoreCase;
	}

	/**
	 * Creates a new builder for {@link SearchArgs}
	 *
	 * @return A new builder for {@link SearchArgs}
	 */
	public static Builder builder() {
		return new Builder();
	}

	/**
	 * Builder for {@link SearchArgs} class.
	 */
	public static final class Builder {
		Map<String,String> search = new LinkedHashMap<>();
		List<String> view = new ArrayList<>();
		Map<String,Boolean> sort = new LinkedHashMap<>();
		int position, limit;
		boolean ignoreCase;

		/**
		 * Adds search terms to this builder.
		 *
		 * <p>
		 * The search terms are a comma-delimited list of key/value pairs of column-names and search tokens.
		 *
		 * <p>
		 * For example:
		 * <p class='bcode w800'>
		 * 	builder.search(<js>"column1=foo*, column2=bar baz"</js>);
		 * </p>
		 *
		 * <p>
		 * It's up to implementers to decide the syntax and meaning of the search terms.
		 *
		 * <p>
		 * Whitespace is trimmed from column names and search tokens.
		 *
		 * @param searchTerms
		 * 	The search terms string.
		 * 	Can be <jk>null</jk>.
		 * @return This object (for method chaining).
		 */
		public Builder search(String searchTerms) {
			if (searchTerms != null) {
				for (String s : StringUtils.split(searchTerms)) {
					int i = StringUtils.indexOf(s, '=', '>', '<');
					if (i == -1)
						throw new FormattedRuntimeException("Invalid search terms: ''{0}''", searchTerms);
					char c = s.charAt(i);
					search(s.substring(0, i).trim(), s.substring(c == '=' ? i+1 : i).trim());
				}
			}
			return this;
		}

		/**
		 * Adds a search term to this builder.
		 *
		 * <p>
		 * It's up to implementers to decide the syntax and meaning of the search term.
		 *
		 * @param column The column being searched.
		 * @param searchTerm The search term.
		 * @return This object (for method chaining).
		 */
		public Builder search(String column, String searchTerm) {
			this.search.put(column, searchTerm);
			return this;
		}

		/**
		 * Specifies the list of columns to view.
		 *
		 * <p>
		 * The columns argument is a simple comma-delimited list of column names.
		 *
		 * <p>
		 * For example:
		 * <p class='bcode w800'>
		 * 	builder.view(<js>"column1, column2"</js>);
		 * </p>
		 *
		 * <p>
		 * Whitespace is trimmed from column names.
		 *
		 * <p>
		 * Empty view columns imply view all columns.
		 *
		 * @param columns
		 * 	The columns being viewed.
		 * 	Can be <jk>null</jk>.
		 * @return This object (for method chaining).
		 */
		public Builder view(String columns) {
			if (columns != null)
				return view(Arrays.asList(StringUtils.split(columns)));
			return this;
		}

		/**
		 * Specifies the list of columns to view.
		 *
		 * <p>
		 * Empty view columns imply view all columns.
		 *
		 * @param columns The columns being viewed.
		 * @return This object (for method chaining).
		 */
		public Builder view(Collection<String> columns) {
			this.view.addAll(columns);
			return this;
		}

		/**
		 * Specifies the sort arguments.
		 *
		 * <p>
		 * The sort argument is a simple comma-delimited list of column names.
		 * <br>Column names can be suffixed with <js>'+'</js> or <js>'-'</js> to indicate ascending or descending order.
		 * <br>No suffix implies ascending order.
		 *
		 * <p>
		 * For example:
		 * <p class='bcode w800'>
		 * 	<jc>// Order by column1 ascending, then column2 descending.</jc>
		 * 	builder.sort(<js>"column1, column2-"</js>);
		 * </p>
		 *
		 * <p>
		 * Note that the order of the order arguments is important.
		 *
		 * <p>
		 * Whitespace is trimmed from column names.
		 *
		 * @param sortArgs
		 * 	The columns to sort by.
		 * 	Can be <jk>null</jk>.
		 * @return This object (for method chaining).
		 */
		public Builder sort(String sortArgs) {
			if (sortArgs != null)
				sort(Arrays.asList(StringUtils.split(sortArgs)));
			return this;
		}

		/**
		 * Specifies the sort arguments.
		 *
		 * <p>
		 * Column names can be suffixed with <js>'+'</js> or <js>'-'</js> to indicate ascending or descending order.
		 * <br>No suffix implies ascending order.
		 *
		 * <p>
		 * Note that the order of the sort is important.
		 *
		 * @param sortArgs
		 * 	The columns to sort by.
		 * 	Can be <jk>null</jk>.
		 * @return This object (for method chaining).
		 */
		public Builder sort(Collection<String> sortArgs) {
			for (String s : sortArgs) {
				boolean isDesc = false;
				if (endsWith(s, '-', '+')) {
					isDesc = endsWith(s, '-');
					s = s.substring(0, s.length()-1);
				}
				this.sort.put(s, isDesc);
			}
			return this;
		}

		/**
		 * Specifies the starting line number.
		 *
		 * @param position The zero-indexed position.
		 * @return This object (for method chaining).
		 */
		public Builder position(int position) {
			this.position = position;
			return this;
		}

		/**
		 * Specifies the number of rows to return.
		 *
		 * @param limit
		 * 	The number of rows to return.
		 * 	If <c>&lt;=0</c>, all rows should be returned.
		 * @return This object (for method chaining).
		 */
		public Builder limit(int limit) {
			this.limit = limit;
			return this;
		}

		/**
		 * Specifies whether case-insensitive search should be used.
		 *
		 * <p>
		 * The default is <jk>false</jk>.
		 *
		 * @param value The ignore-case flag value.
		 * @return This object (for method chaining).
		 */
		public Builder ignoreCase(boolean value) {
			this.ignoreCase = value;
			return this;
		}

		/**
		 * Construct the {@link SearchArgs} object.
		 *
		 * <p>
		 * This method can be called multiple times to construct new objects.
		 *
		 * @return A new {@link SearchArgs} object initialized with values in this builder.
		 */
		public SearchArgs build() {
			return new SearchArgs(this);
		}
	}

	/**
	 * The query search terms.
	 *
	 * <p>
	 * The search terms are key/value pairs consisting of column-names and search tokens.
	 *
	 * <p>
	 * It's up to implementers to decide the syntax and meaning of the search term.
	 *
	 * @return An unmodifiable map of query search terms.
	 */
	public Map<String,String> getSearch() {
		return search;
	}

	/**
	 * The view columns.
	 *
	 * <p>
	 * The view columns are the list of columns that should be displayed.
	 * An empty list implies all columns should be displayed.
	 *
	 * @return An unmodifiable list of columns to view.
	 */
	public List<String> getView() {
		return view;
	}

	/**
	 * The sort columns.
	 *
	 * <p>
	 * The sort columns are key/value pairs consisting of column-names and direction flags
	 * (<jk>false</jk> = ascending, <jk>true</jk> = descending).
	 *
	 * @return An unmodifiable ordered map of sort columns and directions.
	 */
	public Map<String,Boolean> getSort() {
		return sort;
	}

	/**
	 * The first-row position.
	 *
	 * @return
	 * 	The zero-indexed row number of the first row to display.
	 * 	Default is <c>0</c>
	 */
	public int getPosition() {
		return position;
	}

	/**
	 * The number of rows to return.
	 *
	 * @return
	 * 	The number of rows to return in the result.
	 * 	Default is <c>0</c> which means return all rows.
	 */
	public int getLimit() {
		return limit;
	}

	/**
	 * The ignore-case flag.
	 *
	 * <p>
	 * Used in conjunction with {@link #getSearch()} to specify whether case-insensitive searches should be performed.
	 *
	 * @return
	 * 	The number of rows to return in the result.
	 * 	Default is <jk>false</jk>.
	 */
	public boolean isIgnoreCase() {
		return ignoreCase;
	}
}
