/** @file

  Http2ClientSession.

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
 */

#ifndef __HTTP2_CLIENT_SESSION_H__
#define __HTTP2_CLIENT_SESSION_H__

#include "HTTP2.h"
#include "ProxyClientSession.h"
#include "Http2ConnectionState.h"

// Name                       Edata                 Description
// HTTP2_SESSION_EVENT_INIT   Http2ClientSession *  HTTP/2 session is born
// HTTP2_SESSION_EVENT_FINI   Http2ClientSession *  HTTP/2 session is ended
// HTTP2_SESSION_EVENT_RECV   Http2Frame *          Received a frame
// HTTP2_SESSION_EVENT_XMIT   Http2Frame *          Send this frame

#define HTTP2_SESSION_EVENT_INIT  (HTTP2_SESSION_EVENTS_START + 1)
#define HTTP2_SESSION_EVENT_FINI  (HTTP2_SESSION_EVENTS_START + 2)
#define HTTP2_SESSION_EVENT_RECV  (HTTP2_SESSION_EVENTS_START + 3)
#define HTTP2_SESSION_EVENT_XMIT  (HTTP2_SESSION_EVENTS_START + 4)

static size_t const HTTP2_HEADER_BUFFER_SIZE_INDEX = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX;

class Http2Frame
{
public:
  Http2Frame(const Http2FrameHeader& h, IOBufferReader * r) {
    this->hdr.cooked = h;
    this->ioreader = r;
  }

  Http2Frame(Http2FrameType type, Http2StreamId streamid, uint8_t flags) {
    Http2FrameHeader hdr = { 0, (uint8_t)type, flags, streamid };
    http2_write_frame_header(hdr, make_iovec(this->hdr.raw));
  }

  IOBufferReader * reader() const {
    return ioreader;
  }

  const Http2FrameHeader& header() const {
    return this->hdr.cooked;
  }

  // Allocate an IOBufferBlock for this frame. This switches us from using the in-line header
  // buffer, to an external buffer block.
  void alloc(int index) {
    this->ioblock = new_IOBufferBlock();
    this->ioblock->alloc(index);
    memcpy(this->ioblock->start(), this->hdr.raw, sizeof(this->hdr.raw));
    this->ioblock->fill(sizeof(this->hdr.raw));

    http2_parse_frame_header(make_iovec(this->ioblock->start(), HTTP2_FRAME_HEADER_LEN), this->hdr.cooked);
  }

  // Return the writeable buffer space.
  IOVec write() {
    return make_iovec(this->ioblock->end(), this->ioblock->write_avail());
  }

  // Once the frame has been serialized, update the length.
  void finalize(size_t nbytes) {
    if (this->ioblock) {
      ink_assert((int64_t)nbytes <= this->ioblock->write_avail());
      this->ioblock->fill(nbytes);

      this->hdr.cooked.length = this->ioblock->size() - HTTP2_FRAME_HEADER_LEN;
      http2_write_frame_header(this->hdr.cooked, make_iovec(this->ioblock->start(), HTTP2_FRAME_HEADER_LEN));
    }
  }

  void xmit(MIOBuffer * iobuffer) {
    if (ioblock) {
      iobuffer->append_block(this->ioblock);
    } else {
      iobuffer->write(this->hdr.raw, sizeof(this->hdr.raw));
    }
  }

private:
  Http2Frame(Http2Frame &); // noncopyable
  Http2Frame& operator=(const Http2Frame &); // noncopyable

  Ptr<IOBufferBlock>  ioblock;
  IOBufferReader *    ioreader;

  union {
    Http2FrameHeader cooked;
    uint8_t raw[HTTP2_FRAME_HEADER_LEN];
  } hdr;
};

class Http2ClientSession : public ProxyClientSession
{
public:
  Http2ClientSession();

  typedef int (Http2ClientSession::*SessionHandler)(int, void *);

  // Implement ProxyClientSession interface.
  void start();
  void destroy();
  void new_connection(NetVConnection * new_vc, MIOBuffer * iobuf, IOBufferReader * reader, bool backdoor);

  // Implement VConnection interface.
  VIO * do_io_read(Continuation * c, int64_t nbytes = INT64_MAX, MIOBuffer * buf = 0);
  VIO * do_io_write(Continuation * c = NULL, int64_t nbytes = INT64_MAX, IOBufferReader * buf = 0, bool owner = false);
  void do_io_close(int lerrno = -1);
  void do_io_shutdown(ShutdownHowTo_t howto);
  void reenable(VIO * vio);

  int64_t connection_id() const {
    return this->con_id;
  }

private:

  Http2ClientSession(Http2ClientSession &); // noncopyable
  Http2ClientSession& operator=(const Http2ClientSession &); // noncopyable

  int main_event_handler(int, void *);

  int state_read_connection_preface(int, void *);
  int state_start_frame_read(int, void *);
  int state_complete_frame_read(int, void *);

  int64_t               con_id;
  SessionHandler        session_handler;
  NetVConnection *      client_vc;
  MIOBuffer *           read_buffer;
  IOBufferReader *      sm_reader;
  MIOBuffer *           write_buffer;
  IOBufferReader *      sm_writer;
  Http2FrameHeader      current_hdr;
  Http2ConnectionState  connection_state;
};

extern ClassAllocator<Http2ClientSession> http2ClientSessionAllocator;

#endif // __HTTP2_CLIENT_SESSION_H__
