// <copyright file="EasyScriptUncompressedDecoder.cs" company="Scriptel Corporation">
//      Copyright 2015 - Scriptel Corporation
// </copyright>
namespace EasyScriptAPI
{
    using System;
    using System.Collections.Generic;
    using System.Text;

    /// <summary>
    /// This class implements a decoder for the legacy uncompressed keyboard
    /// streams produced by EasyScript devices.
    /// </summary>
    public class EasyScriptUncompressedDecoder: IEasyScriptDecoder
    {
        /// <summary>
        /// The maximum number that it is possible to encode using the numbering
        /// scheme used by the ScripTouch EasyScript products.
        /// </summary>
        private static readonly int MaxNum = (21 * 23) + 22;
        /// <summary>
        /// The signature protocol that should be used to decode the points.
        /// </summary>
        private ISignatureProtocol signatureProtocol;
        /// <summary>
        /// A list of listeners interested in signature events.
        /// </summary>
        private List<IEasyScriptEventListener> listeners;
        /// <summary>
        /// The current position in the signature stream.
        /// </summary>
        private int currentPosition;
        /// <summary>
        /// Whether or not the next coordinate should be preceeded with a new
        /// stroke event.
        /// </summary>
        private bool newStroke;
        /// <summary>
        /// The signature components passed to us required to parse the next
        /// coordinate.
        /// </summary>
        private StringBuilder signatureBuilder = new StringBuilder();

        /// <summary>
        /// Initializes a new instance of the <see cref="EasyScriptUncompressedDecoder"/> class.
        /// Constructor, gets the class ready to parse an uncompressed stream.
        /// </summary>
        /// <param name="signatureProtocol">Signature protocol to use.</param>
        /// <param name="currentPosition">Current position in the signature stream.</param>
        /// <param name="listeners">Listeners to notify on events.</param>
        public EasyScriptUncompressedDecoder(ISignatureProtocol signatureProtocol, int currentPosition, List<IEasyScriptEventListener> listeners)
        {
            this.newStroke = true;
            this.signatureProtocol = signatureProtocol;
            this.listeners = listeners;
            this.currentPosition = currentPosition;
        }

        /// <summary>
        /// Parses the next character in the signature stream.
        /// </summary>
        /// <param name="chr">Next character in the signature stream.</param>
        public void ParseSignature(char chr)
        {
            currentPosition++;
            if (chr == signatureProtocol.GetPenUp())
            {
                newStroke = true;
                if (signatureBuilder.Length != 0)
                {
                    throw new SignatureInvalidException("Buffer wasn't empty when a new stroke was found.", currentPosition);
                }
            }
            else if (chr == signatureProtocol.GetEndStream())
            {
                foreach (IEasyScriptEventListener recv in listeners)
                {
                    recv.EndOfSignature();
                }
            }
            else if (chr == signatureProtocol.GetCancelStream())
            {
                foreach (IEasyScriptEventListener recv in listeners)
                {
                    recv.Cancel();
                }
            }
            else
            {
                signatureBuilder.Append(chr);
                if (signatureBuilder.Length == 4)
                {
                    Coordinate c = DecodePoint(new char[] { signatureBuilder[0], signatureBuilder[1], signatureBuilder[2], signatureBuilder[3]});
                    signatureBuilder.Length = 0;
                    if (newStroke)
                    {
                        foreach (IEasyScriptEventListener recv in listeners)
                        {
                            recv.NewStroke();
                        }
                        newStroke = false;
                    }

                    foreach (IEasyScriptEventListener recv in listeners)
                    {
                        recv.ReceiveCoordinate(c);
                    }
                }

            }
        }

        /// <summary>
        /// This method attempts to take a four character point and decode it using
        /// the protocol's value tables.
        /// </summary>
        /// <param name="ePoint">Array of four characters from the signature array to decode.</param>
        /// <returns>Coordinate representing the decoded point in percentage of the screen (0->1)</returns>
        /// <exception cref="SignatureInvalidException">Thrown in the event there was a problem reading the point.</exception>
        private Coordinate DecodePoint(char[] ePoint)
        {
            int[,] d = { { -1, -1 }, { -1, -1 } };
            byte[][][] values = new byte[2][][];
            values[0] = signatureProtocol.GetXValues();
            values[1] = signatureProtocol.GetYValues();

            for (int i = 0; i < ePoint.Length; i++)
            {
                for (int axis = 0; axis < values.Length; axis++)
                {
                    for (int j = 0; j < values[axis].Length; j++)
                    {
                        for (int k = 0; k < values[axis][j].Length; k++)
                        {
                            if (values[axis][j][k] == ePoint[i])
                            {
                                d[axis, j] = k;
                            }
                        }
                    }
                }
            }

            if (d[0, 0] < 0 || d[0, 1] < 0 || d[1, 0] < 0 || d[1, 1] < 0)
            {
                throw new SignatureInvalidException("Invalid point detected missing code point: x1=" + d[0, 0] + "  x2=" + d[0, 1] + "  y1=" + d[1, 0] + "  y2=" + d[1, 1], currentPosition);
            }

            double x = ((d[0, 0] * 23) + d[0, 1]) / (double)MaxNum;
            double y = ((d[1, 0] * 23) + d[1, 1]) / (double)MaxNum;

            x *= signatureProtocol.GetWidth();
            y *= signatureProtocol.GetHeight();

            return new Coordinate(x, y);
        }
    }
}
