/* ---------------------------------------------------------------------
 * Numenta Platform for Intelligent Computing (NuPIC)
 * Copyright (C) 2014, Numenta, Inc.  Unless you have an agreement
 * with Numenta, Inc., for a separate license for this software code, the
 * following terms and conditions apply:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero Public License for more details.
 *
 * You should have received a copy of the GNU Affero Public License
 * along with this program.  If not, see http://www.gnu.org/licenses.
 *
 * http://numenta.org/licenses/
 * ---------------------------------------------------------------------
 */
package org.numenta.nupic.encoders;

import gnu.trove.list.array.TDoubleArrayList;

import java.util.*;

import org.numenta.nupic.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.lang.String.*;

/**
 * Pass an encoded SDR straight to the model.
 * Each encoding is an SDR in which w out of n bits are turned on.
 *
 * @author wilsondy (from Python original)
 */
public class PassThroughEncoder<T> extends Encoder<T> {

    private static final long serialVersionUID = 1L;
    
    protected final Logger LOGGER = LoggerFactory.getLogger(PassThroughEncoder.class);

    protected PassThroughEncoder() { }

    public PassThroughEncoder(int outputWidth, int outputBitsOnCount) {
        super.setW(outputBitsOnCount);
        super.setN(outputWidth);
        super.setForced(false);
        
        LOGGER.info("Building new PassThroughEncoder instance, outputWidth: {} outputBitsOnCount: {}", outputWidth, outputBitsOnCount);
    }

    /**
     * Returns a builder for building PassThroughEncoders.
     * This builder may be reused to produce multiple builders
     *
     * @return a {@code PassThroughEncoder.Builder}
     */
    public static Encoder.Builder<PassThroughEncoder.Builder, PassThroughEncoder<int[]>> builder() {
        return new PassThroughEncoder.Builder();
    }

    public void init() {
        setForced(false);
    }

    @Override
    /**
     * Does a bitwise compare of the two bitmaps and returns a fractional
     * value between 0 and 1 of how similar they are.
     * 1 => identical
     * 0 => no overlapping bits
     * IGNORES difference in length (only compares bits of shorter list)  e..g 11 and 1100101010 are "identical"
     * @see org.numenta.nupic.encoders.Encoder#closenessScores(gnu.trove.list.TDoubleList, gnu.trove.list.TDoubleList, boolean)
     */
    public gnu.trove.list.TDoubleList closenessScores(gnu.trove.list.TDoubleList expValues, gnu.trove.list.TDoubleList actValues, boolean fractional) {
        TDoubleArrayList result = new TDoubleArrayList();

        double ratio = 1.0d;
        double expectedSum = expValues.sum();
        double actualSum = actValues.sum();

        if (actualSum > expectedSum) {
            double diff = actualSum - expectedSum;
            if (diff < expectedSum)
                ratio = 1 - diff / expectedSum;
            else
                ratio = 1 / diff;
        }

        int[] expectedInts = ArrayUtils.toIntArray(expValues.toArray());
        int[] actualInts = ArrayUtils.toIntArray(actValues.toArray());

        int[] overlap = ArrayUtils.and(expectedInts, actualInts);

        int overlapSum = ArrayUtils.sum(overlap);
        double r = 0.0;
        if (expectedSum != 0)
            r = overlapSum / expectedSum;
        r = r * ratio;

        if(LOGGER.isTraceEnabled()) {
            LOGGER.trace("closenessScores for expValues: {} and actValues: {} is: {}", Arrays.toString(expectedInts), actualInts, r);
        }

        result.add(r);
        return result;
    }

    @Override
    public int getWidth() {
        return n;
    }

    @Override
    public boolean isDelta() {
        return false;
    }

    /**
     * Check for length the same and copy input into output
     * If outputBitsOnCount (w) set, throw error if not true
     *
     * @param input
     * @param output
     */
    @Override
    public void encodeIntoArray(T t, int[] output) {
        int[] input = (int[])t;
        if (input.length != output.length)
            throw new IllegalArgumentException(format("Different input (%d) and output (%d) sizes", input.length, output.length));
        if (ArrayUtils.sum(input) != w)
            throw new IllegalArgumentException(format("Input has %d bits but w was set to %d.", ArrayUtils.sum(input), w));

        System.arraycopy(input, 0, output, 0, input.length);
        if(LOGGER.isTraceEnabled()) {
            LOGGER.trace("encodeIntoArray: Input: {} \nOutput: {} ", Arrays.toString(input), Arrays.toString(output));
        }
    }

    /**
     * Not much real work to do here as this concept doesn't really apply.
     */
    @Override
    public Tuple decode(int[] encoded, String parentFieldName) {
        //TODO: these methods should be properly implemented (this comment in Python)
        String fieldName = this.name;
        if (parentFieldName != null && parentFieldName.length() > 0 && LOGGER.isTraceEnabled())
            LOGGER.trace("Decoding Field: {}.{}", parentFieldName, this.name);

        List<MinMax> ranges = new ArrayList<MinMax>();
        ranges.add(new MinMax(0, 0));
        RangeList inner = new RangeList(ranges, "input");
        Map<String, RangeList> fieldsDict = new HashMap<String, RangeList>();
        fieldsDict.put(fieldName, inner);

        return new DecodeResult(fieldsDict, Arrays.asList(fieldName));
    }

    @Override
    public void setLearning(boolean learningEnabled) {
        //NOOP
    }

    @SuppressWarnings("hiding")
    @Override
    public <T> List<T> getBucketValues(Class<T> returnType) {
        return null;
    }

    /**
     * Returns a {@link Encoder.Builder} for constructing {@link PassThroughEncoder}s
     * <p/>
     * The base class architecture is put together in such a way where boilerplate
     * initialization can be kept to a minimum for implementing subclasses, while avoiding
     * the mistake-proneness of extremely long argument lists.
     */
    public static class Builder extends Encoder.Builder<PassThroughEncoder.Builder, PassThroughEncoder<int[]>> {
        private Builder() { }

        @SuppressWarnings("unchecked")
        @Override
        public PassThroughEncoder<int[]> build() {
            //Must be instantiated so that super class can initialize
            //boilerplate variables.
            encoder = new PassThroughEncoder<int[]>();

            //Call super class here
            super.build();

            ////////////////////////////////////////////////////////
            //  Implementing classes would do setting of specific //
            //  vars here together with any sanity checking       //
            ////////////////////////////////////////////////////////

            ((PassThroughEncoder<int[]>) encoder).init();

            return (PassThroughEncoder<int[]>) encoder;
        }
    }
}