//  
//   Rebex Sample Code License
// 
//   Copyright 2025, REBEX CR s.r.o.
//   All rights reserved.
//   https://www.rebex.net/
// 
//   Permission to use, copy, modify, and/or distribute this software for any
//   purpose with or without fee is hereby granted.
// 
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//   OTHER DEALINGS IN THE SOFTWARE.
// 

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Rebex.IO.FileSystem;
using Rebex.Security.Cryptography.Pkcs;

namespace Rebex.Samples
{
    // Simple custom file system provider based on the System.IO
    public class DriveFileSystemProvider : ReadWriteFileSystemProvider
    {
        private readonly string _basePath;

        public DriveFileSystemProvider(DriveInfo drive, FileSystemProviderSettings settings = null)
            : base(settings)
        {
            if (drive == null)
            {
                throw new ArgumentNullException("drive");
            }

            //if property drive.Name returns "c:\", then our _basePath is "c:"
            _basePath = drive.Name.Trim(Path.DirectorySeparatorChar);
        }

        protected override NodeTimeInfo GetTimeInfo(NodeBase node)
        {
            //convert an uniform path of the node in the Rebex file system to a physical file system path.
            //  /myDir/myFile.txt -> c:\myDir\myFile.txt
            string fullPath = GetFullPath(node.Path);
            
            //get and store CreationTime, LastAccessTime and LastWriteTime.
            DateTime creationTime = File.GetCreationTime(fullPath);
            DateTime lastAccessTime = File.GetLastAccessTime(fullPath);
            DateTime lastWriteTime = File.GetLastWriteTime(fullPath);

            //create and return new instance of the NodeTimeInfo
            return new NodeTimeInfo(creationTime, lastAccessTime, lastWriteTime);
        }


        protected override NodeBase GetChild(string name, DirectoryNode parent)
        {
            //convert an uniform path of the node in the Rebex file system to a physical file system path.
            //suppose that:
            //argument name = myFile.txt
            //argument parent has path /mydir
            //then
            //statement parent.Path.AddPathPart(name) evaluates to '/myDir/myFile.txt'
            //and fullChildPath = 'c:\myDir\myFile.txt'
            string fullChildPath = GetFullPath(parent.Path.AddPathPart(name));

            //if we found a file on the path
            if (File.Exists(fullChildPath))
            {
                //then create and return new FileNode 
                return new FileNode(name, parent);
            }

            //if we found a directory/folder on the path
            if (Directory.Exists(fullChildPath))
            {
                //then create and return new DirectoryNode 
                return new DirectoryNode(name, parent);
            }

            //if we are not able to find a node, then return null.
            return null;
        }

        protected override IEnumerable<NodeBase> GetChildren(DirectoryNode parent, NodeType nodeType)
        {
            //convert an uniform path of the node in the Rebex file system to a physical file system path.
            //  /myDir -> c:\myDir
            string parentPath = GetFullPath(parent.Path);
            
            //retChildren is now empty
            IEnumerable<NodeBase> retChildren = Enumerable.Empty<NodeBase>();

            //if client wants (sub)directories of the argument parent
            if (nodeType.HasFlag(NodeType.Directory))
            {
                //then enumerate all directories in the parent
                retChildren = retChildren.Union(Directory.EnumerateDirectories(parentPath)
                    //and for every found directory instantiate new DirectoryNode 
                    //with the name extracted from the corresponding directory path 
                    //and with the argument parent.
                    .Select(dirPath => new DirectoryNode(GetNodeName(dirPath), parent)));
                
                //Do not forget to union the LINQ result with our variable retChildren.
            }

            //if client wants files contained in the parent.
            if (nodeType.HasFlag(NodeType.File))
            {
                //then enumerate all files in the parent
                retChildren = retChildren.Union(Directory.GetFiles(parentPath)
                    //and for every found file instantiate new FileNode 
                    //with the name extracted from the corresponding file path 
                    //and with the argument parent.
                    .Select(filePath => new FileNode(GetNodeName(filePath), parent)));
                //again: Do not forget to union the LINQ result with our variable retChildren.
            }

            //Now we are done. Return all found children.
            return retChildren;
        }

