/// <summary>
 /// Search for the Level 1 NameField matching namefield, then
 /// walk forward to the token holding is value or
 /// opening [ or opening { right after the :
 /// </summary>
 /// <param name="bar"></param>
 /// <param name="nameField"></param>
 static void parseToValueFor(CKanFormat bar, ref PieceOfPaper p)
 {
     TokenFile.Cursor Curs = bar.MoveCursTo(p.listToAddItTo);
     //WARNING This is awful, but I did it anyways....
     // This as side effect sets the global variable IndentationGloabalHack to the whitespace preceding nameField
     // just in case someone needs it later read allthe code everywher to see how this works or doesnt.
     p.IndentationHack = Curs.TokenObj.WhiteSpace;
     Curs.advance();                            // advance over the just found identifier Token
     // does not accept EOL between name and ":"
     bar.expectToken(":", TokenCategory.Token); //advance over the ":"  NO EOLS allowed
     bar.skipEOL();                             // skips all EOLS and WS
     return;                                    // Cursor points to Value String OR [ OR { dependign on Namevalue Type
 }
        /// <summary>
        /// WARNING: Be aware this code leaves things (Paper && Curs) in a state relied on by InsertProvidesConflicts
        /// See comments there before messing with this.
        /// But genrally speaking this function :
        /// Either adds one value to a Name : [ ... ] list
        /// Or creates the list
        /// Or fxies a Name : Value into and array as required.
        /// </summary>
        /// <param name="bar"> object holding Format and File</param>
        /// <param name="p"> Literally a Place where things are writtendown for use elsewhere... scratchpad</param>
        static void fixValueInList(CKanFormat bar, ref PieceOfPaper p)
        {
            if (bar.hasANameField(p.listToAddItTo) == true)
            { // we already have a provides entry
              // Check AND Add that we provide OldIdentifier
                bool hasOldIdentifierProvided = false;

                parseToValueFor(bar, ref p); // We are now here <"provides"> <:> >here< <[>  (where provides might also be author)
                                             // or worse here ...  <"author"> <:> >here< <"authors_handle"> <,>


                if (bar.isTokenTextUse("[") == false)
                { // yep its worse here ...  <"author"> <:> >here< <"authors_handle"> <,>
                  // there is no EOL trick we can use.
                    string ExistingAuthor = bar.Curs.TokenObj.theToken;
                    if (ExistingAuthor.Equals(p.valueToAdd) == false)
                    {  // The one item in the list is not the thign we wer meant to add.
                        //First valid date to expliclty double check. Whats been implied bythis beign a Valid File and thsi NOT beigna <[>.
                        TokenFile.Line L   = bar.Curs.Line;
                        int            len = L.TheLine.Count;
                        string         t   = "Huh?"; // if it prints huh then this line in the file is an very unexpected length. Sorry: lesson dont use ugly ckan files.
                        // This may be valid line in a ckan file <SOL>"author":"Axle","version":"1.2.9.1"<EOL> but youare SOL(not the star) if you want localiser to localise it.
                        if ((len != bar.Curs.TokNo + 3) || ((t = L.TheLine[L.TheLine.Count - 2].theToken).Equals(",") == false))
                        {  // Yeah nah that kinda cant happen.  and if it does I dont care... make your file prettier.
                           // TODO if the <,> is on another should we keep it and insert here?
                            throw new FormatException($"Fatal Format Error in {bar.TokFile.FilePath}:\n\t Expected <\"author\": \"Authorname\" , \"> got <\"author\": \"Authorname\" {t} >.");
                        }
                        // Okay so the file is still just like we already validated it to be ... a legal(ish) Json File...
                        // delete the Bits we cant keep.  >here< <"ExistingAuthor"> <,> <EOL>
                        L.TheLine.RemoveAt(bar.Curs.TokNo);
                        // delete the Bits we cant keep.  >here< <,> <EOL>
                        bar.Curs.TokenObj.theToken = "[";  // reuse the , token as a [
                        // bar.Curs.TokenObj.TokenCategory = TokenCategory.Token; // already verified as true as it was a ","
                        // delete the Bits we cant keep.  >here< <[> <EOL>
                        // Not strictly required but this code is now more like most of the otehr edit code  see reuse the white space code for why thats good diea.
                        bar.expectToken("[", TokenCategory.Token);
                        // we are here <[> >here< <EOL>
                        L.TheLine.Add(new TokenFile.TokenObject(p.IndentationHack + "    ", p.valueToAdd, TokenCategory.String, (int)'"'));
                        L.TheLine.Add(new TokenFile.TokenObject("", ",", TokenCategory.Token, (int)','));
                        L.TheLine.Add(new TokenFile.TokenObject(bar.Curs.TokenObj.WhiteSpace, "", TokenCategory.tokEOL, (int)'\n'));
                        // we are here <"author"> <:> <[> >here< <EOL> <Author> <,> <EOL>
                        L.TheLine.Add(new TokenFile.TokenObject(p.IndentationHack + "    ", ExistingAuthor, TokenCategory.String, (int)'"'));
                        // nope no comma here L.TheLine.Add(new TokenFile.TokenObject("", ",", TokenCategory.Token, (int)','));
                        L.TheLine.Add(new TokenFile.TokenObject(bar.Curs.TokenObj.WhiteSpace, "", TokenCategory.tokEOL, (int)'\n'));
                        // we are here <"author"> <:> <[> >here< <EOL> <Author> <,> <EOL> <ExistingAuthor> <,> <EOL>
                        L.TheLine.Add(new TokenFile.TokenObject(p.IndentationHack, "]", TokenCategory.Token, (int)']'));
                        // This is now the lexcial equivalent of the <,>  token we deleted above
                        L.TheLine.Add(new TokenFile.TokenObject("", ",", TokenCategory.Token, (int)','));
                        L.TheLine.Add(new TokenFile.TokenObject(bar.Curs.TokenObj.WhiteSpace, "", TokenCategory.tokEOL, (int)'\n'));
                        // we are here <"author"> <:> <[> >here< <EOL> <Author> <,> <EOL> <ExistingAuthor> <EOL> <]> <,> <EOL>
                    }
                    else
                    {
                        bar.expectToken(p.valueToAdd, TokenCategory.String);
                        bar.expectToken(",", TokenCategory.Token);
                        CheckForEOLorThrow(bar);
                    }
                }
                else
                {
                    p.IndentationHack = p.IndentationHack + "    "; // yep I just did that....


                    // already used bar.expectToken("[", TokenCategory.Token);
                    // Remember where this is
                    int oldLineNo        = bar.Curs.LineNo;
                    int oldTokenNo       = bar.Curs.TokNo;
                    int NumOfIdentifiers = 0;
                    //We are here <"provides"> <:> <[> here <EOL>
                    bar.expectToken(TokenCategory.tokEOL); // We require one of these here. We fail if its missing.
                                                           // AKA this file is, for us, NOT valid   >>   "provides" : [ ],<EOL><<  as we require an EOL after the <[>
                    bar.skipEOL();                         // skips all EOLS and WS // be generous ?
                    while (true)
                    {
                        if (bar.isTokenTextUse("]"))
                        {
                            // We are here <"provides"> <:> <[>  <]> >here<
                            // or  possibly if <"provides"> <:> <[> ... <,>  <]> >here< if it passed validation phase
                            // we are NOT <"provides"> <:> <[> <name> <> <:> <value> [ <,> <name> <> <:> <value>]  <]> >here<
                            // the array list is either empty [] or comma  [ ... , ] terminated ...
                            // See ###ZZZ1 below
                            break;
                        }
                        // is it the end of the list
                        // rmember any indentation cues that we see.
                        // override the +4 space decison above and mirror any other provides?
                        if (p.IndentationHack.Length < bar.Curs.TokenObj.WhiteSpace.Length)
                        {   // But only if it is more indented than the earlier decision
                            // AKA we willindent 4 more spaces than provides and ignore the others...
                            // >>    "provides":[<EOL><<
                            // >> "Thing1Provided", "Thing2Provided" <EOL> <<
                            p.IndentationHack = bar.Curs.TokenObj.WhiteSpace;
                        }

                        // is it the one we are after?
                        if (bar.Curs.TokenObj.theToken.Equals(p.valueToAdd))
                        {
                            //We are here <"provides"> <:> <[> ... >here< <OldIdentifier> ... <]>
                            hasOldIdentifierProvided = true;
                        }
                        // Nope it some other thing that the mod  "provides"

                        bar.expectToken(TokenCategory.String); // skip it eitehr way
                        NumOfIdentifiers++;                    //count them
                        bar.skipEOL();                         // skips all EOLS and WS
                        if (bar.Curs.TokenObj.theToken.Equals(",") == false)
                        {                                      // So now we add our value for provides
                            // Failing this epctationThis probably should not have passed valdiation so it cant happen?
                            bar.expectToken("]", TokenCategory.Token);
                            bar.skipEOL(); // skips all EOLS and WS
                            bar.expectToken(",", TokenCategory.Token);
                            CheckForEOLorThrow(bar);
                            // see ###ZZZ1 above
                            // We are here <"provides"> <:> <[> ... <]> >here< <EOL>
                            // Non empty arrays (probably hopefully all the >valid< real one in the wild.) exit here
                            break;
                        }
                        bar.expectToken(",", TokenCategory.Token);
                        // there is (or better be as we were just promsied one). more provides values ...
                        // We are here <"provides"> <:> <[> ... <value> <,> >here< <value>
                        bar.skipEOL(); // skips all EOLS and WS
                    }
                    p.EndProvidesLineNo  = bar.Curs.LineNo;
                    p.EndProvidesTokenNo = bar.Curs.TokNo;
                    // we check for the EOL here a bit later.

                    if (hasOldIdentifierProvided == false)
                    {  // So now we go back and insert OldIdentifier at the front of the list/array of values
                        bar.Curs.setPosition(oldLineNo, oldTokenNo);
                        // as previously established.
                        // Cursor is at an EOL *and* it is the last token on a Line in File.
                        // insert our extra provides value here
                        /////////////////////////////////////////////////////////////////////
                        //   WARNING DANGER DANGER WILL ROBINSON.
                        /////////////////////////////////////////////////////////////////////
                        // After this operation the LINE object will have multiple output lines that it prints due to an <EOL> in the middle
                        // That is never true just after reading a file, and unless we edit it like this, never at all.
                        /////////////////////////////////////////////////////////////////////
                        // We are here <"provides"> <:> <[> ... <]> >here< <EOL>
                        // Which is cool so if we append our Stuff to the line object and end in an EOL too then its been inserted in the file
                        //  **AND**
                        // Every LineNo TokenNo pair that exists still points to the exact same stuff/token... as before the edit/insert

                        // add this <"provides"> <:> <[> >here< <EOL> <wsIndetation/OldIdentifier> [<,>] <EOL>
                        // where the optional <,> is added only if there is  follwing identifier in the array.
                        CheckForEOLorThrow(bar); // out of an abundance of caution. Check again.
                        TokenFile.Line L = bar.Curs.Line;
                        L.TheLine.Add(new TokenFile.TokenObject(p.IndentationHack, p.valueToAdd, TokenCategory.String, (int)'"'));
                        if (NumOfIdentifiers > 0)
                        {
                            //  it was NOT <"provides"> <:> <[> <]> it did have at ldast one idetifier, so we need a comma
                            L.TheLine.Add(new TokenFile.TokenObject("", ",", TokenCategory.Token, (int)','));
                        }
                        // reuse whatever whites space it is that follows the cursor: see CheckForEOLorThrow above
                        L.TheLine.Add(new TokenFile.TokenObject(bar.Curs.TokenObj.WhiteSpace, "", TokenCategory.tokEOL, (int)'\n'));
                        // Line Object Now looks like this <"provides"> <:> <[> >here< <EOL> <wsIndetation/OldIdentifier> [<,>] <EOL>
                        // The comma is present if and only if there is another preexisting identifier in the Provides list
                    }
                    bar.Curs.setPosition(p.EndProvidesLineNo, p.EndProvidesTokenNo);
                    // File Now looks like this <"provides"> <:> <[> <EOL> <wsIndetation/OldIdentifier> [<,>] <EOL> ... <]> >here< <EOL>
                }
            }  // well that was fun
            else
            {  // Now for the easy more usual alternative There no existing provides name value insert one after license.
                // or insert author after abstract if we doing author
                if (bar.hasANameField(p.thingToAdditAfter[0]) == false)
                {
                    //Force an error to throw
                    bar.expectToken(p.thingToAdditAfter[0], TokenCategory.Token);
                    throw new FormatException("Programmer Error: This really cant (logically) happen.");
                }

                // This is a required by the schema field... so it better be there or who cares?
                // find where that nameField is move to the { that is just after it and the ":".
                string t = p.listToAddItTo;
                p.listToAddItTo = p.thingToAdditAfter[0];
                parseToValueFor(bar, ref p);                // find that name:value instead
                p.listToAddItTo   = t;
                p.IndentationHack = p.IndentationHack + ""; // yep, oops I just did that.... again
                // like <"author"> The schema specifies either a single value or an array for license (humans: see licenses at bottom of schema)
                //
                if (bar.isTokenTextUse("["))
                {
                    // already used bar.expectToken("[", TokenCategory.Token);
                    while (bar.Curs.TokenObj.theToken.Equals("]") == false)
                    {   // Accept anythign we find until we get one ]....
                        bar.expectToken(bar.Curs.TokenObj.theToken, bar.Curs.TokenObj.TokenCategory);
                    }
                    bar.expectToken("]", TokenCategory.Token);
                }
                else
                { // if is not a <[> it needs to be a single license string (or a single string abstract)
                    bar.expectToken(TokenCategory.String);
                }
                // Now we require a <,> after that name:value pair followed by an <EOL>
                bar.skipEOL();
                bar.expectToken(",", TokenCategory.Token);
                CheckForEOLorThrow(bar);
                // We are here <"license"> <:> {one_of String or Array} >here< <EOL>   (or equiv but for <"abstract">)
                // Now we add  <IndentationHack:"provides"> <:> <OldIdentifier> <,> <EOL>
                TokenFile.Line L = bar.Curs.Line;
                L.TheLine.Add(new TokenFile.TokenObject(p.IndentationHack, p.listToAddItTo, TokenCategory.String, (int)'"'));
                L.TheLine.Add(new TokenFile.TokenObject("", ":", TokenCategory.Token, (int)':'));
                L.TheLine.Add(new TokenFile.TokenObject(" ", "[", TokenCategory.Token, (int)'['));
                L.TheLine.Add(new TokenFile.TokenObject(bar.Curs.TokenObj.WhiteSpace, "", TokenCategory.tokEOL, (int)'\n'));
                L.TheLine.Add(new TokenFile.TokenObject(p.IndentationHack + "    ", p.valueToAdd, TokenCategory.String, (int)'"'));
                L.TheLine.Add(new TokenFile.TokenObject(bar.Curs.TokenObj.WhiteSpace, "", TokenCategory.tokEOL, (int)'\n'));
                L.TheLine.Add(new TokenFile.TokenObject(p.IndentationHack, "]", TokenCategory.Token, (int)']'));
                L.TheLine.Add(new TokenFile.TokenObject("", ",", TokenCategory.Token, (int)','));
                // reuse whatever whites space it is that follows the cursor: see CheckForEOLorThrow above
                L.TheLine.Add(new TokenFile.TokenObject(bar.Curs.TokenObj.WhiteSpace, "", TokenCategory.tokEOL, (int)'\n'));

                /////////////////////////////////////////////////////////////////////
                //   WARNING DANGER DANGER WILL ROBINSON.
                //   read carefully.
                /////////////////////////////////////////////////////////////////////
                p.EndProvidesLineNo  = bar.Curs.LineNo;
                p.EndProvidesTokenNo = L.TheLine.Count - 1; // Yep as we just added the EOL after "provides" we know right where we put it.
                bar.Curs.setPosition(p.EndProvidesLineNo, p.EndProvidesTokenNo);
            }
        }
        // static string[] NameFields = { "\"spec_version\"", "\"identifier\"", "\"name\"", "\"author\"", "\"version\"",
        //     "\"ksp_version_min\"", "\"ksp_version_max\"", "\"license\"", "\"provides\"", "\"conflicts\"" };
        /// <summary>
        /// This function validates the localises one specified input ckan File and generates/overwrites one output ckanfile
        /// CkanLocaliser Localise  AbsPathToMod SrcFile.ckan  DestFile.ckan [options].
        /// </summary>
        static public int Localise(string[] args)
        {
            CkanLocaliserClass.DoingWhat.Push("Localise");

            // Validate command line Parameters.
            if (args.Length < 4)
            {
                Usagesmsg();
                return(-1);
            }
            if (true)
            {
                using (StreamWriter sw = File.CreateText(args[3]))
                {   // This makes sure that if we can to avoid confusion the outfile does in no sense look valid
                    sw.WriteLine(" { Localise error } ");
                    sw.Close();
                }
            }


            if (!args[1].EndsWith(".zip"))
            {
                Console.Error.WriteLine("ModFile MUSTR be a zip File");
                Usagesmsg();
                return(-1);
            }
            DownloadPath = args[1];
            if (DoAWhiteBoxtest)
            {   // This allows test code to skip having an actual binary zip file.
                DownloadSize = 9999;
                SHA1         = "F550FBBEF92224DD32E1E0D045BAA8C95EF45343";
                SHA256       = "D51D322B8F68FCDE25FCF4334327C0B86AAE0A65E9B2B70B1B03ECC7761AFD2C";
            }
            else
            {
                FileInfo fi1 = new FileInfo(DownloadPath);
                if (!fi1.Exists)
                {
                    Console.Error.WriteLine("ModFile Must exist");
                    Usagesmsg();
                    return(-1);
                }
                if (!Path.IsPathRooted(DownloadPath))
                {
                    throw new FormatException($"Fatal Error: DownLoad path must be an absolute rooted path \"{DownloadPath}\" is not.", true);
                }
                DownloadSize = fi1.Length;

                SHA1   = GetFileHashSha1(DownloadPath);
                SHA256 = GetFileHashSha256(DownloadPath);
            }

            FileInfo fi2 = new FileInfo(args[2]);

            if (!fi2.Exists)
            {
                Console.Error.WriteLine("SrcFile Must exist");
                Console.Error.WriteLine(" PWD is :" + Directory.GetCurrentDirectory());
                Usagesmsg();
                return(-1);
            }
            // Validate Ckan File

            CkanTokeniser CT = new CkanTokeniser(args[2]);
            // CT.AllowSlashN = true;
            TokenFile Foo = new TokenFile(CT);

            Foo.parse();
            CKanFormat bar = new CKanFormat(Foo);

            bar.AllowEOLs = true;

            if (!bar.validation())
            {
                string DL = "\n====================================\n";
                Console.Error.WriteLine($"\n\n{DL}### Validation Error: Parsing Failure in File: <{args[2]}> \n Context in which That happened {DL}");
                bar.validation(true);
                return(-1);
            }
            // do The localisation and write the file
            try
            {   // First tag all three common human readable Fields to identify That we are localised
                CkanLocaliserClass.DoingWhat.Push("RewritingFields");
                parseToValueFor(bar, "\"identifier\"");
                OldIdentifier = bar.Curs.TokenObj.theToken;                                                 // Preserve what it was Called We will need that later
                bar.Curs.TokenObj.theToken = "\"" + Prefix + OldIdentifier.Substring(1);                    // prepend the Localising prefix
                                                                                                            //                Console.WriteLine(Curs.TokenObj.theToken);
                parseToValueFor(bar, "\"name\"");
                bar.Curs.TokenObj.theToken = "\"" + Prefix + ":" + bar.Curs.TokenObj.theToken.Substring(1); // prepend the Localising prefix

                parseToValueFor(bar, "\"abstract\"");
                bar.Curs.TokenObj.theToken = "\"" + Prefix + ":" + bar.Curs.TokenObj.theToken.Substring(1); // prepend the Localising prefix

                // Now Replace download
                parseToValueFor(bar, "\"download\"");
                System.Uri DldURI = new System.Uri(DownloadPath);              // Convert it to a URI
                bar.Curs.TokenObj.theToken = "\"" + DldURI.AbsoluteUri + "\""; // replace the URI

                // Now Replace download_Size
                parseToValueFor(bar, "\"download_size\"");
                bar.Curs.TokenObj.theToken = DownloadSize.ToString();

                // Now Replace download_Size
                if (bar.hasANameField("\"x_generated_by\"") == true)
                {
                    parseToValueFor(bar, "\"x_generated_by\"");
                    bar.Curs.TokenObj.theToken = "\"CkanLocaliser\"";
                }
                // Now for the not so easy bits  Values in CompoundTypes.
                CkanLocaliserClass.DoingWhat.Pop();
                CkanLocaliserClass.DoingWhat.Push("Wiping_Resoruces_Links");
                killResourcesLinks(bar);

                CkanLocaliserClass.DoingWhat.Pop();
                CkanLocaliserClass.DoingWhat.Push("Fixing_Hashes");
                fixHashValues(bar);
                // Now for the hard bits
                // done backwards through the file.. just to feel safer
                CkanLocaliserClass.DoingWhat.Pop();

                // Insert "Conflicts" preceded by "Provides" just after "license"
                InsertProvidesConflicts(bar);
                // Insert an author either makign the string into an array and/or prepending our Author string

                CkanLocaliserClass.DoingWhat.Push("Adding_Author");

                PieceOfPaper p = new PieceOfPaper();
                p.valueToAdd        = Stringify(Author);
                p.listToAddItTo     = "\"author\"";
                p.thingToAdditAfter = new List <string>();
                p.thingToAdditAfter.Add("\"abstract\"");
                p.EndProvidesLineNo  = 0;
                p.EndProvidesTokenNo = 0;
                fixValueInList(bar, ref p);

                CkanLocaliserClass.DoingWhat.Pop();
            }
            catch (FormatException fe)
            {
                if (fe.KnownReal == false)
                {   // EG: we know it is real if we try to localise ckan file and it has no "identifier" Name:Value pair   etc.
                    Console.WriteLine("CAVEAT: There is reasonably good chance th following is a code error.");
                    Console.WriteLine("CAVEAT: The code has been mainly/entirely checking things we thoguht we just checked already.");
                    Console.WriteLine("CAVEAT: ALL examples of ckan files (schema valid or not) that do this when localised gratefully appreciated.");
                }
                Console.WriteLine($"While Doing : {CkanLocaliserClass.WhatString()} ");
                Console.WriteLine(fe.Cause);

                return(-1);
            }
            if (true)
            {
                using (StreamWriter sw = File.CreateText(args[3]))
                {
                    Foo.writeTo(sw);
                    sw.Close();
                }
            }
            CkanLocaliserClass.DoingWhat.Pop();

            return(0);
        }