/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde 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.olap4j.mdx;

import org.olap4j.impl.*;
import org.olap4j.type.Type;

import java.util.*;

/**
 * Multi-part identifier.
 *
 * <p>An identifier is immutable.
 *
 * <p>An identifer consists of one or more {@link IdentifierSegment}s. A segment
 * is either:<ul>
 * <li>An unquoted value such as '{@code CA}',
 * <li>A value quoted in brackets, such as '{@code [San Francisco]}', or
 * <li>A key of one or more parts, each of which is prefixed with '&amp;',
 *     such as '{@code &amp;[Key 1]&amp;Key2&amp;[5]}'.
 * </ul>
 *
 * <p>Segment types are indicated by the {@link Quoting} enumeration.
 *
 * <p>A key segment is of type {@link Quoting#KEY}, and has one or more
 * component parts accessed via the
 * {@link IdentifierSegment#getKeyParts()} method. The parts
 * are of type {@link Quoting#UNQUOTED} or {@link Quoting#QUOTED}.
 *
 * <p>A simple example is the identifier {@code Measures.[Unit Sales]}. It
 * has two segments:<ul>
 * <li>Segment #0 is
 *     {@link Quoting#UNQUOTED UNQUOTED},
 *     name "Measures"</li>
 * <li>Segment #1 is
 *     {@link Quoting#QUOTED QUOTED},
 *     name "Unit Sales"</li>
 * </ul>
 *
 * <p>A more complex example illustrates a compound key. The identifier {@code
 * [Customers].[City].&amp;[San Francisco]&amp;CA&amp;USA.&amp;[cust1234]}
 * contains four segments as follows:
 * <ul>
 * <li>Segment #0 is QUOTED, name "Customers"</li>
 * <li>Segment #1 is QUOTED, name "City"</li>
 * <li>Segment #2 is a {@link Quoting#KEY KEY}.
 *     It has 3 sub-segments:
 *     <ul>
 *     <li>Sub-segment #0 is QUOTED, name "San Francisco"</li>
 *     <li>Sub-segment #1 is UNQUOTED, name "CA"</li>
 *     <li>Sub-segment #2 is UNQUOTED, name "USA"</li>
 *     </ul>
 * </li>
 * <li>Segment #3 is a KEY. It has 1 sub-segment:
 *     <ul>
 *     <li>Sub-segment #0 is QUOTED, name "cust1234"</li>
 *     </ul>
 * </li>
 * </ul>
 *
 * @author jhyde
 */
