/*********************************************************************
*
*  HTTPPrint.h file generation and dynamic variable parsing.
*
**********************************************************************
* FileName:        DynVar.java
* Complier:        JAVA version "1.6.0_20 and onwards"
* IDE :            Netbean
* Software License Agreement
*
* Copyright (C) 2012 Microchip Technology Inc.  All rights reserved.
*
* Microchip licenses to you the right to use, modify, copy, and
* distribute the Software when used with a Microchip microcontroller or
* digital signal controller product which is integrated into Licensee's product.
*
* You should refer to the license agreement accompanying this
* Software for additional information regarding your rights and
* obligations.
*
* 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
**********************************************************************/

package MicrochipMPFS;

import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.*;

public class DynVar {

    int offSetCounter = 0;
    int parseItrtn=0;
    int tempFileRcrdLen=0;
    //private Vector<Byte> resizeArray = new Vector(8,8);
    boolean tcpipVersion6used;
    private static List<DynamicVariable> vars;
    String regEx = "~(inc:[A-Za-z0-9\\ \\.-_\\/]{1,60}|[A-Za-z0-9_]{0,40}(\\([A-Za-z0-9_,\\ ]*\\))?)~";
    private Pattern parser = Pattern.compile(regEx);

    private String projectDir;

    String HTTPPRINT_H_HEADER =
        "/*********************************************************************\r\n" +
        " * FileName: HTTPPrint.h\r\n" +
        " * Provides callback headers and resolution for user\'s custom\r\n" +
        " * HTTP Application.\r\n" +
        " * Processor: PIC18,PIC24E, PIC24F, PIC24H, dsPIC30F, dsPIC33F, dsPIC33E,PIC32\r\n" +
        " * Compiler:  Microchip C18, C30, C32\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" +
        " * Software License Agreement\r\n" +
        " *\r\n" +
        " * Copyright (C) 2012 Microchip Technology Inc.  All rights\r\n" +
        " * reserved.\r\n" +
        " *\r\n" +
        " * Microchip licenses to you the right to use, modify, copy, and \r\n " +
        " * distribute: \r\n" +
        " * (i)  the Software when embedded on a Microchip microcontroller or \r\n" +
        " *      digital signal controller product (\"Device\") which is \r\n" +
        " *      integrated into Licensee's product; or \r\n" +
        " * (ii) ONLY the Software driver source files ENC28J60.c, ENC28J60.h,\r\n" +
        " *		ENCX24J600.c and ENCX24J600.h ported to a non-Microchip device\r\n" +
        " *		used in conjunction with a Microchip ethernet controller for\r\n" +
        " *		the sole purpose of interfacing with the ethernet controller.\r\n" +
        " *\r\n" +
        " * You should refer to the license agreement accompanying this \r\n" +
        " * Software for additional information regarding your rights and \r\n" +
        " * obligations.\r\n" +
        " *\r\n" +
        " * THE SOFTWARE AND DOCUMENTATION ARE PROVIDED \"AS IS\" WITHOUT\r\n" +
        " * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT\r\n" +
        " * LIMITATION, ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR A \r\n" +
        " * PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL\r\n" +
        " * MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR\r\n" +
        " * CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF\r\n" +
        " * PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS\r\n" +
        " * BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE \r\n" +
        " * THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER \r\n" +
        " * SIMILAR COSTS, WHETHER ASSERTED ON THE BASIS OF CONTRACT, TORT\r\n" +
        " * (INCLUDING NEGLIGENCE), BREACH OF WARRANTY, OR OTHERWISE.\r\n" +
        " *\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_STUB httpStubs[MAX_HTTP_CONNECTIONS];\r\n" +
        "extern BYTE curHTTPID;\r\n\r\n" +
        "void HTTPPrint(DWORD callbackID);\r\n";
    String HTTPPRINT_H_MIDDLE =
        "\r\nvoid HTTPPrint(DWORD callbackID)\r\n" +
        "{\r\n" +
        "	switch(callbackID)\r\n" +
        "	{\r\n";
    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";
    String HTTPPRINT_H_HEADER_UNIFIEDSTACK = 
        "/*********************************************************************\r\n" +
        " * FileName: http_print.h\r\n" +
        " * Provides callback headers and resolution for user\'s custom\r\n" +
        " * HTTP Application.\r\n" +
        " * Processor: PIC24E, PIC24F, PIC24H, dsPIC30F, dsPIC33F, dsPIC33E,PIC32\r\n" +
        " * Compiler:  Microchip C30, C32,XC30,XC32\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" +
        " * Software License Agreement\r\n" +
        " *\r\n" +
        " * Copyright (C) 2012 Microchip Technology Inc.  All rights\r\n" +
        " * reserved.\r\n" +
        " *\r\n" +
        " * Microchip licenses to you the right to use, modify, copy, and \r\n " +
        " * distribute: \r\n" +
        " * (i)  the Software when embedded on a Microchip microcontroller or \r\n" +
        " *      digital signal controller product (\"Device\") which is \r\n" +
        " *      integrated into Licensee's product; or \r\n" +
        " * (ii) ONLY the Software driver source files ENC28J60.c, ENC28J60.h,\r\n" +
        " *		ENCX24J600.c and ENCX24J600.h ported to a non-Microchip device\r\n" +
        " *		used in conjunction with a Microchip ethernet controller for\r\n" +
        " *		the sole purpose of interfacing with the ethernet controller.\r\n" +
        " *\r\n" +
        " * You should refer to the license agreement accompanying this \r\n" +
        " * Software for additional information regarding your rights and \r\n" +
        " * obligations.\r\n" +
        " *\r\n" +
        " * THE SOFTWARE AND DOCUMENTATION ARE PROVIDED \"AS IS\" WITHOUT\r\n" +
        " * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT\r\n" +
        " * LIMITATION, ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR A \r\n" +
        " * PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL\r\n" +
        " * MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR\r\n" +
        " * CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF\r\n" +
        " * PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS\r\n" +
        " * BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE \r\n" +
        " * THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER \r\n" +
        " * SIMILAR COSTS, WHETHER ASSERTED ON THE BASIS OF CONTRACT, TORT\r\n" +
        " * (INCLUDING NEGLIGENCE), BREACH OF WARRANTY, OR OTHERWISE.\r\n" +
        " *\r\n"+
        " *********************************************************************/\r\n\r\n" +
        "#ifndef __HTTPPRINT_H\r\n" +
        "#define __HTTPPRINT_H\r\n\r\n" +
        "#include \"tcpip/tcpip.h\"\r\n\r\n" +
        "#if defined(TCPIP_STACK_USE_HTTP2_SERVER)\r\n\r\n" +
        "#if defined(HTTP_SAVE_CONTEXT_IN_PIC_RAM)\r\n" +
        "\textern HTTP_CONN HTTPControlBlocks[HTTP_MAX_CONNECTIONS];\r\n" +
        "\t#define curHTTP HTTPControlBlocks[curHTTPID]\r\n" +
        "#else\r\n" +
        "\textern HTTP_CONN curHTTP;\r\n"+
        "#endif\r\n\r\n" +
        "extern HTTP_STUB httpStubs[HTTP_MAX_CONNECTIONS];\r\n"+
        "extern uint8_t curHTTPID;\r\n\r\n"+
        "void HTTPPrint(uint32_t callbackID);\r\n";
    String HTTPPRINT_H_MIDDLE_UNIFIED_STACK =
        "\r\nvoid HTTPPrint(uint32_t callbackID)\r\n" +
        "{\r\n" +
        "	switch(callbackID)\r\n" +
        "	{\r\n";
    String HTTPPRINT_H_FOOTER_UNIFIED_STACK =
        "		default:\r\n" +
        "			// Output notification for undefined values\r\n" +
        "			TCPPutArray(sktHTTP, (const uint8_t*)\"!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";

/*
 * Collect the Dynamic variable in the HTTPPrint.idx file.
 */
    public DynVar(String path,boolean ifTcpipVersion6used)
    {
        this.projectDir = path;
        vars = new LinkedList<DynamicVariable>();
        tcpipVersion6used = ifTcpipVersion6used;
        // Read previous index file if it exists.
        File file_exists;
        FileInputStream inputFile;
        try
        {
            if(tcpipVersion6used)
            {
                file_exists = new File(projectDir + "http_print.idx");
            }
            else
            {
                file_exists = new File(projectDir + "HTTPPrint.idx");
            }
            if(file_exists.exists() && file_exists.length()>0)
            {
                if(tcpipVersion6used)
                {
                    inputFile = new FileInputStream(projectDir + "http_print.idx");
                }
                else
                {
                    inputFile = new FileInputStream(projectDir + "HTTPPrint.idx");
                }
                DataInputStream in = new DataInputStream(inputFile);
                BufferedReader fin = new BufferedReader(new InputStreamReader(in));
                String s = fin.readLine();

                if (!s.contains("|"))
                {
                    while (s != null)
                    {
                        DynamicVariable dv = new DynamicVariable(s);
                        vars.add(dv);
                        s = fin.readLine();
                    }
                }
                fin.close();
            }
        }
        catch(IOException e)
        {
            // do nothing...just won't have old index information
        }
    }


