using System.Text;

namespace SharpChat.Configuration;

public class StreamConfig : Config {
    private Stream Stream { get; }
    private StreamReader StreamReader { get; }
    private Mutex Lock { get; }

    private const int LOCK_TIMEOUT = 10000;

    private static readonly TimeSpan CACHE_LIFETIME = TimeSpan.FromMinutes(15);

    public StreamConfig(Stream stream) {
        Stream = stream ?? throw new ArgumentNullException(nameof(stream));
        if(!Stream.CanRead)
            throw new ArgumentException("Provided stream must be readable.", nameof(stream));
        if(!Stream.CanSeek)
            throw new ArgumentException("Provided stream must be seekable.", nameof(stream));
        StreamReader = new StreamReader(stream, new UTF8Encoding(false), false);
        Lock = new Mutex();
    }

    public static StreamConfig FromPath(string fileName) {
        return new StreamConfig(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite));
    }

    public string? ReadValue(string name, string? fallback = null) {
        if(!Lock.WaitOne(LOCK_TIMEOUT)) // don't catch this, if this happens something is Very Wrong
            throw new ConfigLockException();

        try {
            Stream.Seek(0, SeekOrigin.Begin);

            string? line;
            while((line = StreamReader.ReadLine()) != null) {
                if(string.IsNullOrWhiteSpace(line))
                    continue;

                line = line.TrimStart();
                if(line.StartsWith(';') || line.StartsWith('#'))
                    continue;

                string[] parts = line.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
                if(parts.Length < 2 || !string.Equals(parts[0], name))
                    continue;

                return parts[1];
            }
        } finally {
            Lock.ReleaseMutex();
        }

        return fallback;
    }

    public T? ReadValue<T>(string name, T? fallback = default) {
        object? value = ReadValue(name);
        if(value == null)
            return fallback;

        Type type = typeof(T);
        if(value is string strVal) {
            if(type == typeof(bool))
                value = !string.Equals(strVal, "0", StringComparison.InvariantCultureIgnoreCase)
                    && !string.Equals(strVal, "false", StringComparison.InvariantCultureIgnoreCase);
            else if(type == typeof(string[]))
                value = strVal.Split(' ');
        }

        try {
            return (T)Convert.ChangeType(value, type);
        } catch(InvalidCastException ex) {
            throw new ConfigTypeException(ex);
        }
    }

    public T? SafeReadValue<T>(string name, T? fallback) {
        try {
            return ReadValue(name, fallback);
        } catch(ConfigTypeException) {
            return fallback;
        }
    }

    public Config ScopeTo(string prefix) {
        if(string.IsNullOrWhiteSpace(prefix))
            throw new ArgumentException("Prefix must exist.", nameof(prefix));
        if(prefix[^1] != ':')
            prefix += ':';

        return new ScopedConfig(this, prefix);
    }

    public CachedValue<T> ReadCached<T>(string name, T? fallback = default, TimeSpan? lifetime = null) {
        return new CachedValue<T>(this, name, lifetime ?? CACHE_LIFETIME, fallback);
    }

    private bool IsDisposed;
    ~StreamConfig()
        => DoDispose();
    public void Dispose() {
        DoDispose();
        GC.SuppressFinalize(this);
    }
    private void DoDispose() {
        if(IsDisposed)
            return;
        IsDisposed = true;

        StreamReader.Dispose();
        Stream.Dispose();
        Lock.Dispose();
    }
}