/*  BarcodeUpcBase.cpp
 *
 *  Copyright (C) 2013  Jim Evins <evins@snaught.com>
 *
 *  This file is part of glbarcode++.
 *
 *  glbarcode++ is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  glbarcode++ 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with glbarcode++.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "BarcodeUpcBase.h"

#include "Constants.h"

#include <cctype>
#include <string>
#include <algorithm>
#include <iostream>


using namespace glbarcode::Constants;


namespace
{
	/*
	 * Symbology
	 */
	const std::string symbols[10][2] = {
		/*          Odd     Even  */
		/*    Left: sBsB    sBsB  */
		/*   Right: BsBs    ----  */
		/*                        */
		/* 0 */  { "3211", "1123" },
		/* 1 */  { "2221", "1222" },
		/* 2 */  { "2122", "2212" },
		/* 3 */  { "1411", "1141" },
		/* 4 */  { "1132", "2311" },
		/* 5 */  { "1231", "1321" },
		/* 6 */  { "1114", "4111" },
		/* 7 */  { "1312", "2131" },
		/* 8 */  { "1213", "3121" },
		/* 9 */  { "3112", "2113" }
	};

	const std::string sSymbol = "111";   /* BsB */
	const std::string eSymbol = "111";   /* BsB */
	const std::string mSymbol = "11111"; /* sBsBs */


	/*
	 * Parity selection
	 */
	enum Parity { P_ODD, P_EVEN };

	const Parity parity[10][6] = {
		/*                Pos 1,  Pos 2,  Pos 3,  Pos 4,  Pos 5,  Pos 6 */
		/* 0 (UPC-A) */ { P_ODD,  P_ODD,  P_ODD,  P_ODD,  P_ODD,  P_ODD  },
		/* 1         */ { P_ODD,  P_ODD,  P_EVEN, P_ODD,  P_EVEN, P_EVEN },
		/* 2         */ { P_ODD,  P_ODD,  P_EVEN, P_EVEN, P_ODD,  P_EVEN },
		/* 3         */ { P_ODD,  P_ODD,  P_EVEN, P_EVEN, P_EVEN, P_ODD  },
		/* 4         */ { P_ODD,  P_EVEN, P_ODD,  P_ODD,  P_EVEN, P_EVEN },
		/* 5         */ { P_ODD,  P_EVEN, P_EVEN, P_ODD,  P_ODD,  P_EVEN },
		/* 6         */ { P_ODD,  P_EVEN, P_EVEN, P_EVEN, P_ODD,  P_ODD  },
		/* 7         */ { P_ODD,  P_EVEN, P_ODD,  P_EVEN, P_ODD,  P_EVEN },
		/* 8         */ { P_ODD,  P_EVEN, P_ODD,  P_EVEN, P_EVEN, P_ODD  },
		/* 9         */ { P_ODD,  P_EVEN, P_EVEN, P_ODD,  P_EVEN, P_ODD  }
	};


	/*
	 * Constants
	 */
	const int    QUIET_MODULES   = 9;

	const double BASE_MODULE_SIZE      = ( 0.01 *  PTS_PER_INCH );
	const double BASE_FONT_SIZE        = 7;
	const double BASE_TEXT_AREA_HEIGHT = 11;
}


namespace glbarcode
{

	/*
	 * UPC data validation, implements Barcode1dBase::validate()
	 */
	bool BarcodeUpcBase::validate( const std::string& rawData )
	{
		int nDigits = 0;

		for (char c : rawData)
		{
			if ( isdigit( c ) )
			{
				nDigits++;
			}
			else if ( c != ' ')
			{
				/* Only allow digits and spaces -- ignoring spaces. */
				return false;
			}
		}

		/* validate nDigits (call implementation from concrete class) */
		return validateDigits( nDigits );
	}


