// edtFTPnet
// 
// Copyright (C) 2004 Enterprise Distributed Technologies Ltd
// 
// www.enterprisedt.com
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library 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
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
// 
// Bug fixes, suggestions and comments should posted on 
// http://www.enterprisedt.com/forums/index.php
// 
// Change Log:
// 
// $Log: OS400FileParser.cs,v $
// Revision 1.8  2009-09-24 04:52:45  bruceb
// get rid of compile warning
//
// Revision 1.7  2009-07-16 01:11:57  bruceb
// tidalsoftware's tweaks integrated
//
// Revision 1.6  2008-10-08 23:49:29  hans
// Commented out unused fields.
//
// Revision 1.5  2008-07-15 05:42:57  bruceb
// refactor parsing code
//
// Revision 1.4  2007-06-26 01:36:32  bruceb
// CF changes
//
// Revision 1.3  2007-05-23 00:29:36  hans
// Added TimeIncludesSeconds property.
//
// Revision 1.2  2007-02-13 12:12:16  bruceb
// extra comment
//
// Revision 1.1  2007/02/02 01:41:26  bruceb
// new parser
//
//

using System;
using System.Globalization;
using System.Text;
using EnterpriseDT.Util;
using EnterpriseDT.Util.Debug;

namespace EnterpriseDT.Net.Ftp
{    
    /// <summary>  
    /// Represents a remote OS400 file parser. 
    /// </summary>
    /// <remarks>
    /// This parser is somewhat experimental :)
    /// </remarks>
    /// <author>Bruce Blackshaw
    /// </author>
    /// <version>$Revision: 1.8 $</version>
    public class OS400FileParser : FTPFileParser
    {            
        /// <summary>Directory field</summary>
        private static readonly string DIR = "*DIR";

        /// <summary>Directory field</summary>
        private static readonly string DDIR = "*DDIR";
    
        /// <summary>Directory field</summary>
        //private static readonly string FILE1 = "*FILE";
        
        /// <summary>Directory field</summary>
        //private static readonly string FILE2 = "*STMF";
    
        /// <summary>MEM field?</summary>
        private static readonly string MEM = "*MEM";
    
        /// <summary> Number of expected fields</summary>
        private static readonly int MIN_EXPECTED_FIELD_COUNT = 6;

        /// <summary>Date formats</summary>
        private static readonly string DATE_FORMAT_1 = "dd'/'MM'/'yy' 'HH':'mm':'ss";	
        private static readonly string DATE_FORMAT_2 = "dd'/'MM'/'yyyy' 'HH':'mm':'ss";	
        private static readonly string DATE_FORMAT_3 = "dd'.'MM'.'yy' 'HH':'mm':'ss";	

        private static readonly string DATE_FORMAT_11 = "yy'/'MM'/'dd' 'HH':'mm':'ss";	
        private static readonly string DATE_FORMAT_12 = "yyyy'/'MM'/'dd' 'HH':'mm':'ss";	
        private static readonly string DATE_FORMAT_13 = "yy'.'MM'.'dd' 'HH':'mm':'ss";

        private static readonly string DATE_FORMAT_21 = "MM'/'dd'/'yy' 'HH':'mm':'ss";
        private static readonly string DATE_FORMAT_22 = "MM'/'dd'/'yyyy' 'HH':'mm':'ss";
        private static readonly string DATE_FORMAT_23 = "MM'.'dd'.'yy' 'HH':'mm':'ss";

        /// <summary>array of formats</summary>
        private static string[] formats1 = {DATE_FORMAT_1,DATE_FORMAT_2,DATE_FORMAT_3};
        private static string[] formats2 = {DATE_FORMAT_11,DATE_FORMAT_12,DATE_FORMAT_13};
        private static string[] formats3 = { DATE_FORMAT_21, DATE_FORMAT_22, DATE_FORMAT_23 };
        private string[][] formats = {formats1,formats2,formats3};

        /// <summary> Logging object</summary>
        private Logger log = Logger.GetLogger("OS400FileParser");

        private int formatIndex = 0;

        /// <summary>
        /// Get flag indicating whether or not the most recent parse returned seconds.
        /// </summary>
        public override bool TimeIncludesSeconds
        {
            get { return true; }
        }

        public override string ToString()
        {
            return "OS400";
        }

