/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.io;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.Closeable;
import java.io.IOException;
import java.util.function.Consumer;
import net.ranides.assira.events.EventListener;
import net.ranides.assira.functional.CheckedConsumer;
import net.ranides.assira.functional.CheckedSupplier;
import net.ranides.assira.trace.ExceptionUtils;
import net.ranides.assira.trace.LoggerUtils;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class IOUtils {
    
    private static final org.slf4j.Logger LOGGER = LoggerUtils.getLogger();
    
    private IOUtils() {
        /* utility class */
    }
    
    public static IOException wrap(Throwable cause) {
        return (cause instanceof IOException) ? (IOException)cause : new IOException(cause);
    }
    
    public static RuntimeException rethrow(Throwable cause) {
        return ExceptionUtils.rethrow(wrap(cause));
    }
    
    public static boolean close(Closeable object) throws IOException {
        if(null != object) {
            object.close();
            return true;
        }
        return false;
    }
    
    public static boolean close(Closeable object, EventListener<? super IOEvent.Failure> listener) {
        try {
            return close(object);
        } catch(IOException cause) {
            if(null != listener) {
                listener.handleEvent(IOEvent.failure(cause));
            } else {
                LOGGER.warn("Exception ignored on #close", cause);
            }
            return false;
        }
    }
    
    public static boolean close(Object object) throws Exception {
        if(object instanceof Closeable) {
            ((Closeable)object).close();
            return true;
        }
        if(object instanceof AutoCloseable) {
            ((AutoCloseable)object).close();
            return true;
        }
        return false;
    }
    
    public static boolean close(Object object, Consumer<? super Exception> handler) {
        try {
            return close(object);
        } catch(Exception cause) {
            if(null != handler) {
                handler.accept(cause);
            } else {
                LOGGER.warn("Exception ignored on #close", cause);
            }
            return false;
        }
    }
    
    @SuppressWarnings({"DoubleCheckedLocking"})
    @SuppressFBWarnings({"DC_DOUBLECHECK","IS2_INCONSISTENT_SYNC"})
    static final class SharedHandle<T> implements IOHandle<T> {
        
        private final CheckedSupplier<T,IOException> open;
        
        private final CheckedConsumer<T,IOException> close;

        private SharedWrapper<T> ref = null;

        SharedHandle(CheckedSupplier<T,IOException> open) {
            this.close = (CheckedConsumer)(CheckedConsumer<Closeable, IOException>)Closeable::close;
            this.open = open;
        }

        SharedHandle(CheckedConsumer<T,IOException> close, CheckedSupplier<T,IOException> open) {
            this.close = close;
            this.open = open;
        }
        
        @Override
        public void close() throws IOException {
            SharedWrapper<T> temp = ref;
            if (temp == null) {
                synchronized(this) {
                    temp = ref;
                    if (temp == null) {
                        return;
                    }
                }
            }
            if(null != ref.value) {
                close.$accept(ref.value);
            }
        }

        @Override
        public T get() throws IOException {
            SharedWrapper<T> temp = ref;
            if (temp == null) {
                synchronized(this) {
                    temp = ref;
                    if (temp == null) {
                        ref = temp = new SharedWrapper<>(open.get());
                    }
                }
            }
            return temp.value;
        }

        @Override
        public boolean opened() {
            SharedWrapper<T> temp = ref;
            if (temp == null) {
                synchronized(this) {
                    temp = ref;
                    if (temp == null) {
                        return false;
                    }
                }
            }
            return null != temp.value;
        }
        
        private static final class SharedWrapper<T> {

            public final T value;

            public SharedWrapper(T value) {
                this.value = value;
            }

        }

    }

}
