package j2html.tags;

import j2html.attributes.Attr;
import j2html.attributes.Attribute;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;

public abstract class Tag<T extends Tag<T>> extends DomContent {
    protected String tagName;
    private ArrayList<Attribute> attributes;

    protected Tag(String tagName) {
        this.tagName = tagName;
        this.attributes = new ArrayList<>();
    }

    public String getTagName() {
        return this.tagName;
    }

    protected boolean hasTagName() {
        return tagName != null && !tagName.isEmpty();
    }

    String renderOpenTag() throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        renderOpenTag(stringBuilder, null);
        return stringBuilder.toString();
    }

    String renderCloseTag() throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        renderCloseTag(stringBuilder);
        return stringBuilder.toString();
    }

    void renderOpenTag(Appendable writer, Object model) throws IOException {
        if (!hasTagName()) { // avoid <null> and <> tags
            return;
        }
        writer.append("<").append(tagName);
        for (Attribute attribute : attributes) {
            attribute.renderModel(writer, model);
        }
        writer.append(">");
    }

    void renderCloseTag(Appendable writer) throws IOException {
        if (!hasTagName()) { // avoid <null> and <> tags
            return;
        }
        writer.append("</");
        writer.append(tagName);
        writer.append(">");
    }

    ArrayList<Attribute> getAttributes() {
        return attributes;
    }

    /**
     * Sets an attribute on an element
     *
     * @param name  the attribute
     * @param value the attribute value
     */
    boolean setAttribute(String name, String value) {
        if (value == null) {
            return attributes.add(new Attribute(name));
        }
        for (Attribute attribute : attributes) {
            if (attribute.getName().equals(name)) {
                attribute.setValue(value); // update with new value
                return true;
            }
        }
        return attributes.add(new Attribute(name, value));
    }

    /**
     * Sets a custom attribute
     *
     * @param attribute the attribute name
     * @param value     the attribute value
     * @return itself for easy chaining
     */
    public T attr(String attribute, Object value) {
        setAttribute(attribute, value == null ? null : String.valueOf(value));
        return (T) this;
    }

    /**
     * Adds the specified attribute. If the Tag previously contained an attribute with the same name, the old attribute is replaced by the specified attribute.
     *
     * @param attribute the attribute
     * @return itself for easy chaining
     */
    public T attr(Attribute attribute) {
        Iterator<Attribute> iterator = attributes.iterator();
        String name = attribute.getName();
        if (name != null) {
            // name == null is allowed, but those Attributes are not rendered. So we add them anyway.
            while (iterator.hasNext()) {
                Attribute existingAttribute = iterator.next();
                if (existingAttribute.getName().equals(name)) {
                    iterator.remove();
                }
            }
        }
        attributes.add(attribute);
        return (T) this;
    }

    /**
     * Sets a custom attribute without value
     *
     * @param attribute the attribute name
     * @return itself for easy chaining
     * @see Tag#attr(String, Object)
     */
    public T attr(String attribute) {
        return attr(attribute, null);
    }

    /**
     * Call attr-method based on condition
     * {@link #attr(String attribute, Object value)}
     *
     * @param condition the condition
     * @param attribute the attribute name
     * @param value     the attribute value
     * @return itself for easy chaining
     */
    public T condAttr(boolean condition, String attribute, String value) {
        return (condition ? attr(attribute, value) : (T) this);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof Tag)) {
            return false;
        }
        return ((Tag) obj).render().equals(this.render());
    }

    /**
     * Convenience methods that call attr with predefined attributes
     *
     * @return itself for easy chaining
     */
    public T withClasses(String... classes) {
        StringBuilder sb = new StringBuilder();
        for (String s : classes) {
            sb.append(s != null ? s : "").append(" ");
        }
        return attr(Attr.CLASS, sb.toString().trim());
    }

    public T isAutoComplete() {
        return attr(Attr.AUTOCOMPLETE, null);
    }

    public T isAutoFocus() {
        return attr(Attr.AUTOFOCUS, null);
    }

    public T isHidden() {
        return attr(Attr.HIDDEN, null);
    }

    public T isRequired() {
        return attr(Attr.REQUIRED, null);
    }

    public T withAlt(String alt) {
        return attr(Attr.ALT, alt);
    }

    public T withAction(String action) {
        return attr(Attr.ACTION, action);
    }

    public T withCharset(String charset) {
        return attr(Attr.CHARSET, charset);
    }

    public T withClass(String className) {
        return attr(Attr.CLASS, className);
    }

    public T withContent(String content) {
        return attr(Attr.CONTENT, content);
    }

    public T withDir(String dir) {
        return attr(Attr.DIR, dir);
    }

    public T withHref(String href) {
        return attr(Attr.HREF, href);
    }

    public T withId(String id) {
        return attr(Attr.ID, id);
    }

    public T withData(String dataAttr, String value) {
        return attr(Attr.DATA + "-" + dataAttr, value);
    }

    public T withLang(String lang) {
        return attr(Attr.LANG, lang);
    }

    public T withMethod(String method) {
        return attr(Attr.METHOD, method);
    }

    public T withName(String name) {
        return attr(Attr.NAME, name);
    }

    public T withPlaceholder(String placeholder) {
        return attr(Attr.PLACEHOLDER, placeholder);
    }

    public T withTarget(String target) {
        return attr(Attr.TARGET, target);
    }

    public T withTitle(String title) {
        return attr(Attr.TITLE, title);
    }

    public T withType(String type) {
        return attr(Attr.TYPE, type);
    }

    public T withRel(String rel) {
        return attr(Attr.REL, rel);
    }

    public T withRole(String role) {
        return attr(Attr.ROLE, role);
    }

    public T withSrc(String src) {
        return attr(Attr.SRC, src);
    }

    public T withStyle(String style) {
        return attr(Attr.STYLE, style);
    }

    public T withValue(String value) {
        return attr(Attr.VALUE, value);
    }

    public T withCondAutoComplete(boolean condition) {
        return condAttr(condition, Attr.AUTOCOMPLETE, null);
    }

    public T withCondAutoFocus(boolean condition) {
        return condAttr(condition, Attr.AUTOFOCUS, null);
    }

    public T withCondHidden(boolean condition) {
        return condAttr(condition, Attr.HIDDEN, null);
    }

    public T withCondRequired(boolean condition) {
        return condAttr(condition, Attr.REQUIRED, null);
    }

    public T withCondAlt(boolean condition, String alt) {
        return condAttr(condition, Attr.ALT, alt);
    }

    public T withCondAction(boolean condition, String action) {
        return condAttr(condition, Attr.ACTION, action);
    }

    public T withCharset(boolean condition, String charset) {
        return condAttr(condition, Attr.CHARSET, charset);
    }

    public T withCondClass(boolean condition, String className) {
        return condAttr(condition, Attr.CLASS, className);
    }

    public T withCondContent(boolean condition, String content) {
        return condAttr(condition, Attr.CONTENT, content);
    }

    public T withCondDir(boolean condition, String dir) {
        return condAttr(condition, Attr.DIR, dir);
    }

    public T withCondHref(boolean condition, String href) {
        return condAttr(condition, Attr.HREF, href);
    }

    public T withCondId(boolean condition, String id) {
        return condAttr(condition, Attr.ID, id);
    }

    public T withCondData(boolean condition, String dataAttr, String value) {
        return condAttr(condition, Attr.DATA + "-" + dataAttr, value);
    }

    public T withCondLang(boolean condition, String lang) {
        return condAttr(condition, Attr.LANG, lang);
    }

    public T withCondMethod(boolean condition, String method) {
        return condAttr(condition, Attr.METHOD, method);
    }

    public T withCondName(boolean condition, String name) {
        return condAttr(condition, Attr.NAME, name);
    }

    public T withCondPlaceholder(boolean condition, String placeholder) {
        return condAttr(condition, Attr.PLACEHOLDER, placeholder);
    }

    public T withCondTarget(boolean condition, String target) {
        return condAttr(condition, Attr.TARGET, target);
    }

    public T withCondTitle(boolean condition, String title) {
        return condAttr(condition, Attr.TITLE, title);
    }

    public T withCondType(boolean condition, String type) {
        return condAttr(condition, Attr.TYPE, type);
    }

    public T withCondRel(boolean condition, String rel) {
        return condAttr(condition, Attr.REL, rel);
    }

    public T withCondSrc(boolean condition, String src) {
        return condAttr(condition, Attr.SRC, src);
    }

    public T withCondStyle(boolean condition, String style) {
        return condAttr(condition, Attr.STYLE, style);
    }

    public T withCondValue(boolean condition, String value) {
        return condAttr(condition, Attr.VALUE, value);
    }
}


