-- Topal: GPG/GnuPG and Alpine/Pine integration
-- Copyright (C) 2001--2022  Phillip J. Brooke
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License version 3 as
-- published by the Free Software Foundation.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

with Ada.IO_Exceptions;
with Ada.Strings.Fixed;
with Ada.Text_IO;
with Externals.Simple;
with Keys;
with Menus;              use Menus;
with Misc;
with Readline;

package body Configuration is

   Binary_Name : constant array(Binaries) of UBS :=
     (Chmod => ToUBS("chmod"),
      Clear => ToUBS("clear"),
      Cut => ToUBS("cut"),
      Date => ToUBS("date"),
      Diff => ToUBS("diff"),
      File => ToUBS("file"),
      Formail => ToUBS("formail"),
      GPGOP => ToUBS("gpg"),
      GPGSM => ToUBS("gpgsm"),
      Grep => ToUBS("grep"),
      Iconv => ToUBS("iconv"),
      Less => ToUBS("less"),
      Locale => ToUBS("locale"),
      Md5sum => ToUBS("md5sum"),
      Metamail => ToUBS("metamail"),
      Mimeconstruct => ToUBS("mimeconstruct"),
      Mimetool => ToUBS("mimetool"),
      Mkdir => ToUBS("mkdir"),
      Mkfifo => ToUBS("mkfifo"),
      Mv => ToUBS("mv"),
      Openssl => ToUBS("openssl"),
      Rm => ToUBS("rm"),
      Runmailcap => ToUBS("runmailcap"),
      Scp => ToUBS("scp"),
      Sed => ToUBS("sed"),
      Ssh => ToUBS("ssh"),
      Stty => ToUBS("stty"),
      Tee => ToUBS("tee"),
      Test => ToUBS("test"));

   UBS_Opts_Names : constant array (UBS_Opts_Keys) of UBS :=
     (My_Key => ToUBS("my-key"),
      GPG_Options=> ToUBS("gpg-options"),
      GPGSM_Options=> ToUBS("gpgsm-options"),
      General_Options => ToUBS("general-options"),
      Receiving_Options=> ToUBS("receiving-options"),
      Sending_Options => ToUBS("sending-options"),
      Colour_Menu_Title => ToUBS("colour-menu-title"),
      Colour_Menu_Key => ToUBS("colour-menu-key"),
      Colour_Menu_Choice => ToUBS("colour-menu-choice"),
      Colour_Important => ToUBS("colour-important"),
      Colour_Banner => ToUBS("colour-banner"),
      Colour_Info => ToUBS("colour-info"),
      Decrypt_Prereq => ToUBS("decrypt-prereq"));

   Positive_Opts_Names : constant array (Positive_Opts_Keys) of UBS :=
     (Decrypt_Not_Cached => ToUBS("decrypt-not-cached"),
      Decrypt_Not_Cached_Use_Cache => ToUBS("decrypt-not-cached-use-cache"),
      Decrypt_Cached => ToUBS("decrypt-cached"),
      Decrypt_Cached_Use_Cache => ToUBS("decrypt-cached-use-cache"),
      Verify_Not_Cached => ToUBS("verify-not-cached"),
      Verify_Not_Cached_Use_Cache => ToUBS("verify-not-cached-use-cache"),
      Verify_Cached => ToUBS("verify-cached"),
      Verify_Cached_Use_Cache => ToUBS("verify-cached-use-cache"),
      Mime_Viewer_Decrypt => ToUBS("mime-viewer-decrypt"),
      Mime_Viewer_Verify => ToUBS("mime-viewer-verify"),
      Use_Agent => ToUBS("use-agent"),
      Replace_IDs => ToUBS("replace-ids"),
      Include_Send_Token => ToUBS("include-send-token"));

   Boolean_Opts_Names : constant array (Boolean_Opts_Keys) of UBS :=
      (Decrypt_Cached_Fast_Continue => ToUBS("decrypt-cached-fast-continue"),
       Verify_Cached_Fast_Continue => ToUBS("verify-cached-fast-continue"),
       Verify_Not_Cached_Fast_Continue => ToUBS("verify-not-cached-fast-continue"),
       FE_Simple => ToUBS("fe-simple"),
       Inline_Separate_Output => ToUBS("inline-separate-output"),
       Omit_Inline_Disposition_Name => ToUBS("omit-inline-disposition-name"),
       Omit_Inline_Disposition_Header => ToUBS("omit-inline-disposition-header"),
       Save_On_Send => ToUBS("save-on-send"),
       ANSI_Terminal => ToUBS("ansi-terminal"),
       Wait_If_Missing_Keys => ToUBS("wait-if-missing-keys"),
       Attachment_Trap => ToUBS("attachment-trap"),
       Fix_Fcc => ToUBS("fix-fcc"),
       Fix_Bcc => ToUBS("fix-bcc"),
       Debug => ToUBS("debug"),
       -- The following ones don't really matter, since they won't be used.
       No_Clean => ToUBS("no-clean"),
       All_Headers => ToUBS("all-headers"),
       Read_From => ToUBS("read-from"),
       Ask_Charset => ToUBS("ask-charset"));

   procedure Parse_AKE (KE   : in  UBS;
                        Key   : out UBS;
                        Email  : out UBS) is
      KES : constant String := ToStr(KE);
      Sep : Natural;
   begin
      Sep := Ada.Strings.Fixed.Index(Source => KES,
                                     Pattern => ",");
      Key := ToUBS(KES(KES'First..Sep-1));
      Email := ToUBS(KES(Sep+1..KES'Last));
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Configuration.Parse_AKE");
         raise;
   end Parse_AKE;

   procedure Parse_Config_Line (Line   : in  String;
                                Name   : out UBS;
                                Value  : out UBS) is
      Sep : Natural;
   begin
      Sep := Ada.Strings.Fixed.Index(Source => Line,
                                     Pattern => "=");
      Name := ToUBS(Line(Line'First..Sep-1));
      Value := ToUBS(Line(Sep+1..Line'Last));
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Configuration.Parse_Config_Line");
         raise;
   end Parse_Config_Line;

   function Set_Two_Way (S : String) return Boolean is
   begin
      if S = "on" then
         return True;
      elsif S = "off" then
         return False;
      else
         raise Switch_Parse_Error;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Configuration.Set_Two_Way");
         raise;
   end Set_Two_Way;

   function Match_Binary_Path (N : String) return Boolean is
   begin
      for I in Binaries loop
         if ToStr(Binary_Name(I)) & "-binary" = N then
            return True;
         end if;
      end loop;
      return False;
   end Match_Binary_Path;

   No_Binary_Match : exception;
   function Get_Binary_Path (N : String) return Binaries is
   begin
      for I in Binaries loop
         if ToStr(Binary_Name(I)) & "-binary" = N then
            return I;
         end if;
      end loop;
      raise No_Binary_Match;
   end Get_Binary_Path;

   function Match_UBS_Opts_Key (N : String) return Boolean is
   begin
      for I in UBS_Opts_Keys loop
         if ToStr(UBS_Opts_Names(I)) = N then
            return True;
         end if;
      end loop;
      return False;
   end Match_UBS_Opts_Key;

   No_UBS_Match : exception;
   function Get_UBS_Opts_Key (N : String) return UBS_Opts_Keys is
   begin
      for I in UBS_Opts_Keys loop
         if ToStr(UBS_Opts_Names(I)) = N then
            return I;
         end if;
      end loop;
      raise No_UBS_Match;
   end Get_UBS_Opts_Key;

   function Match_Positive_Opts_Key (N : String) return Boolean is
   begin
      for I in Positive_Opts_Keys loop
         if ToStr(Positive_Opts_Names(I)) = N then
            return True;
         end if;
      end loop;
      return False;
   end Match_Positive_Opts_Key;

   No_Positive_Match : exception;
   function Get_Positive_Opts_Key (N : String) return Positive_Opts_Keys is
   begin
      for I in Positive_Opts_Keys loop
         if ToStr(Positive_Opts_Names(I)) = N then
            return I;
         end if;
      end loop;
      raise No_Positive_Match;
   end Get_Positive_Opts_Key;

   function Match_Boolean_Opts_Key (N : String) return Boolean is
   begin
      for I in Boolean_Opts_Keys_Save loop
         if ToStr(Boolean_Opts_Names(I)) = N then
            return True;
         end if;
      end loop;
      return False;
   end Match_Boolean_Opts_Key;

   No_Boolean_Match : exception;
   function Get_Boolean_Opts_Key (N : String) return Boolean_Opts_Keys is
   begin
      for I in Boolean_Opts_Keys_Save loop
         if ToStr(Boolean_Opts_Names(I)) = N then
            return I;
         end if;
      end loop;
      raise No_Boolean_Match;
   end Get_Boolean_Opts_Key;

   procedure Read_Config_File (Warnings : out Boolean) Is
      use Ada.Text_IO;
      use Misc;
      CF : File_Type;
   begin
      Warnings := False;
      Open(File => CF,
           Mode => In_File,
           Name => ToStr(Topal_Directory) & "/config");
  Config_Loop:
      loop
         begin
            declare
               The_Line : constant String
                 := ToStr(Misc.Unbounded_Get_Line(CF));
               Name     : UBS;
               Value    : UBS;
	       K, E     : UBS;
            begin
               if The_Line'Length > 0
                 and then The_Line(The_Line'First) = '#' then
                  -- It's a comment.  Ignore it.
                  null;
               else
                  Parse_Config_Line(The_Line, Name, Value);
                  declare
                     N : constant String := ToStr(Name);
                     V : UBS renames Value;
                  begin
                     if Match_Binary_Path(N) then
                        Config.Binary(Get_Binary_Path(N)) := V;
                     elsif Match_UBS_Opts_Key(N) then
                        Config.UBS_Opts(Get_UBS_Opts_Key(N)) := V;
                     elsif Match_Positive_Opts_Key(N) then
                        Config.Positive_Opts(Get_Positive_Opts_Key(N))
                          := String_To_Integer(V);
                     elsif Match_Boolean_Opts_Key(N) then
                        Config.Boolean_Opts(Get_Boolean_Opts_Key(N))
                          := Set_Two_Way(ToStr(V));
                     elsif N = "ake" then
			Parse_AKE(V, K, E);
			Config.AKE.Append(UPC(K, E));
                     elsif N = "xk" then
                        -- This is a key to be excluded.
			Config.XK.Append(V);
                     elsif N = "sake" then
			Parse_AKE(V, K, E);
			Config.SAKE.Append(UP'(K, E));
                     elsif N = "sxk" then
			Config.SXK.Append(V);
                     elsif N = "sd" then
			Parse_AKE(V, K, E);
			Config.SD.Append(UP'(K, E));
                     elsif N = "sc" then
			Parse_AKE(V, K, E);
			Config.SC.Append(UP'(K, E));
                     elsif N = "st" then
			Parse_AKE(V, K, E);
			Config.ST.Append(UP'(K, E));
                     else
                        Put_Line(Ada.Text_IO.Standard_Error,
				 "WARNING: Bogus line in configuration file: " & The_Line);
                        Warnings := True;
                     end if;
                  end;
               end if;
            end;
         exception
            when Ada.IO_Exceptions.End_Error =>
               exit Config_Loop;
         end;
      end loop Config_Loop;
      Close(CF);
   exception
      when Ada.IO_Exceptions.Name_Error =>
         -- Silently ignore the lack of a config file.
         Close(CF);
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Configuration.Read_Config_File");
         raise;
   end Read_Config_File;

   -- Given the current value (V) and default config (D), Put_Line if
   --  different or make it a comment if the same.
   procedure Dump_Bin (K : in String;
                       V : in UBS;
                       D : in UBS) is
      use type UBS;
   begin
      if V = D then
         Ada.Text_IO.Put("#");
      end if;
      Ada.Text_IO.Put_Line(K & "-binary=" & ToStr(V));
   end Dump_Bin;

   procedure Dump (Overwrite_Config : in Boolean := False) is
      use Ada.Text_IO;
      use Misc;
      F : File_Type;
      Default_Config : Config_Record;
   begin
      if Overwrite_Config then
         if Externals.Simple.Test_F(ToStr(Topal_Directory) & "/config") then
            Externals.Simple.Mv_F(ToStr(Topal_Directory) & "/config",
                                  ToStr(Topal_Directory) & "/config.bak");
         end if;
         Create(File => F,
                Mode => Out_File,
                Name => ToStr(Topal_Directory) & "/config");
         Set_Output(F);
      end if;
      Default_Configuration(Default_Config);
      for I in Binaries loop
         Dump_Bin(ToStr(Binary_Name(I)), Config.Binary(I), Default_Config.Binary(I));
      end loop;
      for I in UBS_Opts_Keys loop
         Put_Line(ToStr(UBS_Opts_Names(I))
                    & "="
                    & ToStr(Config.UBS_Opts(I)));
      end loop;
      for I in Positive_Opts_Keys loop
         Put_Line(ToStr(Positive_Opts_Names(I))
                    & "="
                    & Trim_Leading_Spaces(Integer'Image(Config.Positive_Opts(I))));
      end loop;
      for I in Boolean_Opts_Keys_Save loop
         Put_Line(ToStr(Boolean_Opts_Names(I))
                    & "="
                    & ToStr(Two_Way(Config.Boolean_Opts(I))));
      end loop;
      for I in 1 .. Integer(Config.AKE.Length) loop
         Put_Line("ake="
                  & ToStr(Config.AKE.Element(I).Key)
                  & ","
                  & ToStr(Config.AKE.Element(I).Value));
      end loop;
      for I in 1 .. Integer(Config.XK.Length) loop
         Put_Line("xk="
                  & ToStr(Config.XK.Element(I)));
      end loop;
      for I in 1 .. Integer(Config.SAKE.Length) loop
         Put_Line("sake="
                  & ToStr(Config.SAKE.Element(I).Key)
                  & ","
                  & ToStr(Config.SAKE.Element(I).Value));
      end loop;
      for I in 1 .. Integer(Config.SXK.Length) loop
         Put_Line("sxk="
                  & ToStr(Config.SXK.Element(I)));
      end loop;
      for I in 1 .. Integer(Config.SD.Length) loop
         Put_Line("sd="
                  & ToStr(Config.SD.Element(I).Key)
                  & ","
                  & ToStr(Config.SD.Element(I).Value));
      end loop;
      for I in 1 .. Integer(Config.SC.Length) loop
         Put_Line("sc="
                  & ToStr(Config.SC.Element(I).Key)
                  & ","
                  & ToStr(Config.SC.Element(I).Value));
      end loop;
      for I in 1 .. Integer(Config.ST.Length) loop
         Put_Line("st="
                  & ToStr(Config.ST.Element(I).Key)
                  & ","
                  & ToStr(Config.ST.Element(I).Value));
      end loop;
      if Overwrite_Config then
         Set_Output(Standard_Output);
         Close(F);
      end if;
   exception
      when others =>
         Set_Output(Standard_Output);
         Put_Line("Problem in Configuration.Dump, probably file related");
         Close(F);
         raise;
   end Dump;

   procedure Edit_Own_Key(SMIME : in Boolean) is
      use Ada.Text_IO;
   begin
      New_Line(2);
      Put_Line("Signing key is currently: "
               & ToStr(Config.UBS_Opts(My_Key)));
      Put_Line("Do you want to replace this key?");
      Put_Line("Answer Yes to replace it; No to keep the current key.");
      if YN_Menu = Yes then
         -- Now, find all secret keys and offer them as a `select 1' list.
         declare
            SKL            : Keys.Key_List;
            Pattern        : UBS;
            New_Secret_Key : UBS;
            Use_Old_Key    : Boolean;
         begin
            Keys.Empty_Keylist(SKL, SMIME);
            Pattern := ToUBS(Readline.Get_String("Type GPG search pattern: "));
            Keys.Add_Secret_Keys(Pattern, SKL, Keys.Any);
            if Keys.Count(SKL) = 0 then
               Put_Line("No matching secret keys found!");
            else
               Keys.Select_Key_From_List(SKL, New_Secret_Key, Use_Old_Key);
               if not Use_Old_Key then
                  Config.UBS_Opts(My_Key) := New_Secret_Key;
               end if;
            end if;
         end;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Configuration.Edit_Own_Key");
         raise;
   end Edit_Own_Key;

   procedure Edit_Configuration is
      use Ada.Text_IO;
      use Misc;
      Default_Config : Config_Record;
      Three_Str : constant array (Integer range 1..3) of UBS
        := (1 => ToUBS("Always"),
            2 => ToUBS("Ask"),
            3 => ToUBS("Never"));
      Five_Str : constant array (Integer range 1..5) of UBS
        := (1 => ToUBS("Use"),
            2 => ToUBS("Replace"),
            3 => ToUBS("Ask-replace"),
            4 => ToUBS("Ignore cache"),
            5 => ToUBS("Offer all"));
      MV_Str : constant array (Integer range 1..5) of UBS
        := (1 => ToUBS("Ask"),
            2 => ToUBS("Metamail"),
            3 => ToUBS("Run-Mailcap"),
            4 => ToUBS("Save"),
            5 => ToUBS("Skip"));
      UGA_Str: constant array (Integer range 1..3) of UBS
        := (1 => ToUBS("Never"),
            2 => ToUBS("Decrypt only"),
            3 => ToUBS("Always"));
      Three_Values : constant array (Three_Way_Index) of Integer
        := (Always => 1,
            Ask    => 2,
            Never  => 3);
      Five_Values : constant array (Five_Way_Index) of Integer
        := (UUse        => 1,
            Replace     => 2,
            AskReplace  => 3,
            IgnoreCache => 4,
            OfferAll    => 5);
      Selection   : Edit_Index;
      Selection2O : Options_Index;
      Selection2S : Settings_Index;
   begin
      Default_Configuration(Default_Config);
  Edit_Loop:
      loop
         Ada.Text_IO.New_Line(5);
         Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Menu_Title))
                                &  "** Configuration menu:"
                                & Reset_SGR);
         Put_Line("Configuration:             Debugging currently "
                  & ToStr(Two_Way(Config.Boolean_Opts(Debug))));
         Put_Line("                           Inline separate output currently "
                  & ToStr(Two_Way(Config.Boolean_Opts(Inline_Separate_Output))));
         Put_Line("                           Omit inline disposition name currently "
                  & ToStr(Two_Way(Config.Boolean_Opts(Omit_Inline_Disposition_Name))));
         Put_Line("                           Omit inline disposition header currently "
                  & ToStr(Two_Way(Config.Boolean_Opts(Omit_Inline_Disposition_Header))));
         Put_Line("                           Save on send currently "
                  & ToStr(Two_Way(Config.Boolean_Opts(Save_On_Send))));
         Put_Line("                           Wait if missing keys currently "
                  & ToStr(Two_Way(Config.Boolean_Opts(Wait_If_Missing_Keys))));
         Put_Line("                           MIME viewer for decrypting is "
                  & ToStr(MV_Str(Config.Positive_Opts(MIME_Viewer_Decrypt))));
         Put_Line("                           MIME viewer for verifying is "
                  & ToStr(MV_Str(Config.Positive_Opts(MIME_Viewer_Verify))));
         Put_Line("                           Use GPG agent is "
                  & ToStr(UGA_Str(Config.Positive_Opts(Use_Agent))));
         Put_Line("Configuration is not saved beyond this session unless explicitly saved");
         Selection := Edit_Menu;
         case Selection is
            when Quit => -- Quit editing.
               New_Line(3);
               exit Edit_Loop;
            when Save => -- Save changes.
               Dump(Overwrite_Config => True);
               Put_Line("Configuration saved");
               New_Line(3);
            when Binary_Paths => -- Binary paths.
               New_Line(3);
               Put_Line("Currently not implemented.  Use topal -dump, then edit the configuration file.");
               New_Line(3);
            when GPG_Options => -- GPG options.
               Options_Loop:
               loop
               Put_Line("Options submenu (all relevant options are concatenated together):");
               Put_Line("  GPG options (all GPG operations): "
                        & NL & "    " & ToStr(Config.UBS_Opts(GPG_Options)));
               Put_Line("  General options (send & receive): "
                        & NL & "    " & ToStr(Config.UBS_Opts(General_Options)));
               Put_Line("  Receiving options (receive only): "
                        & NL & "    " & ToStr(Config.UBS_Opts(Receiving_Options)));
               Put_Line("  Sending options (send only): "
                        & NL & "    " & ToStr(Config.UBS_Opts(Sending_Options)));
               Selection2O := Options_Menu;
               case Selection2O is
                  when Quit =>
                     New_Line(3);
                     exit Options_Loop;
                  when Change_General =>
                     Readline.Add_History(ToStr(Config.UBS_Opts(General_Options)));
                     Config.UBS_Opts(General_Options) := ToUBS(Readline.Get_String("General options: "));
                  when Change_GPG =>
                     Readline.Add_History(ToStr(Config.UBS_Opts(GPG_Options)));
                     Config.UBS_Opts(GPG_Options) := ToUBS(Readline.Get_String("GPG options: "));
                  when Change_Receiving =>
                     Readline.Add_History(ToStr(Config.UBS_Opts(Receiving_Options)));
                     Config.UBS_Opts(Receiving_Options) := ToUBS(Readline.Get_String("Receiving options: "));
                  when Change_Sending =>
                     Readline.Add_History(ToStr(Config.UBS_Opts(Sending_Options)));
                     Config.UBS_Opts(Sending_Options) := ToUBS(Readline.Get_String("Sending options: "));
                  when Default_General =>
                     Config.UBS_Opts(General_Options) := Default_Config.UBS_Opts(General_Options);
                  when Default_GPG =>
                     Config.UBS_Opts(GPG_Options) := Default_Config.UBS_Opts(GPG_Options);
                  when Default_Receiving =>
                     Config.UBS_Opts(Receiving_Options) := Default_Config.UBS_Opts(Receiving_Options);
                  when Default_Sending =>
                     Config.UBS_Opts(Sending_Options) := Default_Config.UBS_Opts(Sending_Options);
                  when Default_All =>
                     Config.UBS_Opts(General_Options) := Default_Config.UBS_Opts(General_Options);
                     Config.UBS_Opts(GPG_Options) := Default_Config.UBS_Opts(GPG_Options);
                     Config.UBS_Opts(Receiving_Options) := Default_Config.UBS_Opts(Receiving_Options);
                     Config.UBS_Opts(Sending_Options) := Default_Config.UBS_Opts(Sending_Options);
               end case;
               end loop Options_Loop;
            when DV_Settings => -- Decrypt/verify settings.
               Settings_Loop:
               loop
               Put_Line("Decrypt/Verify settings submenu:");
               Put_Line("  Decrypt/cached:                  " & ToStr(Three_Str(Config.Positive_Opts(Decrypt_Cached))));
               Put_Line("  Decrypt/cached, cache usage:     " & ToStr(Five_Str(Config.Positive_Opts(Decrypt_Cached_Use_Cache))));
               Put_Line("  Decrypt/not cached:              " & ToStr(Three_Str(Config.Positive_Opts(Decrypt_Not_Cached))));
               Put_Line("  Decrypt/not cached, cache usage: " & ToStr(Three_Str(Config.Positive_Opts(Decrypt_Not_Cached_Use_Cache))));
               Put_Line("  Verify/cached:                   " & ToStr(Three_Str(Config.Positive_Opts(Verify_Cached))));
               Put_Line("  Verify/cached, cache usage:      " & ToStr(Five_Str(Config.Positive_Opts(Verify_Cached_Use_Cache))));
               Put_Line("  Verify/not cached:               " & ToStr(Three_Str(Config.Positive_Opts(Verify_Not_Cached))));
               Put_Line("  Verify/not cached, cache usage:  " & ToStr(Three_Str(Config.Positive_Opts(Verify_Not_Cached_Use_Cache))));
                Put_Line("  Decrypt/cached fast continue:    " & ToStr(Two_Way(Config.Boolean_Opts(Decrypt_Cached_Fast_Continue))));
               Put_Line("  Verify/cached fast continue:     " & ToStr(Two_Way(Config.Boolean_Opts(Verify_Cached_Fast_Continue))));
               Put_Line("  Verify/not cached fast continue: " & ToStr(Two_Way(Config.Boolean_Opts(Verify_Not_Cached_Fast_Continue))));
                Selection2S := Settings_Menu;
               case Selection2S is
                  when Decrypt_Cached =>
                     Config.Positive_Opts(Decrypt_Cached) := Three_Values(Three_Way_Menu);
                  when Decrypt_Cached_Use =>
                     Config.Positive_Opts(Decrypt_Cached_Use_Cache) := Five_Values(Five_Way_Menu);
                  when Decrypt_Not_Cached =>
                     Config.Positive_Opts(Decrypt_Not_Cached) := Three_Values(Three_Way_Menu);
                  when Decrypt_Not_Cached_Use =>
                     Config.Positive_Opts(Decrypt_Not_Cached_Use_Cache) := Three_Values(Three_Way_Menu);
                  when Verify_Cached =>
                     Config.Positive_Opts(Verify_Cached) := Three_Values(Three_Way_Menu);
                  when Verify_Cached_Use =>
                     Config.Positive_Opts(Verify_Cached_Use_Cache) := Five_Values(Five_Way_Menu);
                  when Verify_Not_Cached =>
                     Config.Positive_Opts(Verify_Not_Cached) := Three_Values(Three_Way_Menu);
                  when Verify_Not_Cached_Use =>
                     Config.Positive_Opts(Verify_Not_Cached_Use_Cache) := Three_Values(Three_Way_Menu);
                   when Quit =>
                     New_Line(3);
                     exit Settings_Loop;
                  when All_Defaults =>
                     Config.Positive_Opts(Decrypt_Cached) := Default_Config.Positive_Opts(Decrypt_Cached);
                     Config.Positive_Opts(Decrypt_Cached_Use_Cache) := Default_Config.Positive_Opts(Decrypt_Cached_Use_Cache);
                     Config.Positive_Opts(Decrypt_Not_Cached) := Default_Config.Positive_Opts(Decrypt_Not_Cached);
                     Config.Positive_Opts(Decrypt_Not_Cached_Use_Cache) := Default_Config.Positive_Opts(Decrypt_Not_Cached_Use_Cache);
                     Config.Positive_Opts(Verify_Cached) := Default_Config.Positive_Opts(Verify_Cached);
                     Config.Positive_Opts(Verify_Cached_Use_Cache) := Default_Config.Positive_Opts(Verify_Cached_Use_Cache);
                     Config.Positive_Opts(Verify_Not_Cached) := Default_Config.Positive_Opts(Verify_Not_Cached);
                     Config.Positive_Opts(Verify_Not_Cached_Use_Cache) := Default_Config.Positive_Opts(Verify_Not_Cached_Use_Cache);
                   when Toggle_Decrypt_Cached_Fast =>
                     Config.Boolean_Opts(Decrypt_Cached_Fast_Continue) := not Config.Boolean_Opts(Decrypt_Cached_Fast_Continue);
                  when Toggle_Verify_Cached_Fast =>
                     Config.Boolean_Opts(Verify_Cached_Fast_Continue) := not Config.Boolean_Opts(Verify_Cached_Fast_Continue);
                  when Toggle_Verify_Not_Cached_Fast =>
                     Config.Boolean_Opts(Verify_Not_Cached_Fast_Continue) := not Config.Boolean_Opts(Verify_Not_Cached_Fast_Continue);
                end case;
               end loop Settings_Loop;
            when Own_Key => -- Own key.
               Edit_Own_Key(False); -- Only handles OpenPGP key here.
            when Key_Assoc => -- Key associations.
               New_Line(3);
               Put_Line("Currently not implemented.  Use topal -dump, then edit the configuration file.");
               New_Line(3);
            when Key_Excl => -- Key exclusions.
               New_Line(3);
               Put_Line("Currently not implemented.  Use topal -dump, then edit the configuration file.");
               New_Line(3);
            when Set_Defaults => -- Set all defaults.
               Copy_Configuration(Config, Default_Config);
               Put_Line("All configuration settings are now default");
               New_Line(3);
            when Toggle_Debug => -- Toggle debugging.
               New_Line(3);
               Config.Boolean_Opts(Debug) := not Config.Boolean_Opts(Debug);
            when Toggle_ISO => -- Toggle inline separate output.
               New_Line(3);
               Config.Boolean_Opts(Inline_Separate_Output) := not Config.Boolean_Opts(Inline_Separate_Output);
            when MV_D_Setting => -- Change MIME viewer decrypt setting.
               New_Line(3);
               Put_Line("MIME viewer for decrypt is set to "
                  & ToStr(MV_Str(Config.Positive_Opts(MIME_Viewer_Decrypt))));
               Config.Positive_Opts(MIME_Viewer_Decrypt) := MV_Values(MIME_Viewer_Menu1);
            when MV_V_Setting => -- Change MIME viewer verify setting.
               New_Line(3);
               Put_Line("MIME viewer for verify is set to "
                  & ToStr(MV_Str(Config.Positive_Opts(MIME_Viewer_Verify))));
               Config.Positive_Opts(MIME_Viewer_Verify) := MV_Values(MIME_Viewer_Menu1);
            when UGA_Setting => -- Change use GPG agent setting.
               New_Line(3);
               Put_Line("Use GPG agent is set to "
                  & ToStr(UGA_Str(Config.Positive_Opts(Use_Agent))));
               Config.Positive_Opts(Use_Agent) := UGA_Values(Use_Agent_Menu);
            when Toggle_OIDN => -- Toggle omit inline disposition name.
               New_Line(3);
               Config.Boolean_Opts(Omit_Inline_Disposition_Name) := not Config.Boolean_Opts(Omit_Inline_Disposition_Name);
            when Toggle_OIDH => -- Toggle omit inline disposition header.
               New_Line(3);
               Config.Boolean_Opts(Omit_Inline_Disposition_Header) := not Config.Boolean_Opts(Omit_Inline_Disposition_Header);
            when Toggle_SOS => -- Toggle save on send.
               New_Line(3);
               Config.Boolean_Opts(Save_On_Send) := not Config.Boolean_Opts(Save_On_Send);
            when Toggle_WIMS => -- Toggle save on send.
               New_Line(3);
               Config.Boolean_Opts(Wait_If_Missing_Keys) := not Config.Boolean_Opts(Wait_If_Missing_Keys);
             when Reset => -- Restore to current saved file.
               New_Line(3);
               Copy_Configuration(Config, Default_Config);
               declare
                  Warnings : Boolean;
               begin
                  Read_Config_File(Warnings);
               end;
               Put_Line("Read saved configuration");
               New_Line(3);
         end case;
      end loop Edit_Loop;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Configuration.Edit_Configuration");
         raise;
   end Edit_Configuration;

   procedure Default_Configuration (C : in out Config_Record) is
   begin
      C.UBS_Opts(My_Key) := ToUBS("");
      C.Binary(Chmod) := ToUBS("chmod");
      C.Binary(Clear) := ToUBS("clear");
      C.Binary(Cut) := ToUBS("cut");
      C.Binary(Date) := ToUBS("date");
      C.Binary(Diff) := ToUBS("diff");
      C.Binary(Formail) := ToUBS("formail");
      C.Binary(File) := ToUBS("file");
      C.Binary(Gpgop) := ToUBS("gpg");
      C.Binary(Gpgsm) := ToUBS("gpgsm");
      C.Binary(Grep) := ToUBS("grep");
      C.Binary(Iconv) := ToUBS("iconv");
      C.Binary(Less) := ToUBS("less");
      C.Binary(Locale) := ToUBS("locale");
      C.Binary(Md5sum) := ToUBS("md5sum");
      C.Binary(Metamail) := ToUBS("metamail");
      C.Binary(Mimeconstruct) := ToUBS("mime-construct");
      C.Binary(Mimetool) := ToUBS("mime-tool");
      C.Binary(Mkdir) := ToUBS("mkdir");
      C.Binary(Mkfifo) := ToUBS("mkfifo");
      C.Binary(Mv) := ToUBS("mv");
      C.Binary(openssl) := ToUBS("openssl");
      C.Binary(Rm) := ToUBS("rm");
      C.Binary(Runmailcap) := ToUBS("run-mailcap");
      C.Binary(Scp) := ToUBS("scp");
      C.Binary(Sed) := ToUBS("sed");
      C.Binary(Ssh) := ToUBS("ssh");
      C.Binary(Stty) := ToUBS("stty");
      C.Binary(Tee) := ToUBS("tee");
      C.Binary(Test) := ToUBS("test");
      C.UBS_Opts(GPG_Options) := ToUBS("--no-options");
      C.UBS_Opts(GPGSM_Options) := ToUBS("--no-options");
      C.UBS_Opts(General_Options) := ToUBS("");
      C.UBS_Opts(Receiving_Options) := ToUBS("--keyserver=hkp://subkeys.pgp.net");
      C.UBS_Opts(Sending_Options) := ToUBS("--comment ""Topal (https://zircon.org.uk/topal/)""");
      C.UBS_Opts(Colour_Menu_Title) := ToUBS("1;45;37");
      C.UBS_Opts(Colour_Menu_Key) := ToUBS("0;42;30");
      C.UBS_Opts(Colour_Menu_Choice) := ToUBS("0;32;40");
      C.UBS_Opts(Colour_Important) := ToUBS("1;41;37");
      C.UBS_Opts(Colour_Banner) := ToUBS("1;47;30");
      C.UBS_Opts(Colour_Info) := ToUBS("1;40;36");
      C.UBS_Opts(Decrypt_Prereq) := ToUBS("");
      C.Positive_Opts(Decrypt_Not_Cached) := 2;
      C.Positive_Opts(Decrypt_Not_Cached_Use_Cache) := 1;
      C.Positive_Opts(Decrypt_Cached) := 2;
      C.Positive_Opts(Decrypt_Cached_Use_Cache) := 1;
      C.Positive_Opts(Verify_Not_Cached) := 1;
      C.Positive_Opts(Verify_Not_Cached_Use_Cache) := 1;
      C.Positive_Opts(Verify_Cached) := 1;
      C.Positive_Opts(Verify_Cached_Use_Cache) := 1;
      C.Positive_Opts(MIME_Viewer_Decrypt) := 1;
      C.Positive_Opts(MIME_Viewer_Verify) := 1;
      C.Positive_Opts(Use_Agent) := 2;
      C.Positive_Opts(Replace_IDs) := 2;
      C.Positive_Opts(Include_Send_Token) := 3;
      C.Boolean_Opts(Decrypt_Cached_Fast_Continue) := False;
      C.Boolean_Opts(Verify_Cached_Fast_Continue) := True;
      C.Boolean_Opts(Verify_Not_Cached_Fast_Continue) := False;
      C.Boolean_Opts(Inline_Separate_Output) := False;
      C.Boolean_Opts(Omit_Inline_Disposition_Name) := False;
      C.Boolean_Opts(Omit_Inline_Disposition_Header) := False;
      C.Boolean_Opts(Save_On_Send) := False;
      C.Boolean_Opts(ANSI_Terminal) := True;
      C.Boolean_Opts(Wait_If_Missing_Keys) := True;
      C.Boolean_Opts(Attachment_Trap) := False;
      C.Boolean_Opts(FE_Simple) := False;
      C.Boolean_Opts(No_Clean) := False;
      C.Boolean_Opts(All_Headers) := False;
      C.Boolean_Opts(Read_From) := False;
      C.Boolean_Opts(Ask_Charset) := False;
      C.Boolean_Opts(Fix_Fcc) := True;
      C.Boolean_Opts(Fix_Bcc) := True;
      C.Boolean_Opts(Debug) := False;
      C.AKE := UPP.Empty_Vector;
      C.XK := UVP.Empty_Vector;
      C.SAKE := UPP.Empty_Vector;
      C.SXK := UVP.Empty_Vector;
      C.SD := UPP.Empty_Vector;
      C.SC := UPP.Empty_Vector;
      C.ST := UPP.Empty_Vector;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Configuration.Default_Configuration");
         raise;
   end Default_Configuration;

   procedure Copy_Configuration(Left  : in out Config_Record;
                                Right : in     Config_Record) is
   begin
      for I in Binaries loop
         Left.Binary(I) := Right.Binary(I);
      end loop;
      for I in UBS_Opts_Keys loop
         Left.UBS_Opts(I) := Right.UBS_Opts(I);
      end loop;
      for I in Positive_Opts_Keys loop
         Left.Positive_Opts(I) := Right.Positive_Opts(I);
      end loop;
      for I in Boolean_Opts_Keys loop
         Left.Boolean_Opts(I) := Right.Boolean_Opts(I);
      end loop;
      Left.AKE :=  Right.AKE;
      Left.XK := Right.XK;
      Left.SAKE := Right.SAKE;
      Left.SXK := Right.SXK;
      Left.SD := Right.SD;
      Left.SC := Right.SC;
      Left.ST := Right.ST;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Configuration.Copy_Configuration");
         raise;
   end Copy_Configuration;

end Configuration;