public class IdentifierNode
    implements ParseTreeNode
{
    private final List<IdentifierSegment> segments;

    /**
     * Creates an identifier containing one or more segments.
     *
     * @param segments Array of Segments, each consisting of a name and quoting
     * style
     */
    public IdentifierNode(IdentifierSegment... segments) {
        if (segments.length < 1) {
            throw new IllegalArgumentException();
        }
        this.segments = UnmodifiableArrayList.asCopyOf(segments);
    }

    /**
     * Creates an identifier containing a list of segments.
     *
     * @param segments List of segments
     */
    public IdentifierNode(List<IdentifierSegment> segments) {
        if (segments.size() < 1) {
            throw new IllegalArgumentException();
        }
        this.segments =
            new UnmodifiableArrayList<IdentifierSegment>(
                segments.toArray(
                    new IdentifierSegment[segments.size()]));
    }

    public Type getType() {
        // Can't give the type until we have resolved.
        throw new UnsupportedOperationException();
    }

    /**
     * Returns the list of segments which consistitute this identifier.
     *
     * @return list of constituent segments
     */
    public List<IdentifierSegment> getSegmentList() {
        return segments;
    }

    public ParseRegion getRegion() {
        // Region is the span from the first segment to the last.
        return sumSegmentRegions(segments);
    }

    /**
     * Returns a region encompassing the regions of the first through the last
     * of a list of segments.
     *
     * @param segments List of segments
     * @return Region encompassed by list of segments
     */
    static ParseRegion sumSegmentRegions(
        final List<? extends IdentifierSegment> segments)
    {
        return ParseRegion.sum(
            new AbstractList<ParseRegion>() {
                public ParseRegion get(int index) {
                    return segments.get(index).getRegion();
                }

                public int size() {
                    return segments.size();
                }
            });
    }

    /**
     * Returns a new Identifier consisting of this one with another segment
     * appended. Does not modify this Identifier.
     *
     * @param segment Name of segment
     * @return New identifier
     */
    public IdentifierNode append(IdentifierSegment segment) {
        List<IdentifierSegment> newSegments =
            new ArrayList<IdentifierSegment>(segments);
        newSegments.add(segment);
        return new IdentifierNode(newSegments);
    }

    public <T> T accept(ParseTreeVisitor<T> visitor) {
        return visitor.visit(this);
    }

    public void unparse(ParseTreeWriter writer) {
        writer.getPrintWriter().print(this);
    }

    public String toString() {
        return unparseIdentifierList(segments);
    }

    public IdentifierNode deepCopy() {
        // IdentifierNode is immutable
        return this;
    }

    /**
     * Parses an MDX identifier string into an
     * {@link org.olap4j.mdx.IdentifierNode}.
     *
     * <p>It contains a list of {@link IdentifierSegment segments}, each
     * of which is a name combined with a description of how the name
     * was {@link Quoting quoted}. For example,
     *
     * <blockquote><code>
     * parseIdentifier(
     * "[Customers].USA.[South Dakota].[Sioux Falls].&amp;[1245]")
     * </code></blockquote>
     *
     * returns an IdentifierNode consisting of the following segments:
     *
     * <code><ul>
     * <li>NameSegment("Customers", quoted=true),
     * <li>NameSegment("USA", quoted=false),
     * <li>NameSegment("South Dakota", quoted=true),
     * <li>NameSegment("Sioux Falls", quoted=true),
     * <li>KeySegment( { NameSegment("1245", quoted=true) } )
     * </ul></code>
     *
     * @see #ofNames(String...)
     *
     * @param identifier MDX identifier string
     *
     * @return Identifier parse tree node
     *
     * @throws IllegalArgumentException if the format of the identifier is
     * invalid
     */
    public static IdentifierNode parseIdentifier(String identifier)  {
        return new IdentifierNode(IdentifierParser.parseIdentifier(identifier));
    }

    /**
     * Converts an array of quoted name segments into an identifier.
     *
     * <p>For example,
     *
     * <blockquote><code>
     * IdentifierNode.ofNames("Store", "USA", "CA")</code></blockquote>
     *
     * returns an IdentifierNode consisting of the following segments:
     *
     * <code><ul>
     * <li>NameSegment("Customers", quoted=true),
     * <li>NameSegment("USA", quoted=false),
     * <li>NameSegment("South Dakota", quoted=true),
     * <li>NameSegment("Sioux Falls", quoted=true),
     * <li>KeySegment( { NameSegment("1245", quoted=true) } )
     * </ul></code>
     *
     * @see #parseIdentifier(String)
     *
     * @param names Array of names
     *
     * @return Identifier parse tree node
     */
    public static IdentifierNode ofNames(String... names) {
        final List<IdentifierSegment> list =
            new ArrayList<IdentifierSegment>();
        for (String name : names) {
            list.add(new NameSegment(null, name, Quoting.QUOTED));
        }
        return new IdentifierNode(list);
    }

    /**
     * Returns string quoted in [...].
     *
     * <p>For example, "San Francisco" becomes
     * "[San Francisco]"; "a [bracketed] string" becomes
     * "[a [bracketed]] string]".
     *
     * @param id Unquoted name
     * @return Quoted name
     */
    static String quoteMdxIdentifier(String id) {
        StringBuilder buf = new StringBuilder(id.length() + 20);
        quoteMdxIdentifier(id, buf);
        return buf.toString();
    }

    /**
     * Returns a string quoted in [...], writing the results to a
     * {@link StringBuilder}.
     *
     * @param id Unquoted name
     * @param buf Builder to write quoted string to
     */
    static void quoteMdxIdentifier(String id, StringBuilder buf) {
        buf.append('[');
        int start = buf.length();
        buf.append(id);
        Olap4jUtil.replace(buf, start, "]", "]]");
        buf.append(']');
    }

    /**
     * Converts a sequence of identifiers to a string.
     *
     * <p>For example, {"Store", "USA",
     * "California"} becomes "[Store].[USA].[California]".
     *
     * @param segments List of segments
     * @return Segments as quoted string
     */
    static String unparseIdentifierList(
        List<? extends IdentifierSegment> segments)
    {
        final StringBuilder buf = new StringBuilder(64);
        for (int i = 0; i < segments.size(); i++) {
            IdentifierSegment segment = segments.get(i);
            if (i > 0) {
                buf.append('.');
            }
            segment.toString(buf);
        }
        return buf.toString();
    }
}

// End IdentifierNode.java