	/*
	 * UPC data encoding, implements Barcode1dBase::encode()
	 */
	std::string BarcodeUpcBase::encode( const std::string& cookedData )
	{
		int sumOdd  = 0;
		int sumEven = mFirstDigitVal;

		std::string code;

		/* Left frame symbol */
		code += sSymbol;

		/* Left 6 digits */
		for ( int i = 0; i < 6; i++ )
		{
			int cValue = cookedData[i] - '0';
			code += symbols[ cValue ][ parity[ mFirstDigitVal ][ i ] ];

			if ( (i & 1) == 0 )
			{
				sumOdd += cValue;
			}
			else
			{
				sumEven += cValue;
			}
		}

		/* Middle frame symbol */
		code += mSymbol;

		/* Right 5 digits */
		for ( int i = 6; i < 11; i++ )
		{
			int cValue = cookedData[i] - '0';
			code += symbols[cValue][P_ODD];

			if ( (i & 1) == 0 )
			{
				sumOdd += cValue;
			}
			else
			{
				sumEven += cValue;
			}
		}

		/* Check digit */
		mCheckDigitVal = (3*sumOdd + sumEven) % 10;
		if ( mCheckDigitVal != 0 )
		{
			mCheckDigitVal = 10 - mCheckDigitVal;
		}
		code += symbols[mCheckDigitVal][P_ODD];

		/* Right frame symbol */
		code += eSymbol;

		/* Append a final zero length space to make the length of the encoded string even. */
		/* This is because vectorize() handles bars and spaces in pairs. */
		code += "0";

		return code;
	}



	/*
	 * UPC prepare text for display, implements Barcode1dBase::prepareText()
	 */
	std::string BarcodeUpcBase::prepareText( const std::string& rawData )
	{
		std::string displayText;

		for (char c : rawData)
		{
			if ( isdigit( c ) )
			{
				displayText += c;
			}
		}

		displayText += (mCheckDigitVal + '0');

		return displayText;
	}


	/*
	 * UPC vectorization, implements Barcode1dBase::vectorize()
	 */
	void BarcodeUpcBase::vectorize( const std::string& codedData,
					const std::string& displayText,
					const std::string& cookedData,
					double&            w,
					double&            h )
	{
		/* determine width and establish horizontal scale */
		int nModules     = 7*int(cookedData.size()+1) + 11;

		double scale;
		if ( w == 0 )
		{
			scale = 1.0;
		}
		else
		{
			scale = w / ((nModules + 2*QUIET_MODULES) * BASE_MODULE_SIZE);

			if ( scale < 1.0 )
			{
				scale = 1.0;
			}
		}
		double mscale     = scale * BASE_MODULE_SIZE;

		double width      = mscale * (nModules + 2*QUIET_MODULES);
		double xQuiet     = mscale * QUIET_MODULES;

		/* determine bar height */
		double hTextArea   = scale * BASE_TEXT_AREA_HEIGHT;
		double hBar1       = std::max( (h - hTextArea), width/2 );
		double hBar2       = hBar1 + hTextArea/2;

		/* determine text parameters */
		double textSize1   = scale * BASE_FONT_SIZE;
		double textSize2   = 0.75*textSize1;

		double textX1Left  = xQuiet + mscale*(0.25*nModules + 0.5*mEndBarsModules - 0.75);
		double textX1Right = xQuiet + mscale*(0.75*nModules - 0.5*mEndBarsModules + 0.75);
		double textX2Left  = 0.5*xQuiet;
		double textX2Right = 1.5*xQuiet + mscale*nModules;

		double textY1      = hBar2 + textSize1/4;
		double textY2      = hBar2 + textSize2/4;


		/* now traverse the code string and draw each bar */
		auto nBarsSpaces = int( codedData.size() - 1 ); /* coded data has dummy "0" on end. */

		double xModules = 0;
		for ( int i = 0; i < nBarsSpaces; i += 2 )
		{
			double hBar;

			if ( ( (xModules > mEndBarsModules) && (xModules < (nModules/2-1))               ) ||
			     ( (xModules > (nModules/2+1)) && (xModules < (nModules-mEndBarsModules)) ) )
			{
				hBar = hBar1;
			}
			else
			{
				hBar = hBar2;
			}

			/* Bar */
			int wBar = codedData[i] - '0';
			addLine( xQuiet + mscale*xModules, 0.0, mscale*wBar, hBar );
			xModules += wBar;

			/* Space */
			int wSpace = codedData[i+1] - '0';
			xModules += wSpace;
		}

		/* draw text (call implementation from concrete class) */
		vectorizeText( displayText,
			       textSize1, textSize2,
			       textX1Left, textX1Right, textY1,
			       textX2Left, textX2Right, textY2 );

		/* Overwrite requested size with actual size. */
		w = width;
		h = hBar1 + hTextArea;
	}

}