package j2html.utils;

/*
 * JSMin.java 2006-02-13
 *
 * Copyright (c) 2006 John Reilly (www.inconspicuous.org)
 *
 * This work is a translation from C to Java of jsmin.c published by
 * Douglas Crockford.  Permission is hereby granted to use the Java
 * version under the same conditions as the jsmin.c on which it is
 * based.
 *
 *
 *
 *
 * jsmin.c 2003-04-21
 *
 * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * The Software shall be used for Good, not Evil.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PushbackInputStream;

public class JSMin {

    private static final int EOF = -1;
    private PushbackInputStream in;
    private OutputStream out;
    private int theA;
    private int theB;
    private JSMin(InputStream in, OutputStream out) {
        this.in = new PushbackInputStream(in);
        this.out = out;
    }

    /**
     * Compress a JS-string
     *
     * @param code the js-code you want to compress
     * @return the compressed code
     */
    public static String compressJs(String code) {
        InputStream inStream = new ByteArrayInputStream(code.getBytes());
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        JSMin jsmin = new JSMin(inStream, outStream);
        try {
            jsmin.jsmin();
            return outStream.toString().trim();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * isAlphanum -- return true if the character is a letter, digit,
     * underscore, dollar sign, or non-ASCII character.
     */
    private static boolean isAlphanum(int c) {
        return ((c >= 'a' && c <= 'z') ||
            (c >= '0' && c <= '9') ||
            (c >= 'A' && c <= 'Z') ||
            c == '_' ||
            c == '$' ||
            c == '\\' ||
            c > 126);
    }

    /**
     * get -- return the next character from stdin. Watch out for lookahead. If
     * the character is a control character, translate it to a space or
     * linefeed.
     */
    private int get() throws IOException {
        int c = in.read();

        if (c >= ' ' || c == '\n' || c == EOF) {
            return c;
        }

        if (c == '\r') {
            return '\n';
        }

        return ' ';
    }

    /**
     * Get the next character without getting it.
     */
    private int peek() throws IOException {
        int lookaheadChar = in.read();
        in.unread(lookaheadChar);
        return lookaheadChar;
    }

    /**
     * next -- get the next character, excluding comments. peek() is used to see
     * if a '/' is followed by a '/' or '*'.
     */
    private int next() throws IOException, UnterminatedCommentException {
        int c = get();
        if (c == '/') {
            switch (peek()) {
                case '/':
                    for (; ; ) {
                        c = get();
                        if (c <= '\n') {
                            return c;
                        }
                    }

                case '*':
                    get();
                    for (; ; ) {
                        switch (get()) {
                            case '*':
                                if (peek() == '/') {
                                    get();
                                    return ' ';
                                }
                                break;
                            case EOF:
                                throw new UnterminatedCommentException();
                        }
                    }

                default:
                    return c;
            }

        }
        return c;
    }

    /**
     * action -- do something! What you do is determined by the argument: 1
     * Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B.
     * (Delete A). 3 Get the next B. (Delete B). action treats a string as a
     * single character. Wow! action recognizes a regular expression if it is
     * preceded by ( or , or =.
     */

    private void action(int d) throws IOException, UnterminatedRegExpLiteralException,
        UnterminatedCommentException, UnterminatedStringLiteralException {
        switch (d) {
            case 1:
                out.write(theA);
            case 2:
                theA = theB;

                if (theA == '\'' || theA == '"') {
                    for (; ; ) {
                        out.write(theA);
                        theA = get();
                        if (theA == theB) {
                            break;
                        }
                        if (theA <= '\n') {
                            throw new UnterminatedStringLiteralException();
                        }
                        if (theA == '\\') {
                            out.write(theA);
                            theA = get();
                        }
                    }
                }

            case 3:
                theB = next();
                if (theB == '/' && (theA == '(' || theA == ',' || theA == '=')) {
                    out.write(theA);
                    out.write(theB);
                    for (; ; ) {
                        theA = get();
                        if (theA == '/') {
                            break;
                        } else if (theA == '\\') {
                            out.write(theA);
                            theA = get();
                        } else if (theA <= '\n') {
                            throw new UnterminatedRegExpLiteralException();
                        }
                        out.write(theA);
                    }
                    theB = next();
                }
        }
    }

    /**
     * jsmin -- Copy the input to the output, deleting the characters which are
     * insignificant to JavaScript. Comments will be removed. Tabs will be
     * replaced with spaces. Carriage returns will be replaced with linefeeds.
     * Most spaces and linefeeds will be removed.
     */
    public void jsmin() throws IOException, UnterminatedRegExpLiteralException, UnterminatedCommentException, UnterminatedStringLiteralException {
        theA = '\n';
        action(3);
        while (theA != EOF) {
            switch (theA) {
                case ' ':
                    if (isAlphanum(theB)) {
                        action(1);
                    } else {
                        action(2);
                    }
                    break;
                case '\n':
                    switch (theB) {
                        case '{':
                        case '[':
                        case '(':
                        case '+':
                        case '-':
                            action(1);
                            break;
                        case ' ':
                            action(3);
                            break;
                        default:
                            if (isAlphanum(theB)) {
                                action(1);
                            } else {
                                action(2);
                            }
                    }
                    break;
                default:
                    switch (theB) {
                        case ' ':
                            if (isAlphanum(theA)) {
                                action(1);
                                break;
                            }
                            action(3);
                            break;
                        case '\n':
                            switch (theA) {
                                case '}':
                                case ']':
                                case ')':
                                case '+':
                                case '-':
                                case '"':
                                case '\'':
                                    action(1);
                                    break;
                                default:
                                    if (isAlphanum(theA)) {
                                        action(1);
                                    } else {
                                        action(3);
                                    }
                            }
                            break;
                        default:
                            action(1);
                            break;
                    }
            }
        }
        out.flush();
    }

    private class UnterminatedCommentException extends Exception {
    }

    private class UnterminatedStringLiteralException extends Exception {
    }

    private class UnterminatedRegExpLiteralException extends Exception {
    }

}


/*
 * * CSSMin Copyright License Agreement (BSD License)
 *

 * Copyright (c) 2011-2014, Barry van Oudtshoorn
 * All rights reserved.
 *

 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *

 * - Redistributions of source code must retain the above
 * copyright notice, this list of conditions and the
 * following disclaimer.
 *

 * - Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the
 * following disclaimer in the documentation and/or other
 * materials provided with the distribution.
 *

 * - Neither the name of Barryvan nor the names of its
 * contributors may be used to endorse or promote products
 * derived from this software without specific prior
 * written permission.
 *

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *

 * CSSMin takes in well-formed, human-readable CSS and reduces its size substantially.
 * It removes unnecessary whitespace and comments, and orders the contents of CSS
 * selectors alphabetically to enhance GZIP compression.
 *

 * Originally by Barry van Oudtshoorn, with bug reports, fixes, and contributions by
 *

 *
Kevin de Groote

 *
Pedro Pinheiro

 *
Asier Lostalé

 *
David Åse

 *

 * Some code is based on the YUI CssCompressor code, by Julien Lecomte.
 *
 * @author Barry van Oudtshoorn
 * Repo: https://github.com/barryvan/CSSMin
 */

package j2html.utils;

import java.io.BufferedReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Vector;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

public class CSSMin {

    private static final Logger LOG = Logger.getLogger(CSSMin.class.getName());
    static boolean debugLogging = false;

    private CSSMin() {
    }

    /**
     * Minify supplied CSS.
     *
     * @param input the CSS
     * @return the compressed version
     */
    public static String compressCss(String input) {
        try {
            int k,
                j, // Number of open braces
                n; // Current position in stream
            char curr;

            BufferedReader br = new BufferedReader(new StringReader(input));
            StringBuilder sb = new StringBuilder();

            if (debugLogging) {
                LOG.info("Reading file into StringBuffer...");
            }
            String s;
            while ((s = br.readLine()) != null) {
                if (s.trim().equals("")) {
                    continue;
                }
                sb.append(s);
            }

            if (debugLogging) {
                LOG.info("Removing comments...");
            }
            // Find the start of the comment
            n = 0;
            while ((n = sb.indexOf("/*", n)) != -1) {
                if (sb.charAt(n + 2) == '*') { // Retain special comments
                    n += 2;
                    continue;
                }
                k = sb.indexOf("*/", n + 2);
                if (k == -1) {
                    throw new UnterminatedCommentException();
                }
                sb.delete(n, k + 2);
            }
            if (debugLogging) {
                LOG.info(sb.toString());
                LOG.info("\n\n");
            }

            if (debugLogging) {
                LOG.info("Parsing and processing selectors...");
            }
            Vector<Selector> selectors = new Vector<>();
            n = 0;
            j = 0;
            for (int i = 0; i < sb.length(); i++) {
                curr = sb.charAt(i);
                if (j < 0) {
                    throw new UnbalancedBracesException();
                }
                if (curr == '{') {
                    j++;
                } else if (curr == '}') {
                    j--;
                    if (j == 0) {
                        try {
                            selectors.addElement(new Selector(sb.substring(n, i + 1)));
                        } catch (UnterminatedSelectorException usex) {
                            LOG.warning("Unterminated selector: " + usex.getMessage());
                        } catch (EmptySelectorBodyException ebex) {
                            if (debugLogging) {
                                LOG.warning("Empty selector body: " + ebex.getMessage());
                            }
                        }
                        n = i + 1;
                    }
                }
            }

            StringBuilder result = new StringBuilder();

            for (Selector selector : selectors) {
                result.append(selector);
            }

            if (debugLogging) {
                LOG.info("Process completed successfully.");
            }

            return result.toString();

        } catch (UnterminatedCommentException ucex) {
            LOG.warning("Unterminated comment.");
        } catch (UnbalancedBracesException ubex) {
            LOG.warning("Unbalanced braces.");
        } catch (Exception ex) {
            LOG.warning(ex.getMessage());
        }

        return null;

    }
}

class Selector {

    private static final Logger LOG = Logger.getLogger(Selector.class.getName());

    private Property[] properties = null;
    private Vector<Selector> subSelectors = null;
    private String selector;

    /**
     * Creates a new Selector using the supplied strings.
     *
     * @param selector The selector; for example, "div { border: solid 1px red; color: blue; }"
     * @throws IncompleteSelectorException, UnterminatedSelectorException, EmptySelectorBodyException If the selector is incomplete and cannot be parsed.
     */
    Selector(String selector) throws IncompleteSelectorException, UnterminatedSelectorException, EmptySelectorBodyException {
        String[] parts = selector.split("\\{"); // We have to escape the { with a \ for the regex, which itself requires escaping for the string. Sigh.
        if (parts.length < 2) {
            throw new IncompleteSelectorException(selector);
        }

        this.selector = parts[0].trim();

        // Simplify combinators
        this.selector = this.selector.replaceAll("\\s?(\\+|~|,|=|~=|\\^=|\\$=|\\*=|\\|=|>)\\s?", "$1");

        // We're dealing with a nested property, eg @-webkit-keyframes
        if (parts.length > 2) {
            this.subSelectors = new Vector<>();
            parts = selector.split("(\\s*\\{\\s*)|(\\s*\\}\\s*)");
            for (int i = 1; i < parts.length; i += 2) {
                parts[i] = parts[i].trim();
                parts[i + 1] = parts[i + 1].trim();
                if (!(parts[i].equals("") || (parts[i + 1].equals("")))) {
                    this.subSelectors.addElement(new Selector(parts[i] + "{" + parts[i + 1] + "}"));
                }
            }
        } else {
            String contents = parts[parts.length - 1].trim();
            if (CSSMin.debugLogging) {
                LOG.info("Parsing selector: " + this.selector);
                LOG.info("\t" + contents);
            }
            if (contents.charAt(contents.length() - 1) != '}') { // Ensure that we have a leading and trailing brace.
                throw new UnterminatedSelectorException(selector);
            }
            if (contents.length() == 1) {
                throw new EmptySelectorBodyException(selector);
            }
            int endIndex = contents.endsWith(";}") ? 2 : 1;
            contents = contents.substring(0, contents.length() - endIndex);

            this.properties = new Property[0];
            this.properties = parseProperties(contents).toArray(this.properties);
            sortProperties(this.properties);
        }
    }

    /**
     * Prints out this selector and its contents nicely, with the contents sorted alphabetically.
     *
     * @return A string representing this selector, minified.
     */
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(this.selector).append("{");
        if (this.subSelectors != null) {
            for (Selector s : this.subSelectors) {
                sb.append(s.toString());
            }
        }
        if (this.properties != null) {
            for (Property p : this.properties) {
                sb.append(p.toString());
            }
        }
        if (sb.charAt(sb.length() - 1) == ';') {
            sb.deleteCharAt(sb.length() - 1);
        }
        sb.append("}");
        return sb.toString();
    }

    /**
     * Parses out the properties of a selector's body.
     *
     * @param contents The body; for example, "border: solid 1px red; color: blue;"
     * @return An array of properties parsed from this selector.
     */
    private ArrayList<Property> parseProperties(String contents) {
        ArrayList<String> parts = new ArrayList<>();
        boolean bInsideString = false,
            bInsideURL = false;
        int j = 0;
        String substr;
        for (int i = 0; i < contents.length(); i++) {
            if (bInsideString) { // If we're inside a string
                bInsideString = !(contents.charAt(i) == '"');
            } else if (bInsideURL) { // If we're inside a URL
                bInsideURL = !(contents.charAt(i) == ')');
            } else if (contents.charAt(i) == '"') {
                bInsideString = true;
            } else if (contents.charAt(i) == '(') {
                if ((i - 3) > 0 && "url".equals(contents.substring(i - 3, i))) {
                    bInsideURL = true;
                }
            } else if (contents.charAt(i) == ';') {
                substr = contents.substring(j, i);
                if (!(substr.trim().equals(""))) {
                    parts.add(substr);
                }
                j = i + 1;
            }
        }
        substr = contents.substring(j, contents.length());
        if (!(substr.trim().equals(""))) {
            parts.add(substr);
        }

        ArrayList<Property> results = new ArrayList<>();
        for (String part : parts) {
            try {
                results.add(new Property(part));
            } catch (IncompletePropertyException ipex) {
                LOG.warning("Incomplete property in selector \"" + this.selector + "\": \"" + ipex.getMessage() + "\"");
            }
        }

        return results;
    }

    /**
     * Sorts the properties array to enhance gzipping.
     *
     * @param properties The array to be sorted.
     */
    private void sortProperties(Property[] properties) {
        Arrays.sort(properties);
    }
}

