/**
 * Copyright (c) 2025 SoftCorporation LLC. All rights reserved.
 *
 * The Software License, Version 1.0
 *
 * SoftCorporation LLC. grants you ("Licensee") a non-exclusive, royalty free,
 * license to use, modify and redistribute this software in source and binary
 * code form, provided that the following conditions are met:
 *
 * 1. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        SoftCorporation LLC. (http://www.softcorporation.com)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 2. This software and SoftCorporation name must not be used to
 *    promote products derived from the software without prior
 *    written permission. For written permission, please contact
 *    info@softcorporation.com.
 *
 * This software is provided "AS IS," without a warranty of any kind.
 * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
 * IN NO EVENT SHALL THE SOFTCORPORATION BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION).
 *
 */
package com.softcorporation.util;

import java.lang.IllegalArgumentException;
import java.util.ArrayList;
import java.util.List;

/**
 * Vector utils
 * @author Vadim Permakoff
 *
 */
public class VecUtil
{
    /**
     * Scale vector
     * @param a array
     * @param scale
     * @return result
     */
    public static float[] scaleVector(final float[] a, final double scale)
    {
        final float s = (float) scale;
        final float[] result = new float[a.length];
        for (int i = 0; i < a.length; i++)
        {
            result[i] = a[i] * s;
        }
        return result;
    }
    
    /**
     * Add vectors
     * @param a
     * @param b
     * @return result
     * @throws IllegalArgumentException
     */
    public static float[] addVectors(final float[] a, final float[] b)
    {
        if (a.length != b.length ) {
            throw new IllegalArgumentException("Vectors must be the same length");
        }
        final float[] result = new float[a.length];
        for (int i = 0; i < a.length; i++)
        {
            result[i] = a[i] + b[i];
        }
        return result;
    }
    
    /**
     * Get Cosine similarity between vectors as [-1.0...1.0]
     * @param a
     * @param b
     * @return result
     * @throws IllegalArgumentException
     */
    public static float getCosineSimilarity(final float[] a, final float[] b)
    {
        if (a.length != b.length ) {
            throw new IllegalArgumentException("Vectors must be the same length");
        }
        float dotP = 0.0F;
        float a2 = 0.0F;
        float b2 = 0.0F;
        for (int i = 0; i < a.length; i++)
        {
            final float ai = a[i]; 
            final float bi = b[i]; 
            dotP += ai * bi;
            a2 += ai * ai;
            b2 += bi * bi;
        }
        float cs = (float) (dotP / Math.sqrt(a2 * b2));
        return cs;
    }
    
    /**
     * Get similarity between vectors as [-1.0...1.0]
     * @param a
     * @param b
     * @return
     * @throws IllegalArgumentException
     */
    public static float getSimilarity(final float[] a, final float[] b)
    {
        if (a.length != b.length ) {
            throw new IllegalArgumentException("Vectors must be the same length");
        }
        float dotP = 0.0F;
        float a2 = 0.0F;
        float b2 = 0.0F;
        for (int i = 0; i < a.length; i++)
        {
            final float ai = a[i]; 
            final float bi = b[i]; 
            dotP += ai * bi;
            a2 += ai * ai;
            b2 += bi * bi;
        }
        float cs = (float) (2.0 * dotP / (a2 + b2));
        return cs;
    }
    
    /**
     * Get similarity between vectors (prone to catastrophic cancellation!)
     * @param a
     * @param b
     * @return
     * @throws IllegalArgumentException
     */
    public static float getSimilarity2(final float[] a, final float[] b)
    {
        if (a.length != b.length ) {
            throw new IllegalArgumentException("Vectors must be the same length");
        }
        float diff = 0.0F;
        float a2 = 0.0F;
        float b2 = 0.0F;
        for (int i = 0; i < a.length; i++)
        {
            float d = a[i] - b[i];
            diff += d * d;
            a2 += a[i] * a[i];
            b2 += b[i] * b[i];
        }
        float cs = (float) (1 - diff / (a2 + b2));
        return cs;
    }
    

    /**
     * Sum arrays with weight for subArray
     * @param mainArray
     * @param subArray
     * @param weight
     * @return result
     */
    public static double[] sumArrays(final double[] mainArray, final double[] subArray, final double weight)
    {
    	double mainWeight = 1.0 - weight;
        final double[] result = new double[mainArray.length];
        for (int i = 0; i < mainArray.length; i++)
        {
            result[i] = mainArray[i] * mainWeight + subArray[i] * weight;
        }
        return result;
    }
    
    /**
     * Normalize array
     * @param array
     */
    public static void normalizeArray(final double[] array) {
        double norm = 0;
        for (double v : array) {
        	norm += v * v;
        }
        norm = Math.sqrt(norm);
        if (norm == 0) {
        	return;
        }
        for (int i = 0; i < array.length; i++) {
            array[i] /= norm;
        }
    }
    
    /**
     * Get byte list from array
     * @param array to convert
     * @param scale double
     * @return result
     */
    public static List<Byte> toByteList(final double[] array, final double scale) {
        List<Byte> result = new ArrayList<>(array.length);
        for (double a : array) {
        	long v = Math.round(a * scale);
        	if (v > 127) {
        		v = 127;
        	} else if (v < -128) {
        		v = -128;
        	}
            result.add((byte) v);
        }
        return result;
    }

}
