/*********************************************************************
 *
 *    Dynamic Variable Parser Library
 *
 *********************************************************************
 * FileName:        DynVar.cs
 * Dependencies:    Microsoft .NET Framework 2.0
 * Processor:       x86
 * Complier:        Microsoft Visual C# 2008 Express Edition
 * Company:         Microchip Technology, Inc.
 *
 * Software License Agreement
 *
 * This software is owned by Microchip Technology Inc. ("Microchip") 
 * and is supplied to you for use exclusively as described in the 
 * associated software agreement.  This software is protected by 
 * software and other intellectual property laws.  Any use in 
 * violation of the software license may subject the user to criminal 
 * sanctions as well as civil liability.  Copyright 2008 Microchip
 * Technology Inc.  All rights reserved.
 *
 *
 * THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT 
 * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT 
 * LIMITATION, ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR A 
 * PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL 
 * MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR 
 * CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF 
 * PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS 
 * BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE 
 * THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER 
 * SIMILAR COSTS, WHETHER ASSERTED ON THE BASIS OF CONTRACT, TORT 
 * (INCLUDING NEGLIGENCE), BREACH OF WARRANTY, OR OTHERWISE.
 *
 *
 * Author               Date   		Comment
 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Elliott Wood		4/17/2008	    Original
 ********************************************************************/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;

namespace Microchip
{

    public class DynamicVariableParser
    {
        #region Fields
        private List<DynamicVariable> vars;
        private Regex parser = new Regex(@"~(inc:[A-Za-z0-9\ \.-_\\/]{1,60}|[A-Za-z0-9_]{0,40}(\([A-Za-z0-9_,\ ]*\))?)~");
        private ASCIIEncoding ascii = new ASCIIEncoding();
        private String projectDir;
        #endregion

        #region String Constants
        const string HTTPPRINT_H_HEADER = 
            "/**************************************************************\r\n" +
            " * HTTPPrint.h\r\n" +
            " * Provides callback headers and resolution for user\'s custom\r\n" +
            " * HTTP Application.\r\n" +
            " * \r\n" +
            " * This file is automatically generated by the MPFS Utility\r\n" +
            " * ALL MODIFICATIONS WILL BE OVERWRITTEN BY THE MPFS GENERATOR\r\n" +
            " **************************************************************/\r\n\r\n" +
            "#ifndef __HTTPPRINT_H\r\n" +
            "#define __HTTPPRINT_H\r\n\r\n" +
            "#include \"TCPIP Stack/TCPIP.h\"\r\n\r\n" +
            "#if defined(STACK_USE_HTTP2_SERVER)\r\n\r\n" +
            "extern HTTP_CONN curHTTP;\r\n" +
            "extern HTTP_STUB httpStubs[MAX_HTTP_CONNECTIONS];\r\n" +
            "extern BYTE curHTTPID;\r\n\r\n" +
            "void HTTPPrint(DWORD callbackID);\r\n";
        const string HTTPPRINT_H_MIDDLE =
            "\r\nvoid HTTPPrint(DWORD callbackID)\r\n" +
            "{\r\n" +
            "	switch(callbackID)\r\n" +
            "	{\r\n";
        const string HTTPPRINT_H_FOOTER =
            "		default:\r\n" +
            "			// Output notification for undefined values\r\n" +
            "			TCPPutROMArray(sktHTTP, (ROM BYTE*)\"!DEF\", 4);\r\n" +
            "	}\r\n\r\n" +
            "	return;\r\n" +
            "}\r\n\r\n" +
            "void HTTPPrint_(void)\r\n" +
            "{\r\n" +
            "	TCPPut(sktHTTP, '~');\r\n" +
            "	return;\r\n" +
            "}\r\n\r\n#endif\r\n\r\n#endif\r\n";
        #endregion

        #region Constructor
        public DynamicVariableParser(String path)
        {
            this.projectDir = path;
            vars = new List<DynamicVariable>();
            
            // Read previous index file if it exists.
            try
            {
                if (File.Exists(projectDir + "HTTPPrint.idx"))
                {
                    StreamReader fin = new StreamReader(projectDir + "HTTPPrint.idx");
                    String s = fin.ReadLine();

                    // Ignore old MPFS2.0 HTTPPrint.idx files
                    if (!s.Contains("|"))
                    {
                        while (s != null)
                        {
                            DynamicVariable dv = new DynamicVariable(s);
                            vars.Add(dv);
                            s = fin.ReadLine();
                        }
                    }
                    fin.Close();
                }
            }
            catch
            {
                // do nothing...just won't have old index information
            }
        }
        #endregion

        /// <summary>
        /// Parses and indexes a file for dynamic variables
        /// </summary>
        /// <param name="file">The MPFSFileRecord to parse</param>
        /// <returns>An MPFSFileRecord of indexes, or null if no variables were found</returns>
        public MPFSFileRecord Parse(MPFSFileRecord file)
        {
            byte[] idxData = new byte[0];

            // Find and index the matches
            MatchCollection matches = parser.Matches(ascii.GetString(file.data));
            foreach(Match m in matches)
            {
                int i = GetIndex(m.Value.Replace(" ","").Replace("~",""));
                Array.Resize(ref idxData, idxData.Length + 8);
                idxData[idxData.Length - 8] = (byte)m.Index;
                idxData[idxData.Length - 7] = (byte)(m.Index >> 8);
                idxData[idxData.Length - 6] = (byte)(m.Index >> 16);
                idxData[idxData.Length - 5] = (byte)(m.Index >> 24);
                idxData[idxData.Length - 4] = (byte)i;
                idxData[idxData.Length - 3] = (byte)(i >> 8);
                idxData[idxData.Length - 2] = (byte)(i >> 16);
                idxData[idxData.Length - 1] = (byte)(i >> 24);
            }

            // Determine if any matches were made
            if (idxData.Length == 0)
                return null;
            else
            {
                // Set up new file record
                MPFSFileRecord idxFile = new MPFSFileRecord();
                idxFile.FileName = "";
                idxFile.fileDate = file.fileDate;
                idxFile.isIndex = true;
                idxFile.data = idxData;
                return idxFile;
            }

        }