class Property implements Comparable<Property> {

    private static final Logger LOG = Logger.getLogger(Property.class.getName());

    private String property;
    private Part[] parts;

    /**
     * Creates a new Property using the supplied strings. Parses out the values of the property selector.
     *
     * @param property The property; for example, "border: solid 1px red;" or "-moz-box-shadow: 3px 3px 3px rgba(255, 255, 0, 0.5);".
     * @throws IncompletePropertyException If the property is incomplete and cannot be parsed.
     */
    Property(String property) throws IncompletePropertyException {
        try {
            // Parse the property.
            ArrayList<String> parts = new ArrayList<>();
            boolean bCanSplit = true;
            int j = 0;
            String substr;
            if (CSSMin.debugLogging) {
                LOG.info("\t\tExamining property: " + property);
            }
            for (int i = 0; i < property.length(); i++) {
                if (!bCanSplit) { // If we're inside a string
                    bCanSplit = (property.charAt(i) == '"');
                } else if (property.charAt(i) == '"') {
                    bCanSplit = false;
                } else if (property.charAt(i) == ':' && parts.isEmpty()) {
                    substr = property.substring(j, i);
                    if (!(substr.trim().equals(""))) {
                        parts.add(substr);
                    }
                    j = i + 1;
                }
            }
            substr = property.substring(j, property.length());
            if (!(substr.trim().equals(""))) {
                parts.add(substr);
            }
            if (parts.size() < 2) {
                throw new IncompletePropertyException(property);
            }

            String prop = parts.get(0).trim();
            if (!(prop.length() > 2 && prop.substring(0, 2).equals("--"))) {
                prop = prop.toLowerCase();
            }
            this.property = prop;

            this.parts = parseValues(simplifyColours(parts.get(1).trim().replaceAll(", ", ",")));

        } catch (PatternSyntaxException e) {
            // Invalid regular expression used.
        }
    }

