/**
 *
 * The MIT License
 *
 * Copyright (c) 2011 the original author or authors.
 *
 * 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 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.
 */

package com.googlecode.charts4j.parameters;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.List;
import java.util.Locale;

import com.googlecode.charts4j.Data;
import com.googlecode.charts4j.DataEncoding;
import com.googlecode.charts4j.collect.Lists;

/**
 * Class for building data parameter string for the Google Chart API.
 *
 * @author Julien Chastang (julien.c.chastang at gmail dot com)
 */
final class DataParameter extends AbstractParameter {

    /** The Google Chart API data parameter. */
    private static final String   URL_PARAMETER_KEY       = "chd";

    /** The decimal formatter. */
    private final DecimalFormat   decimalFormatter        = new DecimalFormat("##0.0", new DecimalFormatSymbols(Locale.US));


    /** The Constant EXTENDED_ENCODING_CHARS. */
    private static final char[]   EXTENDED_ENCODING_CHARS = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
            'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.'};

    /** The Constant SIMPLE_ENCODING_CHARS. */
    private static final char[]   SIMPLE_ENCODING_CHARS   = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
            'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};

    /** The Constant EXTENDED_ENCODING. */
    private static final String[] EXTENDED_ENCODING       = new String[4096];

    /** The Constant SIMPLE_ENCODING. */
    private static final String[] SIMPLE_ENCODING         = new String[62];

    static { // Fill the encoding arrays in static block.
        int cnt = 0;
        for (int i = 0; i < EXTENDED_ENCODING_CHARS.length; i++) {
            for (int j = 0; j < EXTENDED_ENCODING_CHARS.length; j++) {
                EXTENDED_ENCODING[cnt++] = EXTENDED_ENCODING_CHARS[i] + "" + EXTENDED_ENCODING_CHARS[j];
            }
        }
        cnt = 0;
        for (int i = 0; i < SIMPLE_ENCODING_CHARS.length; i++) {
            SIMPLE_ENCODING[cnt++] = SIMPLE_ENCODING_CHARS[i] + "";
        }
    }

    /** The datas. */
    private final List<Data>      datas                   = Lists.newLinkedList();

    /** The data encoding. */
    private DataEncoding          dataEncoding            = DataEncoding.EXTENDED;

    /**
     * Instantiates a new data parameter.
     */
    DataParameter() {
        super();
    }

    /**
     * Adds the data.
     *
     * @param data
     *            the data
     */
    void addData(final Data data) {
        datas.add(data);
    }

    /**
     * Gets the data encoding.
     *
     * @return the data encoding
     */
    DataEncoding getDataEncoding() {
        return dataEncoding;
    }

    /**
     * Sets the data encoding.
     *
     * @param dataEncoding
     *            the new data encoding
     */
    void setDataEncoding(final DataEncoding dataEncoding) {
        this.dataEncoding = dataEncoding;
    }

    /**
     * Extended encoding.
     *
     * @param data
     *            the data
     *
     * @return the string
     */
    private String extendedEncoding(final double[] data) {

        if (data == null || data.length == 0) {
            return "";
        }

        final StringBuilder encodedData = new StringBuilder();
        for (double datum : data) { // The following cast to int is safe.
            final int index = (int) ((datum >= Data.MIN_VALUE && datum <= Data.MAX_VALUE) ? Math.round((datum / Data.MAX_VALUE) * (EXTENDED_ENCODING.length - 1)) : -1);
            encodedData.append((index < 0 || index > (EXTENDED_ENCODING.length - 1)) ? "__" : EXTENDED_ENCODING[index]);
        }
        return encodedData.toString();
    }

    /**
     * Simple encoding.
     *
     * @param data
     *            the data
     *
     * @return the string
     */
    private String simpleEncoding(final double[] data) {

        if (data == null || data.length == 0) {
            return "";
        }

        final StringBuilder encodedData = new StringBuilder();
        for (double datum : data) { // The following cast to int is safe.
            int index = (int) ((datum >= Data.MIN_VALUE && datum <= Data.MAX_VALUE) ? Math.round((datum / Data.MAX_VALUE) * (SIMPLE_ENCODING.length - 1)) : -1);
            encodedData.append((index < 0 || index > (SIMPLE_ENCODING.length - 1)) ? "_" : SIMPLE_ENCODING[index]);
        }
        return encodedData.toString();
    }

    /**
     * Text encoding.
     *
     * @param data
     *            the data
     *
     * @return the string
     */
    private String textEncoding(final double[] data) {
        if (data == null || data.length == 0) {
            return "";
        }

        final StringBuilder encodedData = new StringBuilder();
        int count = 0;
        for (double datum : data) {
            encodedData.append(count++ > 0 ? "," : "").append(datum >= Data.MIN_VALUE && datum <= Data.MAX_VALUE ? decimalFormatter.format(datum) : "-1");
        }
        return encodedData.toString();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getKey() {
        return URL_PARAMETER_KEY;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getValue() {
        final StringBuilder sb = new StringBuilder(dataEncoding + "");
        final String seperator = dataEncoding.equals(DataEncoding.TEXT) ? "|" : ",";
        int cnt = 0;
        for (Data data : datas) {
            final String dataString = dataEncoding.equals(DataEncoding.SIMPLE) ? simpleEncoding(data.getData()) : dataEncoding.equals(DataEncoding.TEXT) ? textEncoding(data.getData()) : extendedEncoding(data.getData());
            sb.append(cnt++ > 0 ? seperator : "").append(dataString);
        }
        return sb.toString();
    }
}