        /// <summary>
        /// Test for valid format for this parser
        /// </summary>
        /// <param name="listing">listing to test</param>
        /// <returns>true if valid</returns>
        public override bool IsValidFormat(String[] listing)
        {
            int count = Math.Min(listing.Length, 10);

            bool dir = false;
            bool ddir = false;
            bool lib = false;
            bool stmf = false;
            bool flr = false;
            bool file = false;

            for (int i = 0; i < count; i++)
            {
                if (listing[i].IndexOf("*DIR") > 0)
                    dir = true;
                else if (listing[i].IndexOf("*FILE") > 0)
                    file = true;
                else if (listing[i].IndexOf("*FLR") > 0)
                    flr = true;
                else if (listing[i].IndexOf("*DDIR") > 0)
                    ddir = true;
                else if (listing[i].IndexOf("*STMF") > 0)
                    stmf = true;
                else if (listing[i].IndexOf("*LIB") > 0)
                    lib = true;
            }
            if (dir || file || ddir || lib || stmf || flr)
                return true;
            log.Debug("Not in OS/400 format");
            return false;
        }
        
        /// <summary> Parse server supplied string</summary>
        /// <param name="raw">raw string to parse</param>
        /// <returns>FTPFile object representing the raw string</returns>
        /// <remarks>Listing look like the below:
        ///        CFT             45056 04/12/06 14:19:31 *FILE AFTFRE1.FILE
        ///        CFT                                     *MEM AFTFRE1.FILE/AFTFRE1.MBR
        ///        CFT             36864 28/11/06 15:19:30 *FILE AFTFRE2.FILE
        ///        CFT                                     *MEM AFTFRE2.FILE/AFTFRE2.MBR
        ///        CFT             45056 04/12/06 14:19:37 *FILE AFTFRE6.FILE
        ///        CFT                                     *MEM  AFTFRE6.FILE/AFTFRE6.MBR
        ///        QSYSOPR         28672 01/12/06 20:08:04 *FILE FPKI45POK5.FILE
        ///        QSYSOPR                                 *MEM FPKI45POK5.FILE/FPKI45POK5.MBR        
        /// </remarks>
        public override FTPFile Parse(string raw) 
        {
            string[] fields = StringSplitter.Split(raw);      
            
            // skip blank lines
            if(fields.Length <= 0)
                return null;
            // return what we can for MEM
            if (fields.Length >= 2 && fields[1].Equals(MEM))
            {
                DateTime lastModifiedm = DateTime.MinValue;
                string ownerm = fields[0];
                string namem = fields[2];
                FTPFile filem = new FTPFile(FTPFile.OS400, raw, namem, 0, false, ref lastModifiedm);
                filem.Owner = ownerm;
                return filem;
            } 
            if (fields.Length < MIN_EXPECTED_FIELD_COUNT)
                return null;
            
            // first field is owner
            string owner = fields[0];

            // next is size
            long size = Int64.Parse(fields[1]);

            string lastModifiedStr = fields[2] + " " + fields[3];
            DateTime lastModified = GetLastModified(lastModifiedStr);
 
            // test is dir
            bool isDir = false;
            if (fields[4] == DIR || fields[4] == DDIR)
                isDir = true; 

            string name = fields[5];
            if (name.EndsWith("/"))
            {
                isDir = true;
                name = name.Substring(0, name.Length - 1);
            }
            
            FTPFile file = new FTPFile(FTPFile.OS400, raw, name, size, isDir, ref lastModified); 
            file.Owner = owner;
            return file;
        }


        private DateTime GetLastModified(string lastModifiedStr)
        {
            DateTime lastModified = DateTime.MinValue;
            if (formatIndex >= formats.Length)
            {
                log.Warn("Exhausted formats - failed to parse date");
                return DateTime.MinValue;
            }
            int prevIndex = formatIndex;
            for (int i = formatIndex; i < formats.Length; i++, formatIndex++)
            {
                try
                {
                    lastModified = DateTime.ParseExact(lastModifiedStr, formats[formatIndex],
                        ParsingCulture.DateTimeFormat, DateTimeStyles.None);
                    if (lastModified > DateTime.Now.AddDays(2))
                    {
                        log.Debug("Swapping to alternate format (found date in future)");
                        continue;
                    }
                    else // all ok, exit loop
                        break;
                }
                catch (FormatException)
                {
                    continue;
                }
            }
            if (formatIndex >= formats.Length)
            {
                log.Warn("Exhausted formats - failed to parse date");
                return DateTime.MinValue;
            }
            if (formatIndex > prevIndex) // we've changed formatters so redo
            {
                throw new RestartParsingException();
            }
            return lastModified;
        }        
    }
}