    /// <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,StringBuilder strLine)
    {
        int dynVarCntr=0;
        //Vector<Byte> resizeArray = new Vector(8,8);
        ByteArrayOutputStream resizeArray = new ByteArrayOutputStream();
        Matcher matches = parser.matcher(strLine);
        
        while(matches.find())
        {
             int i = GetIndex(matches.group().replace(" ","").replace("~",""));

            resizeArray.write((byte)matches.start());
            resizeArray.write((byte)(matches.start()>>8));
            resizeArray.write((byte)(matches.start()>>16));
            resizeArray.write((byte)(matches.start()>>24));
            resizeArray.write((byte)i);
            resizeArray.write((byte)(i>>8));
            resizeArray.write((byte)(i>>16));
            resizeArray.write((byte)(i>>24));
      
            file.dynVarOffsetAndIndexID.addElement((byte)(matches.start() >> 0));
            file.dynVarOffsetAndIndexID.addElement((byte)(matches.start() >> 8));
            file.dynVarOffsetAndIndexID.addElement((byte)(matches.start() >> 16));
            file.dynVarOffsetAndIndexID.addElement((byte)(matches.start() >> 24));
            file.dynVarOffsetAndIndexID.addElement((byte)(i >> 0));
            file.dynVarOffsetAndIndexID.addElement((byte)(i >> 8));
            file.dynVarOffsetAndIndexID.addElement((byte)(i >> 16));
            file.dynVarOffsetAndIndexID.addElement((byte)(i >> 24));
            
            file.dynVarCntr = ++dynVarCntr;
            offSetCounter = offSetCounter + 8;
        }

        if(parseItrtn == (int)0x0)
        {
             file.fileRecordOffset = (int)0x0;
             offSetCounter = (int)0x0;
        }
        else
        {
              file.fileRecordOffset=tempFileRcrdLen;
        }
        if(file.dynVarCntr != 0)
        {
            file.fileRecordLength = 4 /* 4 bytes for file record length itself*/
                                    +2 /*To store the hasIndex/isZipped flag*/
                                    //+(UInt32)file.FileName.Length
                                    + file.dynVarCntr*8;

            tempFileRcrdLen += file.fileRecordLength;

            parseItrtn++;
        }


        // Determine if any matches were made
        if (resizeArray.size() == 0)
        {
            return null;
        }
        else
        {
            // Set up new file record
            MPFSFileRecord idxFile = new MPFSFileRecord();
            idxFile.SetFileName("");   //.FileName = "";
            idxFile.SetFiledate(file.fileDate);
            idxFile.isIndex = true;
            idxFile.data = resizeArray.toByteArray();
            idxFile.fileSizeLen = resizeArray.size();
            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 boolean WriteIndices()
    {
        // Determine if an update is necessary
        boolean isChanged = false;
        File httpFileIdx;
        for(DynamicVariable dv : vars)
        {
            if ((dv.getWasUsed() && dv.getCount() == 0) ||
                (!dv.getWasUsed() && dv.getCount() != 0))
            {
                isChanged = true;
                break;
            }
        }
        if (!isChanged)
        {
            return false;
        }

        // Write out HTTPPrint.idx
        if(tcpipVersion6used)
        {
            httpFileIdx = new File(projectDir +"http_print.idx");
        }
        else
        {
            httpFileIdx = new File(projectDir +"HTTPPrint.idx");
        }
        try{
        FileOutputStream file_output = new FileOutputStream(httpFileIdx);
        DataOutputStream data_out = new DataOutputStream (file_output);
        BufferedWriter fout = new BufferedWriter(new OutputStreamWriter(data_out));
               
        for(DynamicVariable dv : vars)
        {
            if (dv.getCount() > 0)
            {
                fout.write('+');
            }
            fout.write(dv.GetName());
            fout.write('\n');
        }
        fout.flush();
        fout.close();

        // Begin writing HTTPPrint.h
        if(tcpipVersion6used)
        {
            file_output = new FileOutputStream(projectDir + "http_print.h");
        }
        else
        {
            file_output = new FileOutputStream(projectDir + "HTTPPrint.h");
        }
        data_out = new DataOutputStream (file_output);
        fout = new BufferedWriter(new OutputStreamWriter(data_out));
        if(tcpipVersion6used)
        {
            fout.write(HTTPPRINT_H_HEADER_UNIFIEDSTACK);
        }
        else
        {
            fout.write(HTTPPRINT_H_HEADER);
        }

        // Write the prototypes
        List<String> prototypes = new ArrayList<String>();
        Pattern rGetParts = Pattern.compile("([A-Za-z0-9_]{0,40})(\\(([A-Za-z0-9_,]*)\\))?");
        for(DynamicVariable dv : vars)
        {
            if (dv.GetName().startsWith("inc:") || dv.getCount() == 0)
            {
                continue;
            }
            Matcher m = rGetParts.matcher(dv.GetName());
            if(m.find()){
            if (prototypes.contains(m.group(1)))
            {
                continue;
            }

            // Add the prototype
            prototypes.add(m.group(1));
            String temp_str = "void HTTPPrint_" + m.group(1) + "(";
            fout.write(temp_str);
            if(m.group(3) != null)
            {
                if(m.groupCount() == 3 && m.group(3).length() > 0)
                {
                    int numParams = m.group(3).split(",").length;
                    for (int i = 0; i < numParams - 1; i++)
                    {
                        if(tcpipVersion6used)
                        {
                            fout.write("uint16_t,");
                        }
                        else
                        {
                            fout.write("WORD,");
                        }
                        
                    }
                    if(tcpipVersion6used)
                    {
                        fout.write("uint16_t");
                    }
                    else
                    {
                        fout.write("WORD");
                    }
                }
            }
            else
            {
                fout.write("void");
            }
            fout.write(");\r\n");
            }
        }

        // Write the function itself
        if(tcpipVersion6used)
        {
            fout.write(HTTPPRINT_H_MIDDLE_UNIFIED_STACK);
        }
        else
        {
            fout.write(HTTPPRINT_H_MIDDLE);
        }
        int index = 0;
        for(DynamicVariable dv : vars)
        {
            if (dv.getCount() == 0)
            {
                index++;
                continue;
            }
            String temp_str  = "        case 0x"+String.format("%08x",index++)+":\r\n";
            fout.write(temp_str);

            // Write the actual case statement
            if(dv.GetName().startsWith("inc:"))
            {
                if(tcpipVersion6used)
                {
                    temp_str = "\t\t\tHTTPIncFile((const uint8_t*)\"" + dv.GetName().substring(4) + "\");\r\n\t\t\tbreak;\r\n";
                }
                else
                {
                    temp_str = "\t\t\tHTTPIncFile((ROM BYTE*)\"" + dv.GetName().substring(4) + "\");\r\n\t\t\tbreak;\r\n";
                }
                fout.write(temp_str);
            }
            else
            {
                temp_str = "\t\t\tHTTPPrint_" + dv.GetName();
                fout.write(temp_str);
                if(!dv.GetName().endsWith(")"))
                {
                    fout.write("()");
                }
                fout.write(";\r\n\t\t\tbreak;\r\n");
            }
        }

        // Write the footer part
        if(tcpipVersion6used)
        {
            fout.write(HTTPPRINT_H_FOOTER_UNIFIED_STACK);
        }
        else
        {
            fout.write(HTTPPRINT_H_FOOTER);
        }
        fout.flush();
        fout.close();
        }catch (IOException e){}
        return true;
    }


    /// <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)
    {
        int count=0;
        // 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.size()-1;
        }
        // Mark as used and return the index
        count = vars.get(i).getCount();
        vars.get(i).setCount(++count);
       
        return i;
    }
}

/*
 * Dynamic variable class to count and find out the instance how many times
 * it is being used.
 */
class DynamicVariable
{
    private String name;
    private boolean wasUsed;
    private int count;

