///////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com>                   //
//                                                                                   //
// This program is free software; you can redistribute it and/or modify              //
// it under the terms of the GNU General Public License as published by              //
// the Free Software Foundation as version 3 of the License, or                      //
// (at your option) any later version.                                               //
//                                                                                   //
// 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 General Public License V3 for more details.                                   //
//                                                                                   //
// You should have received a copy of the GNU General Public License                 //
// along with this program. If not, see <http://www.gnu.org/licenses/>.              //
///////////////////////////////////////////////////////////////////////////////////////
/*
SIMD-ified LDPC algorithms

Copyright 2018 Ahmet Inan <xdsopl@gmail.com>
*/

#ifndef ALGORITHMS_HH
#define ALGORITHMS_HH

#include <vector>

#include "generic.h"
#include "exclusive_reduce.h"

namespace ldpctool {

template <typename VALUE, int WIDTH>
struct SelfCorrectedUpdate<SIMD<VALUE, WIDTH>>
{
	typedef SIMD<VALUE, WIDTH> TYPE;
	static void update(TYPE *a, TYPE b)
	{
		*a = vreinterpret<TYPE>(vand(vmask(b), vorr(vceqz(*a), veor(vcgtz(*a), vcltz(b)))));
	}
};

template <typename VALUE, int WIDTH, typename UPDATE>
struct MinSumAlgorithm<SIMD<VALUE, WIDTH>, UPDATE>
{
	typedef SIMD<VALUE, WIDTH> TYPE;
	static TYPE zero()
	{
		return vzero<TYPE>();
	}
	static TYPE one()
	{
		return vdup<TYPE>(1);
	}
	static TYPE min(TYPE a, TYPE b)
	{
		return vmin(a, b);
	}
	static TYPE sign(TYPE a, TYPE b)
	{
		return vsign(a, b);
	}
	static void finalp(TYPE *links, int cnt)
	{
		std::vector<TYPE> mags(cnt);
		std::vector<TYPE> mins(cnt);
		for (int i = 0; i < cnt; ++i)
			mags[i] = vabs(links[i]);
		CODE::exclusive_reduce(mags, mins, cnt, min);

		std::vector<TYPE> signs(cnt);
		CODE::exclusive_reduce(links, signs, cnt, sign);

		for (int i = 0; i < cnt; ++i)
			links[i] = sign(mins[i], signs[i]);
	}
	static TYPE add(TYPE a, TYPE b)
	{
		return vadd(a, b);
	}
	static TYPE sub(TYPE a, TYPE b)
	{
		return vsub(a, b);
	}
	static bool bad(TYPE v, int blocks)
	{
		auto tmp = vcgtz(v);
		for (int i = 0; i < blocks; ++i)
			if (!tmp.v[i])
				return true;
		return false;
	}
	static void update(TYPE *a, TYPE b)
	{
		UPDATE::update(a, b);
	}
};

template <int WIDTH, typename UPDATE>
struct MinSumAlgorithm<SIMD<int8_t, WIDTH>, UPDATE>
{
	typedef int8_t VALUE;
	typedef SIMD<VALUE, WIDTH> TYPE;
	static TYPE zero()
	{
		return vzero<TYPE>();
	}
	static TYPE one()
	{
		return vdup<TYPE>(1);
	}
	static TYPE sign(TYPE a, TYPE b)
	{
		return vsign(a, b);
	}
	static TYPE eor(TYPE a, TYPE b)
	{
		return vreinterpret<TYPE>(veor(vmask(a), vmask(b)));
	}
	static TYPE orr(TYPE a, TYPE b)
	{
		return vreinterpret<TYPE>(vorr(vmask(a), vmask(b)));
	}
	static TYPE other(TYPE a, TYPE b, TYPE c)
	{
		return vreinterpret<TYPE>(vbsl(vceq(a, b), vmask(c), vmask(b)));
	}
	static void finalp(TYPE *links, int cnt)
	{
		std::vector<TYPE> mags(cnt);
		for (int i = 0; i < cnt; ++i)
			mags[i] = vqabs(links[i]);

		TYPE mins[2];
		mins[0] = vmin(mags[0], mags[1]);
		mins[1] = vmax(mags[0], mags[1]);
		for (int i = 2; i < cnt; ++i) {
			mins[1] = vmin(mins[1], vmax(mins[0], mags[i]));
			mins[0] = vmin(mins[0], mags[i]);
		}

		TYPE signs = links[0];
		for (int i = 1; i < cnt; ++i)
			signs = eor(signs, links[i]);

		for (int i = 0; i < cnt; ++i)
			links[i] = sign(other(mags[i], mins[0], mins[1]), orr(eor(signs, links[i]), vdup<TYPE>(127)));
	}
	static TYPE add(TYPE a, TYPE b)
	{
		return vqadd(a, b);
	}
	static TYPE sub(TYPE a, TYPE b)
	{
		return vqsub(a, b);
	}
	static bool bad(TYPE v, int blocks)
	{
		auto tmp = vcgtz(v);
		for (int i = 0; i < blocks; ++i)
			if (!tmp.v[i])
				return true;
		return false;
	}
	static void update(TYPE *a, TYPE b)
	{
		UPDATE::update(a, vmin(vmax(b, vdup<TYPE>(-32)), vdup<TYPE>(31)));
	}
};

template <typename VALUE, int WIDTH, typename UPDATE, int FACTOR>
struct OffsetMinSumAlgorithm<SIMD<VALUE, WIDTH>, UPDATE, FACTOR>
{
	typedef SIMD<VALUE, WIDTH> TYPE;
	static TYPE zero()
	{
		return vzero<TYPE>();
	}
	static TYPE one()
	{
		return vdup<TYPE>(1);
	}
	static TYPE min(TYPE a, TYPE b)
	{
		return vmin(a, b);
	}
	static TYPE sign(TYPE a, TYPE b)
	{
		return vsign(a, b);
	}
	static void finalp(TYPE *links, int cnt)
	{
		TYPE beta = vdup<TYPE>(0.5 * FACTOR);
		std::vector<TYPE> mags(cnt);
		std::vector<TYPE> mins(cnt);
		for (int i = 0; i < cnt; ++i)
			mags[i] = vmax(vsub(vabs(links[i]), beta), vzero<TYPE>());
		CODE::exclusive_reduce(mags, mins, cnt, min);

		std::vector<TYPE> signs(cnt);
		CODE::exclusive_reduce(links, signs, cnt, sign);

		for (int i = 0; i < cnt; ++i)
			links[i] = sign(mins[i], signs[i]);
	}
	static TYPE add(TYPE a, TYPE b)
	{
		return vadd(a, b);
	}
	static TYPE sub(TYPE a, TYPE b)
	{
		return vsub(a, b);
	}
	static bool bad(TYPE v, int blocks)
	{
		auto tmp = vcgtz(v);
		for (int i = 0; i < blocks; ++i)
			if (!tmp.v[i])
				return true;
		return false;
	}
	static void update(TYPE *a, TYPE b)
	{
		UPDATE::update(a, b);
	}
};

template <int WIDTH, typename UPDATE, int FACTOR>
struct OffsetMinSumAlgorithm<SIMD<int8_t, WIDTH>, UPDATE, FACTOR>
{
	typedef int8_t VALUE;
	typedef SIMD<VALUE, WIDTH> TYPE;
	static TYPE zero()
	{
		return vzero<TYPE>();
	}
	static TYPE one()
	{
		return vdup<TYPE>(1);
	}
	static TYPE sign(TYPE a, TYPE b)
	{
		return vsign(a, b);
	}
	static TYPE eor(TYPE a, TYPE b)
	{
		return vreinterpret<TYPE>(veor(vmask(a), vmask(b)));
	}
	static TYPE orr(TYPE a, TYPE b)
	{
		return vreinterpret<TYPE>(vorr(vmask(a), vmask(b)));
	}
	static TYPE other(TYPE a, TYPE b, TYPE c)
	{
		return vreinterpret<TYPE>(vbsl(vceq(a, b), vmask(c), vmask(b)));
	}
	static void finalp(TYPE *links, int cnt)
	{
		auto beta = vunsigned(vdup<TYPE>(std::nearbyint(0.5 * FACTOR)));
		std::vector<TYPE> mags(cnt);
		for (int i = 0; i < cnt; ++i)
			mags[i] = vsigned(vqsub(vunsigned(vqabs(links[i])), beta));

		TYPE mins[2];
		mins[0] = vmin(mags[0], mags[1]);
		mins[1] = vmax(mags[0], mags[1]);
		for (int i = 2; i < cnt; ++i) {
			mins[1] = vmin(mins[1], vmax(mins[0], mags[i]));
			mins[0] = vmin(mins[0], mags[i]);
		}

		TYPE signs = links[0];
		for (int i = 1; i < cnt; ++i)
			signs = eor(signs, links[i]);

		for (int i = 0; i < cnt; ++i)
			links[i] = sign(other(mags[i], mins[0], mins[1]), orr(eor(signs, links[i]), vdup<TYPE>(127)));
	}
	static TYPE add(TYPE a, TYPE b)
	{
		return vqadd(a, b);
	}
	static TYPE sub(TYPE a, TYPE b)
	{
		return vqsub(a, b);
	}
	static bool bad(TYPE v, int blocks)
	{
		auto tmp = vcgtz(v);
		for (int i = 0; i < blocks; ++i)
			if (!tmp.v[i])
				return true;
		return false;
	}
	static void update(TYPE *a, TYPE b)
	{
		UPDATE::update(a, vmin(vmax(b, vdup<TYPE>(-32)), vdup<TYPE>(31)));
	}
};


template <typename VALUE, int WIDTH, typename UPDATE, int FACTOR>
struct MinSumCAlgorithm<SIMD<VALUE, WIDTH>, UPDATE, FACTOR>
{
	typedef SIMD<VALUE, WIDTH> TYPE;
	static TYPE zero()
	{
		return vzero<TYPE>();
	}
	static TYPE one()
	{
		return vdup<TYPE>(1);
	}
	static TYPE sign(TYPE a, TYPE b)
	{
		return vsign(a, b);
	}
	static TYPE correction_factor(TYPE a, TYPE b)
	{
		TYPE apb = vabs(vadd(a, b));
		TYPE apb2 = vadd(apb, apb);
		TYPE amb = vabs(vsub(a, b));
		TYPE amb2 = vadd(amb, amb);
		TYPE factor2 = vdup<TYPE>(FACTOR * 2);
		auto pc = vmask(vdup<TYPE>(VALUE(FACTOR) / VALUE(2)));
		auto nc = vmask(vdup<TYPE>(-VALUE(FACTOR) / VALUE(2)));
		pc = vand(pc, vand(vcgt(factor2, apb), vcgt(amb, apb2)));
		nc = vand(nc, vand(vcgt(factor2, amb), vcgt(apb, amb2)));
		return vreinterpret<TYPE>(vorr(pc, nc));
	}
	static TYPE minc(TYPE a, TYPE b)
	{
		TYPE m = vmin(vabs(a), vabs(b));
		TYPE x = vsign(vsign(m, a), b);
		x = vadd(x, correction_factor(a, b));
		return x;
	}
	static void finalp(TYPE *links, int cnt)
	{
		std::vector<TYPE> tmp(cnt);
		CODE::exclusive_reduce(links, tmp, cnt, minc);
		for (int i = 0; i < cnt; ++i)
			links[i] = tmp[i];
	}
	static TYPE add(TYPE a, TYPE b)
	{
		return vadd(a, b);
	}
	static TYPE sub(TYPE a, TYPE b)
	{
		return vsub(a, b);
	}
	static bool bad(TYPE v, int blocks)
	{
		auto tmp = vcgtz(v);
		for (int i = 0; i < blocks; ++i)
			if (!tmp.v[i])
				return true;
		return false;
	}
	static void update(TYPE *a, TYPE b)
	{
		UPDATE::update(a, b);
	}
};

template <int WIDTH, typename UPDATE, int FACTOR>
struct MinSumCAlgorithm<SIMD<int8_t, WIDTH>, UPDATE, FACTOR>
{
	typedef int8_t VALUE;
	typedef SIMD<VALUE, WIDTH> TYPE;
	static TYPE zero()
	{
		return vzero<TYPE>();
	}
	static TYPE one()
	{
		return vdup<TYPE>(1);
	}
	static TYPE sign(TYPE a, TYPE b)
	{
		return vsign(a, b);
	}
	static TYPE correction_factor(TYPE a, TYPE b)
	{
		TYPE apb = vqabs(vqadd(a, b));
		TYPE apb2 = vqadd(apb, apb);
		TYPE amb = vqabs(vqsub(a, b));
		TYPE amb2 = vqadd(amb, amb);
		TYPE factor2 = vdup<TYPE>(FACTOR * 2);
		auto pc = vmask(vdup<TYPE>(VALUE(FACTOR) / VALUE(2)));
		auto nc = vmask(vdup<TYPE>(-VALUE(FACTOR) / VALUE(2)));
		pc = vand(pc, vand(vcgt(factor2, apb), vcgt(amb, apb2)));
		nc = vand(nc, vand(vcgt(factor2, amb), vcgt(apb, amb2)));
		return vreinterpret<TYPE>(vorr(pc, nc));
	}
	static TYPE minc(TYPE a, TYPE b)
	{
		TYPE m = vmin(vqabs(a), vqabs(b));
		TYPE x = vsign(vsign(m, a), b);
		x = vqadd(x, correction_factor(a, b));
		return x;
	}
	static void finalp(TYPE *links, int cnt)
	{
		TYPE *tmp = new TYPE[cnt];
		CODE::exclusive_reduce(links, tmp, cnt, minc);
		for (int i = 0; i < cnt; ++i)
			links[i] = tmp[i];
		delete[] tmp;
	}
	static TYPE add(TYPE a, TYPE b)
	{
		return vqadd(a, b);
	}
	static TYPE sub(TYPE a, TYPE b)
	{
		return vqsub(a, b);
	}
	static bool bad(TYPE v, int blocks)
	{
		auto tmp = vcgtz(v);
		for (int i = 0; i < blocks; ++i)
			if (!tmp.v[i])
				return true;
		return false;
	}
	static void update(TYPE *a, TYPE b)
	{
		UPDATE::update(a, vmin(vmax(b, vdup<TYPE>(-32)), vdup<TYPE>(31)));
	}
};

} // namespace ldpctool

#endif