        protected override bool Exists(NodePath path, NodeType nodeType)
        {
            //convert an uniform path of the node in the Rebex file system to a physical file system path.
            //  /myDir/myFile.txt -> c:\myDir\myFile.txt
            string fullPath = GetFullPath(path);
            
            //if client requested verification of the existence of the file
            if (nodeType == NodeType.File)
            {
                //then delegate to the File.Exists method and return result
                return File.Exists(fullPath);
            }

            //if client requested verification of the existence of the directory
            if (nodeType == NodeType.Directory)
            {
                //then delegate to the File.Exists method and return result
                return Directory.Exists(fullPath);
            }

            //otherwise return false.
            return false;
        }

        protected override long GetLength(NodeBase node)
        {
            //convert an uniform path of the node in the Rebex file system to a physical file system path.
            //  /myDir/myFile.txt -> c:\myDir\myFile.txt
            string fullPath = GetFullPath(node.Path);

            //delegate to System.IO.FileInfo and return length of the file
            return new FileInfo(fullPath).Length;
        }

        protected override NodeContent GetContent(NodeBase node, NodeContentParameters contentRequest)
        {
            //convert an uniform path of the node in the Rebex file system to a physical file system path.
            //  /myDir/myFile.txt -> c:\myDir\myFile.txt
            string fullPath = GetFullPath(node.Path);

            //client requires read access to the file
            if (contentRequest.AccessType == NodeContentAccess.Read)
            {
                //then use System.IO.File.OpenRead method
                var readOnlyStream = File.OpenRead(fullPath);
                //wrap FileStream to the read-only content
                //and return the content
                return NodeContent.CreateReadOnlyContent(readOnlyStream);
            }

            //client requires write or read-write access to the file
            
            //convert the value of the NodeContentAccess enum 
            //to the corresponding value of the System.IO.FileAccess enum.
            FileAccess realFsAccessMode = (contentRequest.AccessType == NodeContentAccess.Write)
                ? FileAccess.Write
                : FileAccess.ReadWrite;

            //open file stream
            Stream readWriteStream = File.Open(fullPath, FileMode.Open, realFsAccessMode);
            //and wrap filestream to the immediate write content.
            //remember: Immediate write content is used,
            //because System.IO.FileStream itself is responsible for the saving of the changed content of the file.
            //our method SaveContent (see bellow) is never called!
            return NodeContent.CreateImmediateWriteContent(readWriteStream);
        }

        protected override DirectoryNode CreateDirectory(DirectoryNode parent, DirectoryNode child)
        {
            //convert an uniform path of the node in the Rebex file system to a physical file system path.
            //  /myDir/myNewChildDirectory -> c:\myDir\myNewChildDirectory
            string newDirectoryFullPath = GetFullPath(child.Path);
            
            //delegate to the System.IO.Directory.CreateDirectory
            Directory.CreateDirectory(newDirectoryFullPath);

            //return representation of the created node
            //if you don't do anything special, then simply return argument child
            return child;
        }

        protected override FileNode CreateFile(DirectoryNode parent, FileNode child)
        {
            //convert an uniform path of the node in the Rebex file system to a physical file system path.
            //  /myDir/myNewChildFile.txt -> c:\myDir\myNewChildFile.txt
            string newFileFullPath = GetFullPath(child.Path);

            //delegate to the System.IO.File.Create
            //important: stream returned from the method Create must be closed 
            //before the file can be opened again in our or another process.
            File.Create(newFileFullPath).Close();

            return child;
        }

        protected override NodeBase Delete(NodeBase node)
        {
            //convert an uniform path of the node in the Rebex file system to a physical file system path.
            //  /myDir/toBeDeletedFile.bak -> c:\myDir\toBeDeletedFile.bak
            string toDeletePath = GetFullPath(node.Path);

            //for a directory
            if (node.IsDirectory)
            {
                //delegate to the System.IO.Directory.Delete method.
                Directory.Delete(toDeletePath);
            }
            //for a file
            else
            {
                //delegate to the System.IO.File.Delete method.
                File.Delete(toDeletePath);
            }

            //return representation of the deleted node
            //representation of the deleted node must satisfy following conditions:
            //property node.IsDeleted returns true
            //method node.Exists() return false
            return node;
        }

