package ctbrec.recorder.download;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.iheartradio.m3u8.Encoding;
import com.iheartradio.m3u8.Format;
import com.iheartradio.m3u8.ParseException;
import com.iheartradio.m3u8.ParsingMode;
import com.iheartradio.m3u8.PlaylistException;
import com.iheartradio.m3u8.PlaylistParser;
import com.iheartradio.m3u8.data.MediaPlaylist;
import com.iheartradio.m3u8.data.Playlist;
import com.iheartradio.m3u8.data.TrackData;

import ctbrec.Config;
import ctbrec.Model;
import ctbrec.io.HttpClient;
import ctbrec.io.HttpException;
import ctbrec.sites.fc2live.Fc2Live;
import okhttp3.Request;
import okhttp3.Response;

public abstract class AbstractHlsDownload implements Download {

    private static final transient Logger LOG = LoggerFactory.getLogger(AbstractHlsDownload.class);

    protected HttpClient client;
    protected volatile boolean running = false;
    protected volatile boolean alive = true;
    protected Instant startTime;
    protected Model model;
    protected BlockingQueue<Runnable> downloadQueue = new LinkedBlockingQueue<>(50);
    protected ExecutorService downloadThreadPool = new ThreadPoolExecutor(5, 5, 2, TimeUnit.MINUTES, downloadQueue);

    public AbstractHlsDownload(HttpClient client) {
        this.client = client;
    }

    protected SegmentPlaylist getNextSegments(String segments) throws IOException, ParseException, PlaylistException {
        URL segmentsUrl = new URL(segments);
        Request request = new Request.Builder()
                .url(segmentsUrl)
                .header("Accept", "*/*")
                .header("Accept-Language", "en-US,en;q=0.5")
                .header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
                .header("Origin", Fc2Live.BASE_URL)
                .header("Referer", Fc2Live.BASE_URL)
                .header("Connection", "keep-alive")
                .build();
        try(Response response = client.execute(request)) {
            if(response.isSuccessful()) {
                //                String body = response.body().string();
                //                InputStream inputStream = new ByteArrayInputStream(body.getBytes("utf-8"));
                //                LOG.debug("Segments {}", body);
                InputStream inputStream = response.body().byteStream();
                PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT);
                Playlist playlist = parser.parse();
                if(playlist.hasMediaPlaylist()) {
                    MediaPlaylist mediaPlaylist = playlist.getMediaPlaylist();
                    SegmentPlaylist lsp = new SegmentPlaylist(segments);
                    lsp.seq = mediaPlaylist.getMediaSequenceNumber();
                    lsp.targetDuration = mediaPlaylist.getTargetDuration();
                    List<TrackData> tracks = mediaPlaylist.getTracks();
                    for (TrackData trackData : tracks) {
                        String uri = trackData.getUri();
                        if(!uri.startsWith("http")) {
                            String _url = segmentsUrl.toString();
                            _url = _url.substring(0, _url.lastIndexOf('/') + 1);
                            uri = _url + uri;
                        }
                        lsp.totalDuration += trackData.getTrackInfo().duration;
                        lsp.lastSegDuration = trackData.getTrackInfo().duration;
                        lsp.segments.add(uri);
                    }
                    return lsp;
                }
                return null;
            } else {
                throw new HttpException(response.code(), response.message());
            }
        }
    }


    protected String getSegmentPlaylistUrl(Model model) throws IOException, ExecutionException, ParseException, PlaylistException {
        LOG.debug("{} stream idx: {}", model.getName(), model.getStreamUrlIndex());
        List<StreamSource> streamSources = model.getStreamSources();
        Collections.sort(streamSources);
        for (StreamSource streamSource : streamSources) {
            LOG.debug("{} src {}", model.getName(), streamSource);
        }
        String url = null;
        if(model.getStreamUrlIndex() >= 0 && model.getStreamUrlIndex() < streamSources.size()) {
            // TODO don't use the index, but the bandwidth. if the bandwidth does not match, take the closest one
            LOG.debug("{} selected {}", model.getName(), streamSources.get(model.getStreamUrlIndex()));
            url = streamSources.get(model.getStreamUrlIndex()).getMediaPlaylistUrl();
        } else {
            // filter out stream resolutions, which are too high
            int maxRes = Config.getInstance().getSettings().maximumResolution;
            if(maxRes > 0) {
                for (Iterator<StreamSource> iterator = streamSources.iterator(); iterator.hasNext();) {
                    StreamSource streamSource = iterator.next();
                    if(streamSource.height > 0 && maxRes < streamSource.height) {
                        LOG.trace("Res too high {} > {}", streamSource.height, maxRes);
                        iterator.remove();
                    }
                }
            }
            if(streamSources.isEmpty()) {
                throw new ExecutionException(new RuntimeException("No stream left in playlist"));
            } else {
                LOG.debug("{} selected {}", model.getName(), streamSources.get(streamSources.size()-1));
                url = streamSources.get(streamSources.size()-1).getMediaPlaylistUrl();
            }
        }
        LOG.debug("Segment playlist url {}", url);
        return url;
    }

    @Override
    public boolean isAlive() {
        return alive;
    }

    @Override
    public Instant getStartTime() {
        return startTime;
    }

    @Override
    public Model getModel() {
        return model;
    }

    public static class SegmentPlaylist {
        public String url;
        public int seq = 0;
        public float totalDuration = 0;
        public float lastSegDuration = 0;
        public float targetDuration = 0;
        public List<String> segments = new ArrayList<>();

        public SegmentPlaylist(String url) {
            this.url = url;
        }
    }
}