    /**
     * Prints out this property nicely.
     *
     * @return A string representing this property, minified.
     */
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(this.property).append(":");
        for (Part p : this.parts) {
            sb.append(p.toString()).append(",");
        }
        sb.deleteCharAt(sb.length() - 1); // Delete the trailing comma.
        sb.append(";");
        if (CSSMin.debugLogging) {
            LOG.info(sb.toString());
        }
        return sb.toString();
    }

    /**
     * Compare this property with another.
     */
    public int compareTo(Property other) {
        // We can't just use String.compareTo(), because we need to sort properties that have hack prefixes last -- eg, *display should come after display.
        return sort(this.property).compareTo(sort(other.property));
    }

    private String sort(String thisProp) {
        if (thisProp.charAt(0) == '-') {
            thisProp = thisProp.substring(1);
            thisProp = thisProp.substring(thisProp.indexOf('-') + 1);
        } else if (thisProp.charAt(0) < 65) {
            thisProp = thisProp.substring(1);
        }
        return thisProp;
    }

    /**
     * Parse the values out of a property.
     *
     * @param contents The property to parse
     * @return An array of Parts
     */
    private Part[] parseValues(String contents) {
        String[] parts = contents.split(",");
        Part[] results = new Part[parts.length];

        for (int i = 0; i < parts.length; i++) {
            try {
                results[i] = new Part(parts[i], property);
            } catch (Exception e) {
                LOG.warning(e.getMessage());
                results[i] = null;
            }
        }

        return results;
    }

    private String simplifyColours(String contents) {
        // This replacement, although it results in a smaller uncompressed file,
        // actually makes the gzipped file bigger -- people tend to use rgba(0,0,0,0.x)
        // quite a lot, which means that rgba(0,0,0,0) has its first eight or so characters
        // compressed really efficiently; much more so than "transparent".
        //contents = contents.replaceAll("rgba\\(0,0,0,0\\)", "transparent");

        return simplifyRGBColours(contents);
    }

    // Convert rgb(51,102,153) to #336699 (this code largely based on YUI code)
    private String simplifyRGBColours(String contents) {
        StringBuffer newContents = new StringBuffer();
        StringBuffer hexColour;
        String[] rgbColours;
        int colourValue;

        Pattern pattern = Pattern.compile("rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)");
        Matcher matcher = pattern.matcher(contents);

        while (matcher.find()) {
            hexColour = new StringBuffer("#");
            rgbColours = matcher.group(1).split(",");
            for (String rgbColour : rgbColours) {
                colourValue = Integer.parseInt(rgbColour);
                if (colourValue < 16) {
                    hexColour.append("0");
                }
                hexColour.append(Integer.toHexString(colourValue));
            }
            matcher.appendReplacement(newContents, hexColour.toString());
        }
        matcher.appendTail(newContents);

        return newContents.toString();
    }
}

class Part {
    private String contents;
    private String property;