        /// <summary>
        /// Writes out HTTPPrint.h and HTTPPrint.idx if necessary
        /// </summary>
        /// <returns>TRUE if the files were written, FALSE if no changes are needed</returns>
        public bool WriteIndices()
        {
            // Determine if an update is necessary
            bool isChanged = false;
            foreach (DynamicVariable dv in vars)
            {
                if ((dv.WasUsed && dv.Count == 0) ||
                    (!dv.WasUsed && dv.Count != 0))
                {
                    isChanged = true;
                    break;
                }
            }
            if (!isChanged)
                return false;

            // Write out HTTPPrint.idx
            StreamWriter fout = new StreamWriter(projectDir + "HTTPPrint.idx", false);
            foreach (DynamicVariable dv in vars)
            {
                if (dv.Count > 0)
                    fout.Write('+');
                fout.WriteLine(dv.Name);
            }
            fout.Flush();
            fout.Close();

            // Begin writing HTTPPrint.h
            fout = new StreamWriter(projectDir + "HTTPPrint.h", false);
            fout.Write(HTTPPRINT_H_HEADER);

            // Write the prototypes
            List<string> prototypes = new List<string>();
            Regex rGetParts = new Regex(@"([A-Za-z0-9_]{0,40})(\(([A-Za-z0-9_,]*)\))?");
            foreach (DynamicVariable dv in vars)
            {
                if (dv.Name.StartsWith("inc:") || dv.Count == 0)
                    continue;
                Match m = rGetParts.Match(dv.Name);
                if (prototypes.Contains(m.Groups[1].Value))
                    continue;

                // Add the prototype
                prototypes.Add(m.Groups[1].Value);
                fout.Write("void HTTPPrint_" + m.Groups[1].Value + "(");
                if(m.Groups.Count == 4 && m.Groups[3].Value.Length > 0)
                {
                    int numParams = m.Groups[3].Value.Split(',').Length;
                    for (int i = 0; i < numParams - 1; i++)
                        fout.Write("WORD,");
                    fout.Write("WORD");
                }
                else
                {
                    fout.Write("void");
                }
                fout.Write(");\r\n");
            }

            // Write the function itself
            fout.Write(HTTPPRINT_H_MIDDLE);
            int index = 0;
            foreach (DynamicVariable dv in vars)
            {
                if (dv.Count == 0)
                {
                    index++;
                    continue;
                }

                fout.Write("        case 0x" + Convert.ToString(index++, 16).PadLeft(8, '0') + ":\r\n");

                // Write the actual case statement
                if(dv.Name.StartsWith("inc:"))
                {
                    fout.Write("\t\t\tHTTPIncFile((ROM BYTE*)\"" + dv.Name.Substring(4) + "\");\r\n\t\t\tbreak;\r\n");
                }
                else
                {
                    fout.Write("\t\t\tHTTPPrint_" + dv.Name);
                    if(!dv.Name.EndsWith(")"))
                        fout.Write("()");
                    fout.Write(";\r\n\t\t\tbreak;\r\n");
                }
            }

            // Write the footer part
            fout.Write(HTTPPRINT_H_FOOTER);
            fout.Flush();
            fout.Close();
            
            return true;
        }

        #region Private Methods
        /// <summary>
        /// Finds the index of a dynamic variable, or creates a new one
        /// </summary>
        /// <param name="name"></param>
        /// <returns>The index of the dynamic variable</returns>
        private int GetIndex(String name)
        {
            // Search for the dynamic variable
            DynamicVariable dv = new DynamicVariable(name);
            int i = vars.IndexOf(dv);
            
            // If not found, add a new one
            if (i == -1)
            {
                vars.Add(dv);
                i = vars.Count - 1;
            }

            // Mark as used and return the index
            vars[i].Count++;
            return i;
        }
        #endregion

    }

    public class DynamicVariable
    {
        #region Fields
        private String name;
        private bool wasUsed;
        private int count;
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the name of this DynamicVariable
        /// </summary>
        public String Name
        {
            get { return this.name; }
            set { this.name = value; }
        }

       /// <summary>
        /// Indicates if this specific instance was previously used
        /// </summary>
        public bool WasUsed
        {
            get { return this.wasUsed; }
            set { this.wasUsed = value; }
        }

        /// <summary>
        /// Indicates how many times this instance is used
        /// </summary>
        public int Count
        {
            get { return this.count; }
            set { this.count = value; }
        }
        #endregion

        #region Constructor
        public DynamicVariable(String name)
        {
            this.wasUsed = name.StartsWith("+");
            this.name = Regex.Replace(name, @"[\ \+]", "");
            this.count = 0;
        }
        #endregion

        public override bool Equals(object obj)
        {
            if (obj is DynamicVariable)
                return ((DynamicVariable)obj).Name == this.name;
            else
                return false;
        }

        public override int GetHashCode()
        {
            return this.name.GetHashCode();
        }
    }

}