        protected override NodeBase Rename(NodeBase node, string newName)
        {
            //convert an uniform path of the node in the Rebex file system to a physical file system path.
            //suppose that:
            //(to be renamed) argument node has path \myDir\MyFile.txt
            //argument newName has value MySpecialFile.doc
            //then 
            //variable oldPath = c:\myDir\MyFile.txt
            //variable newPath = c:\myDir\MySpecialFile.doc
            string oldPath = GetFullPath(node.Path);
            string newPath = GetFullPath(node.Path.ParentPath.AddPathPart(newName));

            //and this is a variation on the same theme which we have seen before

            //For a directory
            if (node.IsDirectory)
            {
                //use the method System.IO.Directory.Move
                Directory.Move(oldPath, newPath);
                //and return the representation of the renamed node 
                return new DirectoryNode(newName, node.Parent);
            }

            //for a file 
            //use System.IO.File.Move
            File.Move(oldPath, newPath);
            //and return the representation of the renamed node 
            return new FileNode(newName, node.Parent);
        }

        protected override NodeBase SaveContent(NodeBase node, NodeContent content)
        {
            //our provider always returns content with the 'immediate write' characteristic (see GetContent method above),
            //therefore SaveContent method should never be called and we are asserting on this precondition
            Debug.Assert(false);
            return node;
        }


        protected override NodeBase SetAttributes(NodeBase node, NodeAttributes attributes)
        {
            //convert an uniform path of the node in the Rebex file system to a physical file system path.
            //  /myDir/myFile.txt -> c:\myDir\myFile.txt
            string fullNodePath = GetFullPath(node.Path);
            //Fulfill the request for a file, ignore directories for now
            if (node.IsFile)
            {
                //delegate to the System.IO.File.SetAttributes method
                File.SetAttributes(fullNodePath, attributes.FileAttributes);
                //and return up-to-date representation of the node,
                //which Attributes property returns value of the argument attributes
                return new FileNode(node.Name, node.Parent, node.TimeInfo, attributes);
            }

            //nothing to do?
            //simply return argument node
            return node;
        }

        protected override NodeBase SetTimeInfo(NodeBase node, NodeTimeInfo newTimeInfo)
        {
            //convert an uniform path of the node in the Rebex file system to a physical file system path.
            //  /myDir/myFile.txt -> c:\myDir\myFile.txt
            string fullNodePath = GetFullPath(node.Path);

            //initialise variable which will hold return value of this method.
            NodeBase retNode = node;

            //for a file
            if (node.IsFile)
            {
                //delegate to the System.IO.File methods
                File.SetCreationTimeUtc(fullNodePath, newTimeInfo.CreationTime);
                File.SetLastAccessTimeUtc(fullNodePath, newTimeInfo.LastAccessTime);
                File.SetLastWriteTimeUtc(fullNodePath, newTimeInfo.LastWriteTime);
                //create up-to-date representation of the node
                //which TimeInfo property returns value of the argument newTimeInfo
                retNode = new FileNode(node.Name, node.Parent, newTimeInfo, node.Attributes);
            }

            //for a directory
            if (node.IsDirectory)
            {
                //delegate to the System.IO.Directory methods
                Directory.SetCreationTimeUtc(fullNodePath, newTimeInfo.CreationTime);
                Directory.SetLastAccessTimeUtc(fullNodePath, newTimeInfo.LastAccessTime);
                Directory.SetLastWriteTimeUtc(fullNodePath, newTimeInfo.LastWriteTime);
                //create up-to-date representation of the node
                //which TimeInfo property returns value of the argument newTimeInfo
                retNode = new DirectoryNode(node.Name, node.Parent, newTimeInfo, node.Attributes);
            }

            //return up-to-date representation of the node which was created above
            return retNode;
        }

        //helper method
        //returns a file/folder name from a physical file system path
        private static string GetNodeName(string dirPath)
        {
            return Path.GetFileName(dirPath);
        }

        //helper method
        //returns a physical file system path for an instance of the Rebex NodePath
        private string GetFullPath(NodePath path)
        {
            string fullPath = _basePath + path.StringPath;
            fullPath = fullPath.Replace(NodePath.DirectorySeparator, Path.DirectorySeparatorChar);
            return fullPath;
        }
    }
}