    /**
     * Create a new property by parsing the given string.
     *
     * @param contents The string to parse.
     * @throws Exception If the part cannot be parsed.
     */
    Part(String contents, String property) throws Exception {
        // Many of these regular expressions are adapted from those used in the YUI CSS Compressor.

        // For simpler regexes.
        this.contents = " " + contents;
        this.property = property;

        simplify();
    }

    private void simplify() {

        // !important doesn't need to be spaced
        this.contents = this.contents.replaceAll(" !important", "!important");

        // Replace 0in, 0cm, etc. with just 0
        this.contents = this.contents.replaceAll("(\\s)(0)(px|em|%|in|cm|mm|pc|pt|ex)", "$1$2");

        this.contents = this.contents.trim();

        // Simplify multiple zeroes
        if (this.contents.equals("0 0 0 0")) {
            this.contents = "0";
        }
        if (this.contents.equals("0 0 0")) {
            this.contents = "0";
        }
        if (this.contents.equals("0 0")) {
            this.contents = "0";
        }

        // Simplify multiple-parameter properties
        simplifyParameters();

        // Simplify font weights
        simplifyFontWeights();

        // Strip unnecessary quotes from url() and single-word parts, and make as much lowercase as possible.
        simplifyQuotesAndCaps();

        // Simplify colours
        simplifyColourNames();
        simplifyHexColours();
    }

    private void simplifyParameters() {
        if (this.property.equals("background-size")) {
            return;
        }

        StringBuilder newContents = new StringBuilder();

        String[] params = this.contents.split(" ");
        if (params.length == 4 && params[1].equalsIgnoreCase(params[3])) {
            // We can drop off the fourth item if the second and fourth items match
            // ie turn 3px 0 3px 0 into 3px 0 3px
            params = Arrays.copyOf(params, 3);
        }
        if (params.length == 3 && params[0].equalsIgnoreCase(params[2])) {
            // We can drop off the third item if the first and third items match
            // ie turn 3px 0 3px into 3px 0
            params = Arrays.copyOf(params, 2);
        }
        if (params.length == 2 && params[0].equalsIgnoreCase(params[1])) {
            // We can drop off the second item if the first and second items match
            // ie turn 3px 3px into 3px
            params = Arrays.copyOf(params, 1);
        }

        for (String param : params) {
            newContents.append(param).append(" ");
        }
        newContents.deleteCharAt(newContents.length() - 1); // Delete the trailing space

        this.contents = newContents.toString();
    }

    private void simplifyFontWeights() {
        if (!this.property.equals("font-weight")) {
            return;
        }

        String lcContents = this.contents.toLowerCase();

        for (int i = 0; i < Constants.fontWeightNames.length; i++) {
            if (lcContents.equals(Constants.fontWeightNames[i])) {
                this.contents = Constants.fontWeightValues[i];
                break;
            }
        }
    }

    private void simplifyQuotesAndCaps() {
        // Strip quotes from URLs
        if ((this.contents.length() > 4) && (this.contents.substring(0, 4).equalsIgnoreCase("url("))) {
            this.contents = this.contents.replaceAll("(?i)url\\(('|\")?(.*?)\\1\\)", "url($2)");
        } else if ((this.contents.length() > 4) && (this.contents.substring(0, 4).equalsIgnoreCase("var("))) {
            this.contents = this.contents.replaceAll("\\s", "");
        } else {
            String[] words = this.contents.split("\\s");
            if (words.length == 1) {
                if (!this.property.equalsIgnoreCase("animation-name")) {
                    this.contents = this.contents.toLowerCase();
                }
                this.contents = this.contents.replaceAll("('|\")?(.*?)\1", "$2");
            }
        }
    }

    private void simplifyColourNames() {
        String lcContents = this.contents.toLowerCase();

        for (int i = 0; i < Constants.htmlColourNames.length; i++) {
            if (lcContents.equals(Constants.htmlColourNames[i])) {
                if (Constants.htmlColourValues[i].length() < Constants.htmlColourNames[i].length()) {
                    this.contents = Constants.htmlColourValues[i];
                }
                break;
            } else if (lcContents.equals(Constants.htmlColourValues[i])) {
                if (Constants.htmlColourNames[i].length() < Constants.htmlColourValues[i].length()) {
                    this.contents = Constants.htmlColourNames[i];
                }
            }
        }
    }

    private void simplifyHexColours() {
        StringBuffer newContents = new StringBuffer();

        Pattern pattern = Pattern.compile("#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])");
        Matcher matcher = pattern.matcher(this.contents);

        while (matcher.find()) {
            if (matcher.group(1).equalsIgnoreCase(matcher.group(2)) && matcher.group(3).equalsIgnoreCase(matcher.group(4)) && matcher.group(5).equalsIgnoreCase(matcher.group(6))) {
                matcher.appendReplacement(newContents, "#" + matcher.group(1).toLowerCase() + matcher.group(3).toLowerCase() + matcher.group(5).toLowerCase());
            } else {
                matcher.appendReplacement(newContents, matcher.group().toLowerCase());
            }
        }
        matcher.appendTail(newContents);

        this.contents = newContents.toString();
    }

    /**
     * Returns itself.
     *
     * @return this part's string representation.
     */
    public String toString() {
        return this.contents;
    }
}

class UnterminatedCommentException extends Exception {
}

class UnbalancedBracesException extends Exception {
}

class IncompletePropertyException extends Exception {
    private final String message;

    IncompletePropertyException(String message) {
        this.message = message;
    }

    public String getMessage() {
        return this.message;
    }
}

class EmptySelectorBodyException extends Exception {
    private final String message;

    EmptySelectorBodyException(String message) {
        this.message = message;
    }

    public String getMessage() {
        return this.message;
    }
}

class UnterminatedSelectorException extends Exception {
    private final String message;

    UnterminatedSelectorException(String message) {
        this.message = message;
    }

    public String getMessage() {
        return this.message;
    }
}

class IncompleteSelectorException extends Exception {
    private final String message;

    IncompleteSelectorException(String message) {
        this.message = message;
    }

    public String getMessage() {
        return this.message;
    }
}