    /// <summary>
    /// Gets or sets the name of this DynamicVariable
    /// </summary>
    public String GetName()
    {
        return this.name;
    }

    public void SetName(String value)
    {
        this.name = value;
    }

   /// <summary>
    /// Indicates if this specific instance was previously used
    /// </summary>
    public boolean getWasUsed()
    {
       return this.wasUsed;
    }
    public void setWasUsed(boolean value)
    {
        this.wasUsed = value;
    }
    /// <summary>
    /// Indicates how many times this instance is used
    /// </summary>
    public int getCount()
    {
       return this.count;
    }

    public void setCount(int value)
    {
       this.count = value;
    }
    private long _offsetCntr = 0;
    public long getOffsetCntr()
    {
        return this._offsetCntr;
    }

    public void setOffsetCntr(long value)
    {
         this._offsetCntr = value;
    }


    public DynamicVariable(String name)
    {
        String regEx = "[\\ \\+]";
        this.wasUsed = name.startsWith("+");
        //Pattern pattern = Pattern.compile(regEx);
        //Matcher match = pattern.matcher(name);
        //if(match.find())
       //     this.name = pattern.matcher(name).replaceFirst("");
        //else
        this.name = name.replaceFirst(regEx,"");
        //this.name = matches.group().replaceFirst(regEx,"");
        this.count = 0;
    }


    public boolean equals(Object obj)
    {
        if (obj instanceof DynamicVariable)
        {
            return ((DynamicVariable)obj).GetName().contentEquals(this.name);
        }
        else
        {
            return false;
        }
    }

    public int hashCode()
    {
        return this.name.hashCode();
    }
}
