use std::{
    collections::{HashMap, VecDeque},
    str::from_utf8,
};

use hpack::Decoder;

use super::{
    parser,
    state::{FrameResult, OutputFrame},
};

#[derive(Clone, Debug, PartialEq)]
pub enum St {
    Init,
    ClientPrefaceReceived,
    ServerPrefaceSent,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Stream {
    pub id: u32,
    pub state: StreamState,
    pub output: VecDeque<OutputFrame>,
    pub inbound_headers: HashMap<Vec<u8>, Vec<u8>>,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum StreamState {
    Idle,
    ReservedLocal,
    ReservedRemote,
    Open,
    HalfClosedLocal,
    HalfClosedRemote,
    Closed,
}

impl Stream {
    pub fn new(id: u32) -> Stream {
        info!("new stream with id {}", id);

        Stream {
            id,
            state: StreamState::Idle,
            output: VecDeque::new(),
            inbound_headers: HashMap::new(),
        }
    }

    pub fn handle(&mut self, frame: &parser::Frame) -> FrameResult {
        match self.state {
            StreamState::Idle => match frame {
                parser::Frame::Headers(h) => {
                    let mut decoder = Decoder::new();
                    match decoder.decode(h.header_block_fragment) {
                        Err(e) => {
                            error!("error decoding headers: {:?}", e);
                            FrameResult::Close
                        }
                        Ok(mut h) => {
                            let mut has_authority = false;
                            let mut has_path = false;

                            self.inbound_headers.extend(h.drain(..).map(|(k, v)| {
                                if &k == b"authority" {
                                    has_authority = true;
                                }

                                if &k == b"path" {
                                    has_path = true;
                                }

                                info!("{} -> {}", from_utf8(&k).unwrap(), from_utf8(&v).unwrap());
                                (k, v)
                            }));

                            self.state = StreamState::Open;
                            info!("stream[{}] state is now {:?}", self.id, self.state);
                            info!("headers: {:?}", self.inbound_headers);

                            if self.inbound_headers.contains_key(&b":authority"[..])
                                && self.inbound_headers.contains_key(&b":path"[..])
                            {
                                info!("will send connect_to_backend");
                                FrameResult::ConnectBackend(self.id)
                            } else {
                                FrameResult::Continue
                            }
                        }
                    }
                }
                frame => {
                    panic!("unknown frame for now: {:?}", frame);
                }
            },
            s => {
                unimplemented!("stream[{}] state {:?} not implemented", self.id, self.state);
            }
        }
    }
}