class Constants {
    static final String[] htmlColourNames = {"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue", "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", "darkslateblue", "darkslategray", "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "gray", "green", "greenyellow", "honeydew", "hotpink", "indianred ", "indigo ", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgrey", "lightgreen", "lightpink", "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", "purple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "snow", "springgreen", "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen"};
    static final String[] htmlColourValues = {"#f0f8ff", "#faebd7", "#00ffff", "#7fffd4", "#f0ffff", "#f5f5dc", "#ffe4c4", "#000", "#ffebcd", "#00f", "#8a2be2", "#a52a2a", "#deb887", "#5f9ea0", "#7fff00", "#d2691e", "#ff7f50", "#6495ed", "#fff8dc", "#dc143c", "#0ff", "#00008b", "#008b8b", "#b8860b", "#a9a9a9", "#006400", "#bdb76b", "#8b008b", "#556b2f", "#ff8c00", "#9932cc", "#8b0000", "#e9967a", "#8fbc8f", "#483d8b", "#2f4f4f", "#00ced1", "#9400d3", "#ff1493", "#00bfff", "#696969", "#1e90ff", "#b22222", "#fffaf0", "#228b22", "#f0f", "#dcdcdc", "#f8f8ff", "#ffd700", "#daa520", "#808080", "#008000", "#adff2f", "#f0fff0", "#ff69b4", "#cd5c5c", "#4b0082", "#fffff0", "#f0e68c", "#e6e6fa", "#fff0f5", "#7cfc00", "#fffacd", "#add8e6", "#f08080", "#e0ffff", "#fafad2", "#d3d3d3", "#90ee90", "#ffb6c1", "#ffa07a", "#20b2aa", "#87cefa", "#789", "#b0c4de", "#ffffe0", "#0f0", "#32cd32", "#faf0e6", "#f0f", "#800000", "#66cdaa", "#0000cd", "#ba55d3", "#9370d8", "#3cb371", "#7b68ee", "#00fa9a", "#48d1cc", "#c71585", "#191970", "#f5fffa", "#ffe4e1", "#ffe4b5", "#ffdead", "#000080", "#fdf5e6", "#808000", "#6b8e23", "#ffa500", "#ff4500", "#da70d6", "#eee8aa", "#98fb98", "#afeeee", "#d87093", "#ffefd5", "#ffdab9", "#cd853f", "#ffc0cb", "#dda0dd", "#b0e0e6", "#800080", "#f00", "#bc8f8f", "#4169e1", "#8b4513", "#fa8072", "#f4a460", "#2e8b57", "#fff5ee", "#a0522d", "#c0c0c0", "#87ceeb", "#6a5acd", "#708090", "#fffafa", "#00ff7f", "#4682b4", "#d2b48c", "#008080", "#d8bfd8", "#ff6347", "#40e0d0", "#ee82ee", "#f5deb3", "#fff", "#f5f5f5", "#ff0", "#9acd32"};
    static final String[] fontWeightNames = {"normal", "bold", "bolder", "lighter"};
    static final String[] fontWeightValues = {"400", "700", "900", "100"};
}


package com.arlib.floatingsearchview.util.view;

/**
 * Copyright (C) 2015 Ari C.
 *

 * 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.
 */

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.support.v7.view.SupportMenuInflater;
import android.support.v7.view.menu.MenuBuilder;
import android.support.v7.view.menu.MenuItemImpl;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;

import com.arlib.floatingsearchview.R;
import com.arlib.floatingsearchview.util.MenuPopupHelper;
import com.arlib.floatingsearchview.util.Util;
import com.bartoszlipinski.viewpropertyobjectanimator.ViewPropertyObjectAnimator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * A view that shows menu items as actions or
 * as items in a overflow popup.
 */
public class MenuView extends LinearLayout {

    private final int HIDE_IF_ROOM_ITEMS_ANIM_DURATION = 400;
    private final int SHOW_IF_ROOM_ITEMS_ANIM_DURATION = 450;

    private final float ACTION_DIMENSION_PX;

    private int mMenu = -1;
    private MenuBuilder mMenuBuilder;
    private SupportMenuInflater mMenuInflater;
    private MenuPopupHelper mMenuPopupHelper;

    private MenuBuilder.Callback mMenuCallback;

    private int mActionIconColor;
    private int mOverflowIconColor;

    //all menu items
    private List<MenuItemImpl> mMenuItems;

    //items that are currently presented as actions
    private List<MenuItemImpl> mActionItems = new ArrayList<>();

    private List<MenuItemImpl> mActionShowAlwaysItems = new ArrayList<>();

    private boolean mHasOverflow = false;

    private OnVisibleWidthChangedListener mOnVisibleWidthChangedListener;
    private int mVisibleWidth;

    private List<ObjectAnimator> anims = new ArrayList<>();

    public interface OnVisibleWidthChangedListener {
        void onItemsMenuVisibleWidthChanged(int newVisibleWidth);
    }

    public MenuView(Context context) {
        this(context, null);
    }

    public MenuView(Context context, AttributeSet attrs) {
        super(context, attrs);
        ACTION_DIMENSION_PX = context.getResources().getDimension(R.dimen.square_button_size);
        init();
    }

    public List<MenuItemImpl> getCurrentMenuItems(){
        return mMenuItems;
    }

    private void init() {
        mMenuBuilder = new MenuBuilder(getContext());
        mMenuPopupHelper = new MenuPopupHelper(getContext(), mMenuBuilder, this);
        mActionIconColor = Util.getColor(getContext(), R.color.gray_active_icon);
        mOverflowIconColor = Util.getColor(getContext(), R.color.gray_active_icon);
    }

    public void setActionIconColor(int actionColor) {
        this.mActionIconColor = actionColor;
        refreshColors();
    }

    public void setOverflowColor(int overflowColor) {
        this.mOverflowIconColor = overflowColor;
        refreshColors();
    }

    private void refreshColors() {
        for (int i = 0; i < getChildCount(); i++) {
            Util.setIconColor(((ImageView) getChildAt(i)), mActionIconColor);
            if (mHasOverflow && i == getChildCount() - 1) {
                Util.setIconColor(((ImageView) getChildAt(i)), mOverflowIconColor);
            }
        }
    }

    /**
     * Set the callback that will be called when menu
     * items a selected.
     *
     * @param menuCallback
     */
    public void setMenuCallback(MenuBuilder.Callback menuCallback) {
        this.mMenuCallback = menuCallback;
    }

    /**
     * Resets the the view to fit into a new
     * available width.
     *

     *
This clears and then re-inflates the menu items
     * , removes all of its associated action views, and re-creates
     * the menu and action items to fit in the new width.

     *
     * @param availWidth the width available for the menu to use. If
     *                   there is room, menu items that are flagged with
     *                   android:showAsAction="ifRoom" or android:showAsAction="always"
     *                   will show as actions.
     */
    public void reset(int menu, int availWidth) {
        mMenu = menu;
        if (mMenu == -1) {
            return;
        }

        mActionShowAlwaysItems = new ArrayList<>();
        mActionItems = new ArrayList<>();
        mMenuItems = new ArrayList<>();
        mMenuBuilder = new MenuBuilder(getContext());
        mMenuPopupHelper = new MenuPopupHelper(getContext(), mMenuBuilder, this);

        //clean view and re-inflate
        removeAllViews();
        getMenuInflater().inflate(mMenu, mMenuBuilder);

        mMenuItems = mMenuBuilder.getActionItems();
        mMenuItems.addAll(mMenuBuilder.getNonActionItems());

        Collections.sort(mMenuItems, new Comparator<MenuItemImpl>() {
            @Override
            public int compare(MenuItemImpl lhs, MenuItemImpl rhs) {
                return ((Integer) lhs.getOrder()).compareTo(rhs.getOrder());
            }
        });

        List<MenuItemImpl> localActionItems = filter(mMenuItems, new MenuItemImplPredicate() {
            @Override
            public boolean apply(MenuItemImpl menuItem) {
                return menuItem.getIcon() != null && (menuItem.requiresActionButton() || menuItem.requestsActionButton());
            }
        });

        int availItemRoom = availWidth / (int) ACTION_DIMENSION_PX;

        //determine if to show overflow menu
        boolean addOverflowAtTheEnd = false;
        if (((localActionItems.size() < mMenuItems.size()) || availItemRoom < localActionItems.size())) {
            addOverflowAtTheEnd = true;
            availItemRoom--;
        }

        ArrayList<Integer> actionItemsIds = new ArrayList<>();
        if (availItemRoom > 0) {
            for (int i = 0; i < localActionItems.size(); i++) {

                final MenuItemImpl menuItem = localActionItems.get(i);
                if (menuItem.getIcon() != null) {

                    ImageView action = createActionView();
                    action.setContentDescription(menuItem.getTitle());
                    action.setImageDrawable(menuItem.getIcon());
                    Util.setIconColor(action, mActionIconColor);
                    addView(action);
                    mActionItems.add(menuItem);
                    actionItemsIds.add(menuItem.getItemId());

                    action.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {

                            if (mMenuCallback != null) {
                                mMenuCallback.onMenuItemSelected(mMenuBuilder, menuItem);
                            }
                        }
                    });

                    availItemRoom--;
                    if (availItemRoom == 0) {
                        break;
                    }
                }
            }
        }

