using System;
using System.IO;
using System.Net;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using C1.C1Zip;

namespace ZipBrowser.Web
{
    /// <summary>
    /// Summary description for ZipBrowserService
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    [System.Runtime.Serialization.KnownType(typeof(ZipItem))]
    public class ZipBrowserService : System.Web.Services.WebService
    {
        // ** object model

        [WebMethod]
        public ZipItem GetContent(string url)
        {
            var folder = new ZipItem();
            folder.Path = System.IO.Path.GetFileName(url);
            var zip = GetZipFile(url);
            foreach (C1ZipEntry ze in zip.Entries)
            {
                if ((ze.Attributes & FileAttributes.Directory) == 0)
                {
                    AddItem(folder, ze.FileName);
                }
            }
            return folder;
        }

        [WebMethod]
        public string GetEntryText(string url, string path)
        {
            var zip = GetZipFile(url);
            var ze = zip.Entries[path];
            if (ze != null)
            {
                using (var sr = new StreamReader(ze.OpenReader()))
                {
                    return sr.ReadToEnd();
                }
            }
            return null;
        }
        [WebMethod]
        public byte[] GetEntryBytes(string url, string path)
        {
            var zip = GetZipFile(url);
            var ze = zip.Entries[path];
            if (ze != null)
            {
                using(var ms = new MemoryStream())
                using (var zs = ze.OpenReader())
                {
                    zs.CopyTo(ms);
                    return ms.ToArray();
                }
            }
            return null;
        }

        // ** implementation

        /// <summary>
        /// This method gets a C1ZipFile based on a Url.
        /// 
        /// In this sample, it does so by downloading the entire contents of
        /// the zip into a stream, and then building the C1ZipFile from the stream.
        /// 
        /// This works, but can be quite slow since the entire file has to be 
        /// downloaded every time the service is called and zip files can be
        /// very large.
        /// 
        /// If you can count on the zip files being present on the server,
        /// then it would be much faster to build the C1ZipFile directly from
        /// a file. In this case, the C1ZipFile would extract only the 
        /// information it needs, and the app would run one or two orders of
        /// magnitude faster.
        /// </summary>
        C1ZipFile GetZipFile(string url)
        {
            // if we could count on the url being a local file:
            string fileName = ResolveUrlToLocalFile(url);
            if (!string.IsNullOrEmpty(fileName))
            {
                return new C1ZipFile(fileName);
            }

            // get entire zip file from a remote URL and build
            // the C1ZipFile from that stream (MUCH slower):
            var wc = new WebClient();
            using (var ws = wc.OpenRead(url))
            {
                var ms = new MemoryStream();
                ws.CopyTo(ms);
                ms.Position = 0;
                return new C1ZipFile(ms);
            }
        }
        string ResolveUrlToLocalFile(string url)
        {
            // TODO: if you know that the URL corresponds to a file that
            // you have access to, then return the file name here.
            // it will make things much faster.
            return null;
        }
        void AddItem(ZipItem folder, string fileName)
        {
            var path = fileName.Split('/', '\\');
            for (int i = 0; i < path.Length - 1; i++)
            {
                folder = GetFolder(folder, path[i]);
            }
            folder.Items.Add(new ZipItem(fileName));
        }
        ZipItem GetFolder(ZipItem folder, string subFolderName)
        {
            // find existing sub-folder
            foreach (var item in folder.Items)
            {
                var subFolder = item as ZipItem;
                if (subFolder != null && string.Equals(subFolder.FileName, subFolderName, StringComparison.OrdinalIgnoreCase))
                {
                    return subFolder;
                }
            }

            // not found? create now
            var newFolder = new ZipItem();
            newFolder.Path = folder.Path + "/" + subFolderName;
            folder.Items.Add(newFolder);
            return newFolder;
        }
    }
    public class ZipItem
    {
        public string Path { get; set; }
        public List<ZipItem> Items { get; set; }
        public ZipItem()
        {
            Items = new List<ZipItem>();
        }
        public ZipItem(string path)
            : this()
        {
            Path = path;
        }
        public string FileName
        {
            get { return System.IO.Path.GetFileName(Path); }
            set { }
        }
    }
}
