// Each line of BASIC program has the form :
        // - 2 bytes | Line number : more significant byte, less significant byte
        // - 2 bytes | Lenght of text + ENTER : less significant byte, more significant byte
        // - Text
        // - ENTER = 00001101 (13)
        private static void ReadProgramLines(Stream binaryMemoryStream, int programLength, BasicProgram program)
        {
            int programBytesCounter = 0;
            while (programBytesCounter < programLength)
            {
                int lineNumber = ReadLineNumber(binaryMemoryStream, ref programBytesCounter);
                int lengthOfTextPlusEnter = ReadInteger(binaryMemoryStream, ref programBytesCounter);

                BasicLine line = new BasicLine(lineNumber, lengthOfTextPlusEnter);

                for (int i = 0; i < lengthOfTextPlusEnter; i++)
                {
                    SpectrumChar spectrumChar = ReadSpectrumChar(binaryMemoryStream, ref programBytesCounter);

                    // A numerical constant in the program is followed by its binary form,
                    // using the character CHR$ 14 followed by five bytes for the number itself.
                    if (spectrumChar.Code == 14)
                    {
                        SpectrumNumber spectrumNumber = ReadSpectrumNumber(binaryMemoryStream, ref programBytesCounter);
                        i += 5;
                        spectrumChar = spectrumChar.CloneForNumber(spectrumNumber);
                    }

                    line.SpectrumChars[i] = spectrumChar;
                }

                // Check : a basic line should end with Enter char
                if (line.SpectrumChars[line.SpectrumChars.Length - 1].Code != 13)
                {
                    throw new Exception("Basic line does not end with enter char");
                }
                else
                {
                    program.Lines.Add(line);
                }
            }
        }
        // The variables have different formats according to their different features.
        // The letters in the names should be imagined as starting off in lower case.
        private static void ReadProgramVariables(Stream binaryMemoryStream, int variablesLength, BasicProgram program)
        {
            int variablesBytesCounter = 0;
            while(variablesBytesCounter < variablesLength)
            {
                byte varTypeAndFirstLetter = ReadByte(binaryMemoryStream, ref variablesBytesCounter);
                byte varType = (byte)(varTypeAndFirstLetter & 0xE0);
                byte firstLetterCode = (byte)((varTypeAndFirstLetter & 0x1F) + 0x60);

                string name = SpectrumCharSet.GetSpectrumChar(firstLetterCode).Text;

                switch (varType)
                {
                    // Number with one letter name
                    // - 0 1 1 _ _ _ _ _ : letter - 60H
                    // - 5 bytes         : value
                    case 96:
                        NumberVariable numVariable = new NumberVariable() { Name = name };
                        numVariable.Value = ReadSpectrumNumber(binaryMemoryStream, ref variablesBytesCounter);
                        program.Variables.Add(numVariable);
                        break;
                    // Number whose name is longer than one letter
                    // - 1 0 1 _ _ _ _ _ : letter - 60H
                    // - 0 _ _ _ _ _ _ _ : 2nd character
                    //    ...
                    // - 1 _ _ _ _ _ _ _ : last character
                    // - 5 bytes         : value
                    case 160:
                        byte charCode;
                        while(((charCode = ReadByte(binaryMemoryStream, ref variablesBytesCounter)) & 0x80) == 0)
                        {
                            name += SpectrumCharSet.GetSpectrumChar(charCode).Text;
                        }
                        name += SpectrumCharSet.GetSpectrumChar((byte)(charCode-128)).Text;
                        numVariable = new NumberVariable() { Name = name };
                        numVariable.Value = ReadSpectrumNumber(binaryMemoryStream, ref variablesBytesCounter);
                        program.Variables.Add(numVariable);
                        break;
                    // Array of numbers
                    // - 1 0 0 _ _ _ _ _ : letter - 60H
                    // - 2 bytes         : total lenght of elements & dimensions + 1 for no. of dimensions
                    // - 1 byte          : no. of dimensions
                    // - 2 bytes         : first dimension
                    //    ...
                    // - 2 bytes         : last dimension
                    // - 5 bytes each    : values
                    // The order of the elements is:
                    // first, the elements for which the first subscript is 1;
                    // next, the elements for which the first subscript is 2;
                    // next, the elements for which the first subscript is 3;
                    // and so on for all possible values of the first subscript.
                    // The elements with a given first subscript are ordered in the same way using the second subscript, and so on down to the last.
                    // As an example, the elements of the 3*6 array b in Chapter 12 are stored in the order b(1,1) b(1,2) b(1,3) b(1,4) b(1,5) b(1,6) b(2,1) b(2,2) .... b(2,6) b(3,1) b(3,2) ... b(3,6).
                    case 128:
                        NumberArrayVariable numArrayVariable = new NumberArrayVariable() { Name = name };
                            ReadInteger(binaryMemoryStream, ref variablesBytesCounter);
                        numArrayVariable.NumberOfDimensions = ReadByte(binaryMemoryStream, ref variablesBytesCounter);
                        numArrayVariable.DimensionsSizes = new int[numArrayVariable.NumberOfDimensions];
                        int valuesCount = 1;
                        for (int i = 0; i < numArrayVariable.NumberOfDimensions ; i++)
                        {
                            numArrayVariable.DimensionsSizes[i] = ReadInteger(binaryMemoryStream, ref variablesBytesCounter);
                            valuesCount *= numArrayVariable.DimensionsSizes[i];
                        }
                        for (int i = 0; i < valuesCount; i++)
                        {
                            SpectrumNumber value = ReadSpectrumNumber(binaryMemoryStream, ref variablesBytesCounter);
                            numArrayVariable.Values.Add(value);
                        }
                        program.Variables.Add(numArrayVariable);
                        break;
                    // Control variable of a FOR-NEXT loop
                    // - 1 1 1 _ _ _ _ _ : letter - 60H
                    // - 5 bytes         : value
                    // - 5 bytes         : limit
                    // - 5 bytes         : step
                    // - 2 bytes         : looping line, lsb | msb
                    // - 1 byte          : statement number whitin line
                    case 224:
                        ForLoopControlVariable forVariable = new ForLoopControlVariable() { Name = name };
                        forVariable.Value = ReadSpectrumNumber(binaryMemoryStream, ref variablesBytesCounter);
                        forVariable.Limit = ReadSpectrumNumber(binaryMemoryStream, ref variablesBytesCounter);
                        forVariable.Step = ReadSpectrumNumber(binaryMemoryStream, ref variablesBytesCounter);
                        forVariable.LoopingLine = ReadInteger(binaryMemoryStream, ref variablesBytesCounter);
                        forVariable.StatementNumberWithinLine = ReadByte(binaryMemoryStream, ref variablesBytesCounter);
                        program.Variables.Add(forVariable);
                        break;
                    // String
                    // - 0 1 0 _ _ _ _ _ : letter - 60H
                    // - 2 bytes         : number of characters
                    // - text of string (may be empty)
                    case 64:
                        StringVariable strVariable = new StringVariable() { Name = name + "$" };
                        strVariable.CharsCount = ReadInteger(binaryMemoryStream, ref variablesBytesCounter);
                        strVariable.Chars = new SpectrumChar[strVariable.CharsCount];
                        StringBuilder sb = new StringBuilder();
                        for (int i = 0; i < strVariable.CharsCount; i++)
                        {
                            strVariable.Chars[i] = ReadSpectrumChar(binaryMemoryStream, ref variablesBytesCounter);
                            sb.Append(strVariable.Chars[i].Text);
                        }
                        strVariable.Text = sb.ToString();
                        program.Variables.Add(strVariable);
                        break;
                    // Array of characters
                    // - 1 1 0 _ _ _ _ _ : letter - 60H
                    // - 2 bytes         : total no. elements & dims + 1 for no. of dims
                    // - 1 byte          : no. of dimensions
                    // - 2 bytes         : first dimension
                    //    ...
                    // - 2 bytes         : last dimension
                    // - 1 byte each     : elements
                    case 192:
                        CharArrayVariable charArrayVariable = new CharArrayVariable() { Name = name + "$" };
                            ReadInteger(binaryMemoryStream, ref variablesBytesCounter);
                        charArrayVariable.NumberOfDimensions = ReadByte(binaryMemoryStream, ref variablesBytesCounter);
                        charArrayVariable.DimensionsSizes = new int[charArrayVariable.NumberOfDimensions];
                        valuesCount = 1;
                        for (int i = 0; i < charArrayVariable.NumberOfDimensions; i++)
                        {
                            charArrayVariable.DimensionsSizes[i] = ReadInteger(binaryMemoryStream, ref variablesBytesCounter);
                            valuesCount *= charArrayVariable.DimensionsSizes[i];
                        }
                        for (int i = 0; i < valuesCount; i++)
                        {
                            SpectrumChar value = ReadSpectrumChar(binaryMemoryStream, ref variablesBytesCounter);
                            charArrayVariable.Values.Add(value);
                        }
                        program.Variables.Add(charArrayVariable);
                        break;
                    default:
                        throw new Exception("Uknown variable type");
                }
            }
        }
        /// <summary>
        /// Source : ZXBasicManual/zxmanchap24.html
        /// </summary>
        public static BasicProgram ReadMemoryFormat(string sourcePath, Stream binaryMemoryStream, int programLength, int variablesLength)
        {
            BasicProgram program = new BasicProgram(sourcePath);

            ReadProgramLines(binaryMemoryStream, programLength, program);
            ReadProgramVariables(binaryMemoryStream, variablesLength, program);

            return program;
        }