        mHasOverflow = addOverflowAtTheEnd;
        if (addOverflowAtTheEnd) {

            ImageView overflowAction = getOverflowActionView();
            overflowAction.setImageResource(R.drawable.ic_more_vert_black_24dp);
            Util.setIconColor(overflowAction, mOverflowIconColor);
            addView(overflowAction);

            overflowAction.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    mMenuPopupHelper.show();
                }
            });

            mMenuBuilder.setCallback(mMenuCallback);
        }

        //remove all menu items that will be shown as icons (the action items) from the overflow menu
        for (int id : actionItemsIds) {
            mMenuBuilder.removeItem(id);
        }
        actionItemsIds = null;

        if (mOnVisibleWidthChangedListener != null) {
            mVisibleWidth = ((int) ACTION_DIMENSION_PX * getChildCount()) - (mHasOverflow ? Util.dpToPx(8) : 0);
            mOnVisibleWidthChangedListener.onItemsMenuVisibleWidthChanged(mVisibleWidth);
        }
    }

    public int getVisibleWidth() {
        return mVisibleWidth;
    }

    private ImageView createActionView() {
        return (ImageView) LayoutInflater.from(getContext()).inflate(R.layout.action_item_layout, this, false);
    }

    private ImageView getOverflowActionView() {
        return (ImageView) LayoutInflater.from(getContext()).inflate(R.layout.overflow_action_item_layout, this, false);
    }

    /**
     * Hides all the menu items flagged with "ifRoom"
     *
     * @param withAnim
     */
    public void hideIfRoomItems(boolean withAnim) {

        if (mMenu == -1) {
            return;
        }

        mActionShowAlwaysItems.clear();
        cancelChildAnimListAndClear();

        List<MenuItemImpl> showAlwaysActionItems = filter(mMenuItems, new MenuItemImplPredicate() {
            @Override
            public boolean apply(MenuItemImpl menuItem) {
                return  menuItem.getIcon() != null && menuItem.requiresActionButton();
            }
        });

        int actionItemIndex;
        for (actionItemIndex = 0;
             actionItemIndex < mActionItems.size() && actionItemIndex < showAlwaysActionItems.size();
             actionItemIndex++) {

            final MenuItemImpl showAlwaysActionItem = showAlwaysActionItems.get(actionItemIndex);

            //reset action item image if needed
            if (mActionItems.get(actionItemIndex).getItemId() != showAlwaysActionItem.getItemId()) {

                ImageView action = (ImageView) getChildAt(actionItemIndex);
                action.setImageDrawable(showAlwaysActionItem.getIcon());
                Util.setIconColor(action, mOverflowIconColor);
                action.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {

                        if (mMenuCallback != null) {
                            mMenuCallback.onMenuItemSelected(mMenuBuilder, showAlwaysActionItem);
                        }
                    }
                });
            }
            mActionShowAlwaysItems.add(showAlwaysActionItem);
        }

        final int diff = mActionItems.size() - actionItemIndex + (mHasOverflow ? 1 : 0);

        anims = new ArrayList<>();

        //add anims for moving showAlwaysItem views to the right
        for (int i = 0; i < actionItemIndex; i++) {

            final View currentChild = getChildAt(i);
            final float destTransX = (ACTION_DIMENSION_PX * diff) - (mHasOverflow ? Util.dpToPx(8) : 0);
            anims.add(ViewPropertyObjectAnimator.animate(currentChild)
                    .setDuration(withAnim ? HIDE_IF_ROOM_ITEMS_ANIM_DURATION : 0)
                    .setInterpolator(new AccelerateInterpolator())
                    .addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {

                            currentChild.setTranslationX(destTransX);
                        }
                    })
                    .translationXBy(destTransX).get());
        }

        //add anims for moving to right and/or zooming out previously shown items
        for (int i = actionItemIndex; i < (diff + actionItemIndex); i++) {

            final View currentView = getChildAt(i);
            currentView.setClickable(false);

            //move to right
            if (i != (getChildCount() - 1)) {
                anims.add(ViewPropertyObjectAnimator.animate(currentView).setDuration(withAnim ? HIDE_IF_ROOM_ITEMS_ANIM_DURATION : 0)
                        .addListener(new AnimatorListenerAdapter() {
                            @Override
                            public void onAnimationEnd(Animator animation) {

                                currentView.setTranslationX(ACTION_DIMENSION_PX);
                            }
                        }).translationXBy(ACTION_DIMENSION_PX).get());
            }

            //scale and zoom out
            anims.add(ViewPropertyObjectAnimator.animate(currentView)
                    .setDuration(withAnim ? HIDE_IF_ROOM_ITEMS_ANIM_DURATION : 0)
                    .addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {

                            currentView.setScaleX(0.5f);
                        }
                    }).scaleX(.5f).get());
            anims.add(ViewPropertyObjectAnimator.animate(currentView)
                    .setDuration(withAnim ? HIDE_IF_ROOM_ITEMS_ANIM_DURATION : 0)
                    .addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {

                            currentView.setScaleY(0.5f);
                        }
                    }).scaleY(.5f).get());
            anims.add(ViewPropertyObjectAnimator.animate(currentView)
                    .setDuration(withAnim ? HIDE_IF_ROOM_ITEMS_ANIM_DURATION : 0)
                    .addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {

                            currentView.setAlpha(0.0f);
                        }
                    }).alpha(0.0f).get());
        }

        final int actionItemsCount = actionItemIndex;

        //finally, run animation
        if (!anims.isEmpty()) {

            AnimatorSet animSet = new AnimatorSet();
            if (!withAnim) {
                //temporary, from laziness
                animSet.setDuration(0);
            }
            animSet.playTogether(anims.toArray(new ObjectAnimator[anims.size()]));
            animSet.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {

                    if (mOnVisibleWidthChangedListener != null) {
                        mVisibleWidth = ((int) ACTION_DIMENSION_PX * actionItemsCount);
                        mOnVisibleWidthChangedListener.onItemsMenuVisibleWidthChanged(mVisibleWidth);
                    }
                }
            });
            animSet.start();
        }
    }

    /**
     * Shows all the menu items that were hidden by hideIfRoomItems(boolean withAnim)
     *
     * @param withAnim
     */
    public void showIfRoomItems(boolean withAnim) {

        if (mMenu == -1) {
            return;
        }

        cancelChildAnimListAndClear();

        if (mMenuItems.isEmpty()) {
            return;
        }

        anims = new ArrayList<>();

        for (int i = 0; i < getChildCount(); i++) {

            final View currentView = getChildAt(i);

            //reset all the action item views
            if (i < mActionItems.size()) {
                ImageView action = (ImageView) currentView;
                final MenuItem actionItem = mActionItems.get(i);
                action.setImageDrawable(actionItem.getIcon());
                Util.setIconColor(action, mActionIconColor);
                action.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {

                        if (mMenuCallback != null) {
                            mMenuCallback.onMenuItemSelected(mMenuBuilder, actionItem);
                        }
                    }
                });
            }

            Interpolator interpolator = new DecelerateInterpolator();
            if (i > (mActionShowAlwaysItems.size() - 1)) {
                interpolator = new LinearInterpolator();
            }

            currentView.setClickable(true);

            //simply animate all properties of all action item views back to their default/visible state
            anims.add(ViewPropertyObjectAnimator.animate(currentView)
                    .addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {

                            currentView.setTranslationX(0);
                        }
                    })
                    .setInterpolator(interpolator)
                    .translationX(0).get());
            anims.add(ViewPropertyObjectAnimator.animate(currentView)
                    .addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {

                            currentView.setScaleX(1.0f);
                        }
                    })
                    .setInterpolator(interpolator)
                    .scaleX(1.0f).get());
            anims.add(ViewPropertyObjectAnimator.animate(currentView)
                    .addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {

                            currentView.setScaleY(1.0f);
                        }
                    })
                    .setInterpolator(interpolator)
                    .scaleY(1.0f).get());
            anims.add(ViewPropertyObjectAnimator.animate(currentView)
                    .addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {

                            currentView.setAlpha(1.0f);
                        }
                    })
                    .setInterpolator(interpolator)
                    .alpha(1.0f).get());
        }

        if(anims.isEmpty()){
            return;
        }

        AnimatorSet animSet = new AnimatorSet();
        if (!withAnim) {
            //temporary, from laziness
            animSet.setDuration(0);
        }
        animSet.playTogether(anims.toArray(new ObjectAnimator[anims.size()]));
        animSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {

                if (mOnVisibleWidthChangedListener != null) {
                    mVisibleWidth = (getChildCount() * (int) ACTION_DIMENSION_PX) - (mHasOverflow ? Util.dpToPx(8) : 0);
                    mOnVisibleWidthChangedListener.onItemsMenuVisibleWidthChanged(mVisibleWidth);
                }
            }
        });
        animSet.start();
    }

    private interface MenuItemImplPredicate {

        boolean apply(MenuItemImpl menuItem);
    }

    private List<MenuItemImpl> filter(List<MenuItemImpl> target, MenuItemImplPredicate predicate) {
        List<MenuItemImpl> result = new ArrayList<>();
        for (MenuItemImpl element : target) {
            if (predicate.apply(element)) {
                result.add(element);
            }
        }
        return result;
    }

    private MenuInflater getMenuInflater() {
        if (mMenuInflater == null) {
            mMenuInflater = new SupportMenuInflater(getContext());
        }
        return mMenuInflater;
    }

    public void setOnVisibleWidthChanged(OnVisibleWidthChangedListener listener) {
        this.mOnVisibleWidthChangedListener = listener;
    }

    private void cancelChildAnimListAndClear() {
        for (ObjectAnimator animator : anims) {
            animator.cancel();
        }
        anims.clear();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        //clear anims if any to avoid leak
        cancelChildAnimListAndClear();
    }
}


package com.arlib.floatingsearchview.util;

/**
 * Copyright (C) 2015 Ari C.
 *

 * 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.
 */

import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Display;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageView;

public class Util {

    private static final String TAG = "Util";

    public static void showSoftKeyboard(final Context context, final EditText editText) {

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {

                InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
                inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_FORCED);
            }
        }, 100);
    }

    public static void closeSoftKeyboard(Activity activity) {

        View currentFocusView = activity.getCurrentFocus();
        if (currentFocusView != null) {
            InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(currentFocusView.getWindowToken(), 0);
        }
    }

    public static int dpToPx(int dp) {
        DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
        return (int) (dp * metrics.density);
    }

    public static int pxToDp(int px) {
        DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
        return (int) (px / metrics.density);
    }

    public static int spToPx(int sp) {
        DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, metrics);
    }

    public static int pxToSp(int px) {
        DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
        return px / (int) metrics.scaledDensity;
    }

    public static int getScreenWidth(Activity activity) {

        Display display = activity.getWindowManager().getDefaultDisplay();
        DisplayMetrics outMetrics = new DisplayMetrics();
        display.getMetrics(outMetrics);

        return outMetrics.widthPixels;
    }

    public static int getScreenHeight(Activity activity) {

        Display display = activity.getWindowManager().getDefaultDisplay();
        DisplayMetrics outMetrics = new DisplayMetrics();
        display.getMetrics(outMetrics);

        return outMetrics.heightPixels;
    }

    public static void setIconColor(ImageView iconHolder, int color) {
        Drawable wrappedDrawable = DrawableCompat.wrap(iconHolder.getDrawable());
        DrawableCompat.setTint(wrappedDrawable, color);
        iconHolder.setImageDrawable(wrappedDrawable);
        iconHolder.invalidate();
    }

    /**
     * Gets a reference to a given drawable and prepares it for use with tinting through.
     *
     * @param resId the resource id for the given drawable
     * @return a wrapped drawable ready fo use
     * with {@link android.support.v4.graphics.drawable.DrawableCompat}'s tinting methods
     * @throws Resources.NotFoundException
     */
    public static Drawable getWrappedDrawable(Context context, @DrawableRes int resId) throws Resources.NotFoundException {
        return DrawableCompat.wrap(ResourcesCompat.getDrawable(context.getResources(),
                resId, null));
    }

    public static int getColor(Context context, @ColorRes int resId) throws Resources.NotFoundException {
        return ContextCompat.getColor(context, resId);
    }

    public static void removeGlobalLayoutObserver(View view, ViewTreeObserver.OnGlobalLayoutListener layoutListener) {
        if (Build.VERSION.SDK_INT < 16) {
            view.getViewTreeObserver().removeGlobalOnLayoutListener(layoutListener);
        } else {
            view.getViewTreeObserver().removeOnGlobalLayoutListener(layoutListener);
        }
    }

    public static Activity getHostActivity(Context context) {
        while (context instanceof ContextWrapper) {
            if (context instanceof Activity) {
                return (Activity) context;
            }
            context = ((ContextWrapper) context).getBaseContext();
        }
        return null;
    }
}