package context

import (
	"time"
)

// Since looks up key, which should be a time.Time, and returns the duration
// since that time. If the key is not found, the value returned will be zero.
// This is helpful when inferring metrics related to context execution times.
func Since(ctx Context, key interface{}) time.Duration {
	if startedAt, ok := ctx.Value(key).(time.Time); ok {
		return time.Since(startedAt)
	}
	return 0
}

// GetStringValue returns a string value from the context. The empty string
// will be returned if not found.
func GetStringValue(ctx Context, key interface{}) (value string) {
	if valuev, ok := ctx.Value(key).(string); ok {
		value = valuev
	}
	return value
}


package spdystream

import (
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"sync"
	"time"

	"github.com/docker/spdystream/spdy"
)

var (
	ErrInvalidStreamId   = errors.New("Invalid stream id")
	ErrTimeout           = errors.New("Timeout occured")
	ErrReset             = errors.New("Stream reset")
	ErrWriteClosedStream = errors.New("Write on closed stream")
)

const (
	FRAME_WORKERS = 5
	QUEUE_SIZE    = 50
)

type StreamHandler func(stream *Stream)

type AuthHandler func(header http.Header, slot uint8, parent uint32) bool

type idleAwareFramer struct {
	f              *spdy.Framer
	conn           *Connection
	writeLock      sync.Mutex
	resetChan      chan struct{}
	setTimeoutLock sync.Mutex
	setTimeoutChan chan time.Duration
	timeout        time.Duration
}

func newIdleAwareFramer(framer *spdy.Framer) *idleAwareFramer {
	iaf := &idleAwareFramer{
		f:         framer,
		resetChan: make(chan struct{}, 2),
		// setTimeoutChan needs to be buffered to avoid deadlocks when calling setIdleTimeout at about
		// the same time the connection is being closed
		setTimeoutChan: make(chan time.Duration, 1),
	}
	return iaf
}

func (i *idleAwareFramer) monitor() {
	var (
		timer          *time.Timer
		expired        <-chan time.Time
		resetChan      = i.resetChan
		setTimeoutChan = i.setTimeoutChan
	)
Loop:
	for {
		select {
		case timeout := <-i.setTimeoutChan:
			i.timeout = timeout
			if timeout == 0 {
				if timer != nil {
					timer.Stop()
				}
			} else {
				if timer == nil {
					timer = time.NewTimer(timeout)
					expired = timer.C
				} else {
					timer.Reset(timeout)
				}
			}
		case <-resetChan:
			if timer != nil && i.timeout > 0 {
				timer.Reset(i.timeout)
			}
		case <-expired:
			i.conn.streamCond.L.Lock()
			streams := i.conn.streams
			i.conn.streams = make(map[spdy.StreamId]*Stream)
			i.conn.streamCond.Broadcast()
			i.conn.streamCond.L.Unlock()
			go func() {
				for _, stream := range streams {
					stream.resetStream()
				}
				i.conn.Close()
			}()
		case <-i.conn.closeChan:
			if timer != nil {
				timer.Stop()
			}

			// Start a goroutine to drain resetChan. This is needed because we've seen
			// some unit tests with large numbers of goroutines get into a situation
			// where resetChan fills up, at least 1 call to Write() is still trying to
			// send to resetChan, the connection gets closed, and this case statement
			// attempts to grab the write lock that Write() already has, causing a
			// deadlock.
			//
			// See https://github.com/docker/spdystream/issues/49 for more details.
			go func() {
				for _ = range resetChan {
				}
			}()

			go func() {
				for _ = range setTimeoutChan {
				}
			}()

			i.writeLock.Lock()
			close(resetChan)
			i.resetChan = nil
			i.writeLock.Unlock()

			i.setTimeoutLock.Lock()
			close(i.setTimeoutChan)
			i.setTimeoutChan = nil
			i.setTimeoutLock.Unlock()

			break Loop
		}
	}

	// Drain resetChan
	for _ = range resetChan {
	}
}

func (i *idleAwareFramer) WriteFrame(frame spdy.Frame) error {
	i.writeLock.Lock()
	defer i.writeLock.Unlock()
	if i.resetChan == nil {
		return io.EOF
	}
	err := i.f.WriteFrame(frame)
	if err != nil {
		return err
	}

	i.resetChan <- struct{}{}

	return nil
}

func (i *idleAwareFramer) ReadFrame() (spdy.Frame, error) {
	frame, err := i.f.ReadFrame()
	if err != nil {
		return nil, err
	}

	// resetChan should never be closed since it is only closed
	// when the connection has closed its closeChan. This closure
	// only occurs after all Reads have finished
	// TODO (dmcgowan): refactor relationship into connection
	i.resetChan <- struct{}{}

	return frame, nil
}

func (i *idleAwareFramer) setIdleTimeout(timeout time.Duration) {
	i.setTimeoutLock.Lock()
	defer i.setTimeoutLock.Unlock()

	if i.setTimeoutChan == nil {
		return
	}

	i.setTimeoutChan <- timeout
}

type Connection struct {
	conn   net.Conn
	framer *idleAwareFramer

	closeChan      chan bool
	goneAway       bool
	lastStreamChan chan<- *Stream
	goAwayTimeout  time.Duration
	closeTimeout   time.Duration

	streamLock *sync.RWMutex
	streamCond *sync.Cond
	streams    map[spdy.StreamId]*Stream

	nextIdLock       sync.Mutex
	receiveIdLock    sync.Mutex
	nextStreamId     spdy.StreamId
	receivedStreamId spdy.StreamId

	pingIdLock sync.Mutex
	pingId     uint32
	pingChans  map[uint32]chan error

	shutdownLock sync.Mutex
	shutdownChan chan error
	hasShutdown  bool

	// for testing https://github.com/docker/spdystream/pull/56
	dataFrameHandler func(*spdy.DataFrame) error
}

// NewConnection creates a new spdy connection from an existing
// network connection.
func NewConnection(conn net.Conn, server bool) (*Connection, error) {
	framer, framerErr := spdy.NewFramer(conn, conn)
	if framerErr != nil {
		return nil, framerErr
	}
	idleAwareFramer := newIdleAwareFramer(framer)
	var sid spdy.StreamId
	var rid spdy.StreamId
	var pid uint32
	if server {
		sid = 2
		rid = 1
		pid = 2
	} else {
		sid = 1
		rid = 2
		pid = 1
	}

	streamLock := new(sync.RWMutex)
	streamCond := sync.NewCond(streamLock)

	session := &Connection{
		conn:   conn,
		framer: idleAwareFramer,

		closeChan:     make(chan bool),
		goAwayTimeout: time.Duration(0),
		closeTimeout:  time.Duration(0),

		streamLock:       streamLock,
		streamCond:       streamCond,
		streams:          make(map[spdy.StreamId]*Stream),
		nextStreamId:     sid,
		receivedStreamId: rid,

		pingId:    pid,
		pingChans: make(map[uint32]chan error),

		shutdownChan: make(chan error),
	}
	session.dataFrameHandler = session.handleDataFrame
	idleAwareFramer.conn = session
	go idleAwareFramer.monitor()

	return session, nil
}

// Ping sends a ping frame across the connection and
// returns the response time
func (s *Connection) Ping() (time.Duration, error) {
	pid := s.pingId
	s.pingIdLock.Lock()
	if s.pingId > 0x7ffffffe {
		s.pingId = s.pingId - 0x7ffffffe
	} else {
		s.pingId = s.pingId + 2
	}
	s.pingIdLock.Unlock()
	pingChan := make(chan error)
	s.pingChans[pid] = pingChan
	defer delete(s.pingChans, pid)

	frame := &spdy.PingFrame{Id: pid}
	startTime := time.Now()
	writeErr := s.framer.WriteFrame(frame)
	if writeErr != nil {
		return time.Duration(0), writeErr
	}
	select {
	case <-s.closeChan:
		return time.Duration(0), errors.New("connection closed")
	case err, ok := <-pingChan:
		if ok && err != nil {
			return time.Duration(0), err
		}
		break
	}
	return time.Now().Sub(startTime), nil
}

// Serve handles frames sent from the server, including reply frames
// which are needed to fully initiate connections.  Both clients and servers
// should call Serve in a separate goroutine before creating streams.
func (s *Connection) Serve(newHandler StreamHandler) {
	// use a WaitGroup to wait for all frames to be drained after receiving
	// go-away.
	var wg sync.WaitGroup

	// Parition queues to ensure stream frames are handled
	// by the same worker, ensuring order is maintained
	frameQueues := make([]*PriorityFrameQueue, FRAME_WORKERS)
	for i := 0; i < FRAME_WORKERS; i++ {
		frameQueues[i] = NewPriorityFrameQueue(QUEUE_SIZE)

		// Ensure frame queue is drained when connection is closed
		go func(frameQueue *PriorityFrameQueue) {
			<-s.closeChan
			frameQueue.Drain()
		}(frameQueues[i])

		wg.Add(1)
		go func(frameQueue *PriorityFrameQueue) {
			// let the WaitGroup know this worker is done
			defer wg.Done()

			s.frameHandler(frameQueue, newHandler)
		}(frameQueues[i])
	}

	var (
		partitionRoundRobin int
		goAwayFrame         *spdy.GoAwayFrame
	)
Loop:
	for {
		readFrame, err := s.framer.ReadFrame()
		if err != nil {
			if err != io.EOF {
				fmt.Errorf("frame read error: %s", err)
			} else {
				debugMessage("(%p) EOF received", s)
			}
			break
		}
		var priority uint8
		var partition int
		switch frame := readFrame.(type) {
		case *spdy.SynStreamFrame:
			if s.checkStreamFrame(frame) {
				priority = frame.Priority
				partition = int(frame.StreamId % FRAME_WORKERS)
				debugMessage("(%p) Add stream frame: %d ", s, frame.StreamId)
				s.addStreamFrame(frame)
			} else {
				debugMessage("(%p) Rejected stream frame: %d ", s, frame.StreamId)
				continue
			}
		case *spdy.SynReplyFrame:
			priority = s.getStreamPriority(frame.StreamId)
			partition = int(frame.StreamId % FRAME_WORKERS)
		case *spdy.DataFrame:
			priority = s.getStreamPriority(frame.StreamId)
			partition = int(frame.StreamId % FRAME_WORKERS)
		case *spdy.RstStreamFrame:
			priority = s.getStreamPriority(frame.StreamId)
			partition = int(frame.StreamId % FRAME_WORKERS)
		case *spdy.HeadersFrame:
			priority = s.getStreamPriority(frame.StreamId)
			partition = int(frame.StreamId % FRAME_WORKERS)
		case *spdy.PingFrame:
			priority = 0
			partition = partitionRoundRobin
			partitionRoundRobin = (partitionRoundRobin + 1) % FRAME_WORKERS
		case *spdy.GoAwayFrame:
			// hold on to the go away frame and exit the loop
			goAwayFrame = frame
			break Loop
		default:
			priority = 7
			partition = partitionRoundRobin
			partitionRoundRobin = (partitionRoundRobin + 1) % FRAME_WORKERS
		}
		frameQueues[partition].Push(readFrame, priority)
	}
	close(s.closeChan)

	// wait for all frame handler workers to indicate they've drained their queues
	// before handling the go away frame
	wg.Wait()

	if goAwayFrame != nil {
		s.handleGoAwayFrame(goAwayFrame)
	}

	// now it's safe to close remote channels and empty s.streams
	s.streamCond.L.Lock()
	// notify streams that they're now closed, which will
	// unblock any stream Read() calls
	for _, stream := range s.streams {
		stream.closeRemoteChannels()
	}
	s.streams = make(map[spdy.StreamId]*Stream)
	s.streamCond.Broadcast()
	s.streamCond.L.Unlock()
}

func (s *Connection) frameHandler(frameQueue *PriorityFrameQueue, newHandler StreamHandler) {
	for {
		popFrame := frameQueue.Pop()
		if popFrame == nil {
			return
		}

		var frameErr error
		switch frame := popFrame.(type) {
		case *spdy.SynStreamFrame:
			frameErr = s.handleStreamFrame(frame, newHandler)
		case *spdy.SynReplyFrame:
			frameErr = s.handleReplyFrame(frame)
		case *spdy.DataFrame:
			frameErr = s.dataFrameHandler(frame)
		case *spdy.RstStreamFrame:
			frameErr = s.handleResetFrame(frame)
		case *spdy.HeadersFrame:
			frameErr = s.handleHeaderFrame(frame)
		case *spdy.PingFrame:
			frameErr = s.handlePingFrame(frame)
		case *spdy.GoAwayFrame:
			frameErr = s.handleGoAwayFrame(frame)
		default:
			frameErr = fmt.Errorf("unhandled frame type: %T", frame)
		}

		if frameErr != nil {
			fmt.Errorf("frame handling error: %s", frameErr)
		}
	}
}

func (s *Connection) getStreamPriority(streamId spdy.StreamId) uint8 {
	stream, streamOk := s.getStream(streamId)
	if !streamOk {
		return 7
	}
	return stream.priority
}

func (s *Connection) addStreamFrame(frame *spdy.SynStreamFrame) {
	var parent *Stream
	if frame.AssociatedToStreamId != spdy.StreamId(0) {
		parent, _ = s.getStream(frame.AssociatedToStreamId)
	}

	stream := &Stream{
		streamId:   frame.StreamId,
		parent:     parent,
		conn:       s,
		startChan:  make(chan error),
		headers:    frame.Headers,
		finished:   (frame.CFHeader.Flags & spdy.ControlFlagUnidirectional) != 0x00,
		replyCond:  sync.NewCond(new(sync.Mutex)),
		dataChan:   make(chan []byte),
		headerChan: make(chan http.Header),
		closeChan:  make(chan bool),
	}
	if frame.CFHeader.Flags&spdy.ControlFlagFin != 0x00 {
		stream.closeRemoteChannels()
	}

	s.addStream(stream)
}

// checkStreamFrame checks to see if a stream frame is allowed.
// If the stream is invalid, then a reset frame with protocol error
// will be returned.
func (s *Connection) checkStreamFrame(frame *spdy.SynStreamFrame) bool {
	s.receiveIdLock.Lock()
	defer s.receiveIdLock.Unlock()
	if s.goneAway {
		return false
	}
	validationErr := s.validateStreamId(frame.StreamId)
	if validationErr != nil {
		go func() {
			resetErr := s.sendResetFrame(spdy.ProtocolError, frame.StreamId)
			if resetErr != nil {
				fmt.Errorf("reset error: %s", resetErr)
			}
		}()
		return false
	}
	return true
}

func (s *Connection) handleStreamFrame(frame *spdy.SynStreamFrame, newHandler StreamHandler) error {
	stream, ok := s.getStream(frame.StreamId)
	if !ok {
		return fmt.Errorf("Missing stream: %d", frame.StreamId)
	}

	newHandler(stream)

	return nil
}

func (s *Connection) handleReplyFrame(frame *spdy.SynReplyFrame) error {
	debugMessage("(%p) Reply frame received for %d", s, frame.StreamId)
	stream, streamOk := s.getStream(frame.StreamId)
	if !streamOk {
		debugMessage("Reply frame gone away for %d", frame.StreamId)
		// Stream has already gone away
		return nil
	}
	if stream.replied {
		// Stream has already received reply
		return nil
	}
	stream.replied = true

	// TODO Check for error
	if (frame.CFHeader.Flags & spdy.ControlFlagFin) != 0x00 {
		s.remoteStreamFinish(stream)
	}

	close(stream.startChan)

	return nil
}

func (s *Connection) handleResetFrame(frame *spdy.RstStreamFrame) error {
	stream, streamOk := s.getStream(frame.StreamId)
	if !streamOk {
		// Stream has already been removed
		return nil
	}
	s.removeStream(stream)
	stream.closeRemoteChannels()

	if !stream.replied {
		stream.replied = true
		stream.startChan <- ErrReset
		close(stream.startChan)
	}

	stream.finishLock.Lock()
	stream.finished = true
	stream.finishLock.Unlock()

	return nil
}

func (s *Connection) handleHeaderFrame(frame *spdy.HeadersFrame) error {
	stream, streamOk := s.getStream(frame.StreamId)
	if !streamOk {
		// Stream has already gone away
		return nil
	}
	if !stream.replied {
		// No reply received...Protocol error?
		return nil
	}

	// TODO limit headers while not blocking (use buffered chan or goroutine?)
	select {
	case <-stream.closeChan:
		return nil
	case stream.headerChan <- frame.Headers:
	}

	if (frame.CFHeader.Flags & spdy.ControlFlagFin) != 0x00 {
		s.remoteStreamFinish(stream)
	}

	return nil
}

func (s *Connection) handleDataFrame(frame *spdy.DataFrame) error {
	debugMessage("(%p) Data frame received for %d", s, frame.StreamId)
	stream, streamOk := s.getStream(frame.StreamId)
	if !streamOk {
		debugMessage("(%p) Data frame gone away for %d", s, frame.StreamId)
		// Stream has already gone away
		return nil
	}
	if !stream.replied {
		debugMessage("(%p) Data frame not replied %d", s, frame.StreamId)
		// No reply received...Protocol error?
		return nil
	}

	debugMessage("(%p) (%d) Data frame handling", stream, stream.streamId)
	if len(frame.Data) > 0 {
		stream.dataLock.RLock()
		select {
		case <-stream.closeChan:
			debugMessage("(%p) (%d) Data frame not sent (stream shut down)", stream, stream.streamId)
		case stream.dataChan <- frame.Data:
			debugMessage("(%p) (%d) Data frame sent", stream, stream.streamId)
		}
		stream.dataLock.RUnlock()
	}
	if (frame.Flags & spdy.DataFlagFin) != 0x00 {
		s.remoteStreamFinish(stream)
	}
	return nil
}

func (s *Connection) handlePingFrame(frame *spdy.PingFrame) error {
	if s.pingId&0x01 != frame.Id&0x01 {
		return s.framer.WriteFrame(frame)
	}
	pingChan, pingOk := s.pingChans[frame.Id]
	if pingOk {
		close(pingChan)
	}
	return nil
}

func (s *Connection) handleGoAwayFrame(frame *spdy.GoAwayFrame) error {
	debugMessage("(%p) Go away received", s)
	s.receiveIdLock.Lock()
	if s.goneAway {
		s.receiveIdLock.Unlock()
		return nil
	}
	s.goneAway = true
	s.receiveIdLock.Unlock()

	if s.lastStreamChan != nil {
		stream, _ := s.getStream(frame.LastGoodStreamId)
		go func() {
			s.lastStreamChan <- stream
		}()
	}

	// Do not block frame handler waiting for closure
	go s.shutdown(s.goAwayTimeout)

	return nil
}

func (s *Connection) remoteStreamFinish(stream *Stream) {
	stream.closeRemoteChannels()

	stream.finishLock.Lock()
	if stream.finished {
		// Stream is fully closed, cleanup
		s.removeStream(stream)
	}
	stream.finishLock.Unlock()
}

// CreateStream creates a new spdy stream using the parameters for
// creating the stream frame.  The stream frame will be sent upon
// calling this function, however this function does not wait for
// the reply frame.  If waiting for the reply is desired, use
// the stream Wait or WaitTimeout function on the stream returned
// by this function.
func (s *Connection) CreateStream(headers http.Header, parent *Stream, fin bool) (*Stream, error) {
	// MUST synchronize stream creation (all the way to writing the frame)
	// as stream IDs **MUST** increase monotonically.
	s.nextIdLock.Lock()
	defer s.nextIdLock.Unlock()

	streamId := s.getNextStreamId()
	if streamId == 0 {
		return nil, fmt.Errorf("Unable to get new stream id")
	}

	stream := &Stream{
		streamId:   streamId,
		parent:     parent,
		conn:       s,
		startChan:  make(chan error),
		headers:    headers,
		dataChan:   make(chan []byte),
		headerChan: make(chan http.Header),
		closeChan:  make(chan bool),
	}

	debugMessage("(%p) (%p) Create stream", s, stream)

	s.addStream(stream)

	return stream, s.sendStream(stream, fin)
}

func (s *Connection) shutdown(closeTimeout time.Duration) {
	// TODO Ensure this isn't called multiple times
	s.shutdownLock.Lock()
	if s.hasShutdown {
		s.shutdownLock.Unlock()
		return
	}
	s.hasShutdown = true
	s.shutdownLock.Unlock()

	var timeout <-chan time.Time
	if closeTimeout > time.Duration(0) {
		timeout = time.After(closeTimeout)
	}
	streamsClosed := make(chan bool)

	go func() {
		s.streamCond.L.Lock()
		for len(s.streams) > 0 {
			debugMessage("Streams opened: %d, %#v", len(s.streams), s.streams)
			s.streamCond.Wait()
		}
		s.streamCond.L.Unlock()
		close(streamsClosed)
	}()

	var err error
	select {
	case <-streamsClosed:
		// No active streams, close should be safe
		err = s.conn.Close()
	case <-timeout:
		// Force ungraceful close
		err = s.conn.Close()
		// Wait for cleanup to clear active streams
		<-streamsClosed
	}

	if err != nil {
		duration := 10 * time.Minute
		time.AfterFunc(duration, func() {
			select {
			case err, ok := <-s.shutdownChan:
				if ok {
					fmt.Errorf("Unhandled close error after %s: %s", duration, err)
				}
			default:
			}
		})
		s.shutdownChan <- err
	}
	close(s.shutdownChan)

	return
}

// Closes spdy connection by sending GoAway frame and initiating shutdown
func (s *Connection) Close() error {
	s.receiveIdLock.Lock()
	if s.goneAway {
		s.receiveIdLock.Unlock()
		return nil
	}
	s.goneAway = true
	s.receiveIdLock.Unlock()

	var lastStreamId spdy.StreamId
	if s.receivedStreamId > 2 {
		lastStreamId = s.receivedStreamId - 2
	}

	goAwayFrame := &spdy.GoAwayFrame{
		LastGoodStreamId: lastStreamId,
		Status:           spdy.GoAwayOK,
	}

	err := s.framer.WriteFrame(goAwayFrame)
	if err != nil {
		return err
	}

	go s.shutdown(s.closeTimeout)

	return nil
}

// CloseWait closes the connection and waits for shutdown
// to finish.  Note the underlying network Connection
// is not closed until the end of shutdown.
func (s *Connection) CloseWait() error {
	closeErr := s.Close()
	if closeErr != nil {
		return closeErr
	}
	shutdownErr, ok := <-s.shutdownChan
	if ok {
		return shutdownErr
	}
	return nil
}

// Wait waits for the connection to finish shutdown or for
// the wait timeout duration to expire.  This needs to be
// called either after Close has been called or the GOAWAYFRAME
// has been received.  If the wait timeout is 0, this function
// will block until shutdown finishes.  If wait is never called
// and a shutdown error occurs, that error will be logged as an
// unhandled error.
func (s *Connection) Wait(waitTimeout time.Duration) error {
	var timeout <-chan time.Time
	if waitTimeout > time.Duration(0) {
		timeout = time.After(waitTimeout)
	}

	select {
	case err, ok := <-s.shutdownChan:
		if ok {
			return err
		}
	case <-timeout:
		return ErrTimeout
	}
	return nil
}

// NotifyClose registers a channel to be called when the remote
// peer inidicates connection closure.  The last stream to be
// received by the remote will be sent on the channel.  The notify
// timeout will determine the duration between go away received
// and the connection being closed.
func (s *Connection) NotifyClose(c chan<- *Stream, timeout time.Duration) {
	s.goAwayTimeout = timeout
	s.lastStreamChan = c
}

// SetCloseTimeout sets the amount of time close will wait for
// streams to finish before terminating the underlying network
// connection.  Setting the timeout to 0 will cause close to
// wait forever, which is the default.
func (s *Connection) SetCloseTimeout(timeout time.Duration) {
	s.closeTimeout = timeout
}

// SetIdleTimeout sets the amount of time the connection may sit idle before
// it is forcefully terminated.
func (s *Connection) SetIdleTimeout(timeout time.Duration) {
	s.framer.setIdleTimeout(timeout)
}

func (s *Connection) sendHeaders(headers http.Header, stream *Stream, fin bool) error {
	var flags spdy.ControlFlags
	if fin {
		flags = spdy.ControlFlagFin
	}

	headerFrame := &spdy.HeadersFrame{
		StreamId: stream.streamId,
		Headers:  headers,
		CFHeader: spdy.ControlFrameHeader{Flags: flags},
	}

	return s.framer.WriteFrame(headerFrame)
}

func (s *Connection) sendReply(headers http.Header, stream *Stream, fin bool) error {
	var flags spdy.ControlFlags
	if fin {
		flags = spdy.ControlFlagFin
	}

	replyFrame := &spdy.SynReplyFrame{
		StreamId: stream.streamId,
		Headers:  headers,
		CFHeader: spdy.ControlFrameHeader{Flags: flags},
	}

	return s.framer.WriteFrame(replyFrame)
}

func (s *Connection) sendResetFrame(status spdy.RstStreamStatus, streamId spdy.StreamId) error {
	resetFrame := &spdy.RstStreamFrame{
		StreamId: streamId,
		Status:   status,
	}

	return s.framer.WriteFrame(resetFrame)
}

func (s *Connection) sendReset(status spdy.RstStreamStatus, stream *Stream) error {
	return s.sendResetFrame(status, stream.streamId)
}

func (s *Connection) sendStream(stream *Stream, fin bool) error {
	var flags spdy.ControlFlags
	if fin {
		flags = spdy.ControlFlagFin
		stream.finished = true
	}

	var parentId spdy.StreamId
	if stream.parent != nil {
		parentId = stream.parent.streamId
	}

	streamFrame := &spdy.SynStreamFrame{
		StreamId:             spdy.StreamId(stream.streamId),
		AssociatedToStreamId: spdy.StreamId(parentId),
		Headers:              stream.headers,
		CFHeader:             spdy.ControlFrameHeader{Flags: flags},
	}

	return s.framer.WriteFrame(streamFrame)
}

// getNextStreamId returns the next sequential id
// every call should produce a unique value or an error
func (s *Connection) getNextStreamId() spdy.StreamId {
	sid := s.nextStreamId
	if sid > 0x7fffffff {
		return 0
	}
	s.nextStreamId = s.nextStreamId + 2
	return sid
}

// PeekNextStreamId returns the next sequential id and keeps the next id untouched
func (s *Connection) PeekNextStreamId() spdy.StreamId {
	sid := s.nextStreamId
	return sid
}

func (s *Connection) validateStreamId(rid spdy.StreamId) error {
	if rid > 0x7fffffff || rid < s.receivedStreamId {
		return ErrInvalidStreamId
	}
	s.receivedStreamId = rid + 2
	return nil
}

func (s *Connection) addStream(stream *Stream) {
	s.streamCond.L.Lock()
	s.streams[stream.streamId] = stream
	debugMessage("(%p) (%p) Stream added, broadcasting: %d", s, stream, stream.streamId)
	s.streamCond.Broadcast()
	s.streamCond.L.Unlock()
}

func (s *Connection) removeStream(stream *Stream) {
	s.streamCond.L.Lock()
	delete(s.streams, stream.streamId)
	debugMessage("(%p) (%p) Stream removed, broadcasting: %d", s, stream, stream.streamId)
	s.streamCond.Broadcast()
	s.streamCond.L.Unlock()
}

func (s *Connection) getStream(streamId spdy.StreamId) (stream *Stream, ok bool) {
	s.streamLock.RLock()
	stream, ok = s.streams[streamId]
	s.streamLock.RUnlock()
	return
}

// FindStream looks up the given stream id and either waits for the
// stream to be found or returns nil if the stream id is no longer
// valid.
func (s *Connection) FindStream(streamId uint32) *Stream {
	var stream *Stream
	var ok bool
	s.streamCond.L.Lock()
	stream, ok = s.streams[spdy.StreamId(streamId)]
	debugMessage("(%p) Found stream %d? %t", s, spdy.StreamId(streamId), ok)
	for !ok && streamId >= uint32(s.receivedStreamId) {
		s.streamCond.Wait()
		stream, ok = s.streams[spdy.StreamId(streamId)]
	}
	s.streamCond.L.Unlock()
	return stream
}

func (s *Connection) CloseChan() <-chan bool {
	return s.closeChan
}


#!/bin/bash

SCRIPT_NAME="$(basename "${BASH_SOURCE:-$0}")"
readonly SCRIPT_NAME

# Dockerコンテナ系の定数
readonly CONTAINER_DIR_PREFIX=/work

# 着色系ANSIエスケープシーケンス
readonly RED=$'\x1b[31m'
readonly GREEN=$'\x1b[32m'
readonly RESET=$'\x1b[m'

# デフォルトの静的解析対象
readonly DEFAULT_TARGET_FILES=(*.sh bin/*)

test_count=0
err_count=0

main() {
  # 引数なしのときはヘルプを出力して終了
  if [[ $# -lt 1 ]]; then
    usage >&2
    return 1
  fi

  while ((0 < $#)); do
    local opt=$1
    shift

    case "$opt" in
      help)
        usage
        return
        ;;
      setup)
        # フォーマットとlintに使うDockerイメージを取得
        docker-compose pull formatter linter
        ;;
      format)
        # コードフォーマットにかける
        overwrite=""
        cmd_format "$@"
        print_check_result
        return $?
        ;;
      format-save)
        # コードフォーマットにかけて上書き保存
        overwrite="-w"
        cmd_format "$@"
        print_check_result
        return $?
        ;;
      lint)
        # 静的解析
        cmd_lint "$@"
        print_check_result
        return $?
        ;;
      all)
        # コードフォーマット＋静的解析
        cmd_format "$@"
        cmd_lint "$@"
        print_check_result
        return $?
        ;;
      *)
        usage >&2
        return 1
        ;;
    esac
  done
}

usage() {
  cat << EOS
$SCRIPT_NAME はプロジェクト内のシェルスクリプトのコードスタイルチェックをします。

Usage:
    $SCRIPT_NAME <command>

Available commands:
    help                      このヘルプを出力する。
    setup                     リントツールをセットアップする。
    format      [files...]    コードフォーマットにかける。
    format-save [files...]    コードフォーマットして上書き保存する。
    lint        [files...]    リントにかける。
    all                       プロジェクト全体をフォーマットとリントにかける。
EOS
}

## フォーマットにかけるサブコマンド。
cmd_format() {
  # 引数（ファイル）の指定があればそのファイルを解析する
  # なければデフォルト指定（プロジェクト全体）のファイルを解析する
  local files=("${DEFAULT_TARGET_FILES[@]}")
  if [[ 0 -lt $# ]]; then
    files=("$@")
  fi

  # テストするファイルの件数を加算
  test_count=$((test_count + ${#files[@]}))

  # コンテナ内のマウントディレクトリのフルパスに変更
  local fullpath_files=()
  for f in "${files[@]}"; do
    fullpath_files+=("$CONTAINER_DIR_PREFIX/$f")
  done

  run_shfmt "${fullpath_files[@]}"
}

## フォーマットにかける。
run_shfmt() {
  local files=("$@")
  local ret
  docker-compose run formatter $overwrite "${files[@]}"
  ret=$?
  if [[ "$ret" -ne 0 ]]; then
    err_count=$((err_count + 1))
  fi
}

## 静的解析にかけるサブコマンド。
cmd_lint() {
  # 引数（ファイル）の指定があればそのファイルを解析する
  # なければデフォルト指定（プロジェクト全体）のファイルを解析する
  local files=("${DEFAULT_TARGET_FILES[@]}")
  if [[ 0 -lt $# ]]; then
    files=("$@")
  fi

  # テストするファイルの件数を加算
  test_count=$((test_count + ${#files[@]}))

  # コンテナ内のマウントディレクトリのフルパスに変更
  local fullpath_files=()
  for f in "${files[@]}"; do
    # unko.puzzleはスキップする
    if [[ "$f" =~ ^.*unko.puzzle$ ]]; then
      continue
    fi
    fullpath_files+=("$CONTAINER_DIR_PREFIX/$f")
  done

  run_shellcheck "${fullpath_files[@]}"
}

## 静的解析にかける。
run_shellcheck() {
  local files=("$@")
  local ret
  docker-compose run linter "${files[@]}"
  ret=$?
  if [[ "$ret" -ne 0 ]]; then
    err_count=$((err_count + 1))
  fi
}

## テストの結果を出力する。
print_check_result() {
  echo "--------------------------------------------------------------------------------"
  if [[ "$err_count" -lt 1 ]]; then
    echo -e "[ ${GREEN}PASS${RESET} ] ($test_count/$test_count) all lint were passed."
    return
  else
    echo -e "[ ${RED}FAIL${RESET} ] lint were failed."
    return 1
  fi
}

main ${1+"$@"}
exit $?


# SafariBooks
Download and generate *EPUB* of your favorite books from [*Safari Books Online*](https://www.safaribooksonline.com) library.
I'm not responsible for the use of this program, this is only for *personal* and *educational* purpose.
Before any usage please read the *O'Reilly*'s [Terms of Service](https://learning.oreilly.com/terms/).

## Overview:
  * [Requirements & Setup](#requirements--setup)
  * [Usage](#usage)
  * [Example: Download *Test-Driven Development with Python, 2nd Edition*](#download-test-driven-development-with-python-2nd-edition)
  * [Example: Use or not the `--kindle` option](#use-or-not-the---kindle-option)

## Requirements & Setup:
First of all, it requires `python3` and `pip3` or `pipenv` to be installed.
```shell
$ git clone https://github.com/lorenzodifuccia/safaribooks.git
Cloning into 'safaribooks'...

$ cd safaribooks/
$ pip3 install -r requirements.txt

OR

$ pipenv install && pipenv shell
```

The program depends of only two **Python _3_** modules:
```python3
lxml>=4.1.1
requests>=2.20.0
```

## Usage:
It's really simple to use, just choose a book from the library and replace in the following command:
  * X-es with its ID,
  * `email:password` with your own.

```shell
$ python3 safaribooks.py --cred "account_mail@mail.com:password01" XXXXXXXXXXXXX
```

The ID is the digits that you find in the URL of the book description page:
`https://www.safaribooksonline.com/library/view/book-name/XXXXXXXXXXXXX/`
Like: `https://www.safaribooksonline.com/library/view/test-driven-development-with/9781491958698/`

#### Program options:
```shell
$ python3 safaribooks.py --help
usage: safaribooks.py [--cred <EMAIL:PASS> | --login] [--no-cookies]
                      [--kindle] [--preserve-log] [--help]
                      <BOOK ID>

Download and generate an EPUB of your favorite books from Safari Books Online.

positional arguments:
  <BOOK ID>            Book digits ID that you want to download. You can find
                       it in the URL (X-es):
                       `https://learning.oreilly.com/library/view/book-
                       name/XXXXXXXXXXXXX/`

optional arguments:
  --cred <EMAIL:PASS>  Credentials used to perform the auth login on Safari
                       Books Online. Es. ` --cred
                       "account_mail@mail.com:password01" `.
  --login              Prompt for credentials used to perform the auth login
                       on Safari Books Online.
  --no-cookies         Prevent your session data to be saved into
                       `cookies.json` file.
  --kindle             Add some CSS rules that block overflow on `table` and
                       `pre` elements. Use this option if you're going to
                       export the EPUB to E-Readers like Amazon Kindle.
  --preserve-log       Leave the `info_XXXXXXXXXXXXX.log` file even if there
                       isn't any error.
  --help               Show this help message.
```

The first time you use the program, you'll have to specify your Safari Books Online account credentials (look [`here`](/../../issues/15) for special character).
The next times you'll download a book, before session expires, you can omit the credential, because the program save your session cookies in a file called `cookies.json`.
For **SSO**, please use the `sso_cookies.py` program in order to create the `cookies.json` file from the SSO cookies retrieved by your browser session (please follow [`these steps`](/../../issues/150#issuecomment-555423085)).

Pay attention if you use a shared PC, because everyone that has access to your files can steal your session.
If you don't want to cache the cookies, just use the `--no-cookies` option and provide all time your credential through the `--cred` option or the more safe `--login` one: this will prompt you for credential during the script execution.

You can configure proxies by setting on your system the environment variable `HTTPS_PROXY` or using the `USE_PROXY` directive into the script.

**Important**: since the script only download HTML pages and create a raw EPUB, many of the CSS and XML/HTML directives are wrong for an E-Reader. To ensure best quality of the output, I suggest you to always convert the `EPUB` obtained by the script to standard-`EPUB` with [Calibre](https://calibre-ebook.com/).
You can also use the command-line version of Calibre with `ebook-convert`, e.g.:
```bash
$ ebook-convert "XXXX/safaribooks/Books/Test-Driven Development with Python 2nd Edition (9781491958698)/9781491958698.epub" "XXXX/safaribooks/Books/Test-Driven Development with Python 2nd Edition (9781491958698)/9781491958698_CLEAR.epub"
```
After the execution, you can read the `9781491958698_CLEAR.epub` in every E-Reader and delete all other files.

The program offers also an option to ensure best compatibilities for who wants to export the `EPUB` to E-Readers like Amazon Kindle: `--kindle`, it blocks overflow on `table` and `pre` elements (see [example](#use-or-not-the---kindle-option)).
In this case, I suggest you to convert the `EPUB` to `AZW3` with Calibre or to `MOBI`, remember in this case to select `Ignore margins` in the conversion options:

![Calibre IgnoreMargins](https://github.com/lorenzodifuccia/cloudflare/raw/master/Images/safaribooks/safaribooks_calibre_IgnoreMargins.png "Select Ignore margins")

## Examples:
  * ## Download [Test-Driven Development with Python, 2nd Edition](https://www.safaribooksonline.com/library/view/test-driven-development-with/9781491958698/):
    ```shell
    $ python3 safaribooks.py --cred "my_email@gmail.com:MyPassword1!" 9781491958698

           ____     ___         _
          / __/__ _/ _/__ _____(_)
         _\ \/ _ `/ _/ _ `/ __/ /
        /___/\_,_/_/ \_,_/_/ /_/
          / _ )___  ___  / /__ ___
         / _  / _ \/ _ \/  '_/(_-<
        /____/\___/\___/_/\_\/___/

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    [-] Logging into Safari Books Online...
    [*] Retrieving book info...
    [-] Title: Test-Driven Development with Python, 2nd Edition
    [-] Authors: Harry J.W. Percival
    [-] Identifier: 9781491958698
    [-] ISBN: 9781491958704
    [-] Publishers: O'Reilly Media, Inc.
    [-] Rights: Copyright © O'Reilly Media, Inc.
    [-] Description: By taking you through the development of a real web application
    from beginning to end, the second edition of this hands-on guide demonstrates the
    practical advantages of test-driven development (TDD) with Python. You’ll learn
    how to write and run tests before building each part of your app, and then develop
    the minimum amount of code required to pass those tests. The result? Clean code
    that works.In the process, you’ll learn the basics of Django, Selenium, Git,
    jQuery, and Mock, along with curre...
    [-] Release Date: 2017-08-18
    [-] URL: https://learning.oreilly.com/library/view/test-driven-development-with/9781491958698/
    [*] Retrieving book chapters...
    [*] Output directory:
        /XXXX/safaribooks/Books/Test-Driven Development with Python 2nd Edition (9781491958698)
    [-] Downloading book contents... (53 chapters)
        [#####################################################################] 100%
    [-] Downloading book CSSs... (2 files)
        [#####################################################################] 100%
    [-] Downloading book images... (142 files)
        [#####################################################################] 100%
    [-] Creating EPUB file...
    [*] Done: /XXXX/safaribooks/Books/Test-Driven Development with Python 2nd Edition
    (9781491958698)/9781491958698.epub

        If you like it, please * this project on GitHub to make it known:
            https://github.com/lorenzodifuccia/safaribooks
        e don't forget to renew your Safari Books Online subscription:
            https://learning.oreilly.com

    [!] Bye!!
    ```
     The result will be (opening the `EPUB` file with Calibre):

    ![Book Appearance](https://github.com/lorenzodifuccia/cloudflare/raw/master/Images/safaribooks/safaribooks_example01_TDD.png "Book opened with Calibre")

  * ## Use or not the `--kindle` option:
    ```bash
    $ python3 safaribooks.py --kindle 9781491958698
    ```
    On the right, the book created with `--kindle` option, on the left without (default):

    ![NoKindle Option](https://github.com/lorenzodifuccia/cloudflare/raw/master/Images/safaribooks/safaribooks_example02_NoKindle.png "Version compare")

---

## Thanks!!
For any kind of problem, please don't hesitate to open an issue here on *GitHub*.

*Lorenzo Di Fuccia*


/*
Copyright The Infusion copyright holders
See the AUTHORS.md file at the top-level directory of this distribution and at
https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.

Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.

You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/

var fluid_3_0_0 = fluid_3_0_0 || {};

(function ($, fluid) {
    "use strict";

    fluid.registerNamespace("fluid.pager");

    /******************
     * Pager Bar View *
     ******************/
    // TODO: Convert one day to the "visibility model" system (FLUID-4928)
    fluid.pager.updateStyles = function (pageListThat, newModel, oldModel) {
        if (oldModel && oldModel.pageIndex !== undefined) {
            var oldLink = pageListThat.pageLinks.eq(oldModel.pageIndex);
            oldLink.removeClass(pageListThat.options.styles.currentPage);
        }
        var pageLink = pageListThat.pageLinks.eq(newModel.pageIndex);
        pageLink.addClass(pageListThat.options.styles.currentPage);
    };

    fluid.pager.bindLinkClick = function (link, initiatePageChange, eventArg) {
        link.off("click.fluid.pager");
        link.on("click.fluid.pager", function () {
            initiatePageChange.fire(eventArg);
            return false;
        });
    };

    // 10 -> 1, 11 -> 2
    fluid.pager.computePageCount = function (model) {
        return Math.max(1, Math.floor((model.totalRange - 1) / model.pageSize) + 1);
    };

    fluid.pager.computePageLimit = function (model) {
        return Math.min(model.totalRange, (model.pageIndex + 1) * model.pageSize);
    };

    fluid.pager.bindLinkClicks = function (pageLinks, initiatePageChange) {
        fluid.each(pageLinks, function (pageLink, i) {
            fluid.pager.bindLinkClick($(pageLink), initiatePageChange, {pageIndex: i});
        });
    };

    // Abstract grade representing all pageLists
    fluid.defaults("fluid.pager.pageList", {
        gradeNames: ["fluid.viewComponent"]
    });

    fluid.defaults("fluid.pager.directPageList", {
        gradeNames: ["fluid.pager.pageList"],
        listeners: {
            onCreate: {
                funcName: "fluid.pager.bindLinkClicks",
                args: ["{that}.pageLinks", "{pager}.events.initiatePageChange"]
            }
        },
        modelListeners: {
            "{pager}.model": "fluid.pager.updateStyles({that}, {change}.value, {change}.oldValue)"
        },
        members: {
            pageLinks: "{that}.dom.pageLinks",
            defaultModel: {
                totalRange: "{that}.pageLinks.length"
            }
        }
    });

    fluid.pager.everyPageStrategy = fluid.iota;

    fluid.pager.gappedPageStrategy = function (locality, midLocality) {
        if (!locality) {
            locality = 3;
        }
        if (!midLocality) {
            midLocality = locality;
        }
        return function (count, first, mid) {
            var togo = [];
            var j = 0;
            var lastSkip = false;
            for (var i = 0; i < count; ++i) {
                if (i < locality || (count - i - 1) < locality || (i >= mid - midLocality && i <= mid + midLocality)) {
                    togo[j++] = i;
                    lastSkip = false;
                } else if (!lastSkip) {
                    togo[j++] = -1;
                    lastSkip = true;
                }
            }
            return togo;
        };
    };

    /**
     * An impl of a page strategy that will always display same number of page links (including skip place holders).
     * @param   {Number} endLinkCount  - The number of elements first and last trunks of elements.
     * @param   {Number} midLinkCount  - The number of elements from beside the selected number.
     * @return {Function} - A paging function.
     * @author  Eric Dalquist
     */
    fluid.pager.consistentGappedPageStrategy = function (endLinkCount, midLinkCount) {
        if (!endLinkCount) {
            endLinkCount = 1;
        }
        if (!midLinkCount) {
            midLinkCount = endLinkCount;
        }
        var endWidth = endLinkCount + 2 + midLinkCount;

        return function (count, first, mid) {
            var pages = [];
            var anchoredLeft = mid < endWidth;
            var anchoredRight = mid >= count - endWidth;
            var anchoredEndWidth = endWidth + midLinkCount;
            var midStart = mid - midLinkCount;
            var midEnd = mid + midLinkCount;
            var lastSkip = false;

            for (var page = 0; page < count; page++) {
                if (page < endLinkCount || // start pages
                        count - page <= endLinkCount || // end pages
                        (anchoredLeft && page < anchoredEndWidth) || // pages if no skipped pages between start and mid
                        (anchoredRight && page >= count - anchoredEndWidth) || // pages if no skipped pages between mid and end
                        (page >= midStart && page <= midEnd) // pages around the mid
                        ) {
                    pages.push(page);
                    lastSkip = false;
                } else if (!lastSkip) {
                    pages.push(-1);
                    lastSkip = true;
                }
            }
            return pages;
        };
    };

    fluid.registerNamespace("fluid.pager.renderedPageList");

    fluid.pager.renderedPageList.assembleComponent = function (page, isCurrent, initiatePageChange, currentPageStyle, currentPageIndexMsg) {
        var obj = {
            ID: "page-link:link",
            localID: page + 1,
            value: page + 1,
            pageIndex: page,
            decorators: [
                {
                    identify: "pageLink:" + page
                },
                {
                    type: "jQuery",
                    func: "click",
                    args: function (event) {
                        initiatePageChange.fire({pageIndex: page});
                        event.preventDefault();
                    }
                }
            ]
        };

        if (isCurrent) {
            obj.current = true;
            obj.decorators = obj.decorators.concat([
                {
                    type: "addClass",
                    classes: currentPageStyle
                },
                {
                    type: "jQuery",
                    func: "attr",
                    args: ["aria-label", currentPageIndexMsg]
                }
            ]);
        }

        return obj;
    };

    fluid.pager.renderedPageList.onModelChange = function (that, newModel) {
        function pageToComponent(current) {
            return function (page) {
                return page === -1 ? {
                    ID: "page-link:skip"
                } : that.assembleComponent(page, page === current);
            };
        }

        var pages = that.options.pageStrategy(newModel.pageCount, 0, newModel.pageIndex);
        var pageTree = fluid.transform(pages, pageToComponent(newModel.pageIndex));
        if (pageTree.length > 1) {
            pageTree[pageTree.length - 1].value = pageTree[pageTree.length - 1].value + that.options.strings.last;
        }
        that.events.onRenderPageLinks.fire(pageTree, newModel);
        that.pageTree = pageTree;
        that.refreshView();
    };

    fluid.pager.renderedPageList.renderLinkBody = function (linkBody, rendererOptions) {
        if (linkBody) {
            rendererOptions.cutpoints.push({
                id: "payload-component",
                selector: linkBody
            });
        }
    };

    fluid.defaults("fluid.pager.renderedPageList", {
        gradeNames: ["fluid.pager.pageList", "fluid.rendererComponent"],
        rendererOptions: {
            idMap: {},
            cutpoints: [
                {
                    id: "page-link:link",
                    selector: "{that}.options.selectors.pageLinks"
                },
                {
                    id: "page-link:skip",
                    selector: "{that}.options.selectors.pageLinkSkip"
                }
            ]
        },
        rendererFnOptions: {
            noexpand: true,
            templateSource: {node: "{that}.dom.root"},
            renderTarget: "{that}.dom.root"
        },
        events: {
            onRenderPageLinks: "{pager}.events.onRenderPageLinks"
        },
        listeners: {
            onCreate: {
                funcName: "fluid.pager.renderedPageList.renderLinkBody",
                args: ["{that}.options.linkBody", "{that}.options.rendererOptions"]
            }
        },
        modelListeners: {
            "{pager}.model": "fluid.pager.renderedPageList.onModelChange({that}, {change}.value)"
        },
        invokers: {
            produceTree: {
                funcName: "fluid.identity",
                args: "{that}.pageTree"
            },
            assembleComponent: {
                funcName: "fluid.pager.renderedPageList.assembleComponent",
                args: ["{arguments}.0", "{arguments}.1",
                    "{pager}.events.initiatePageChange", "{pagerBar}.options.styles.currentPage", "{pagerBar}.options.strings.currentPageIndexMsg"]
            }
        },

        selectors: {
            root: ".flc-pager-links",
            pageLinks: "{pagerBar}.options.selectors.pageLinks",
            pageLinkSkip: "{pagerBar}.options.selectors.pageLinkSkip"
        },
        strings: "{pager}.options.strings",
        linkBody: "a",
        pageStrategy: fluid.pager.everyPageStrategy
    });

    fluid.defaults("fluid.pager.previousNext", {
        gradeNames: ["fluid.viewComponent"],
        members: {
            previous: "{that}.dom.previous",
            next: "{that}.dom.next"
        },
        selectors: {
            previous: ".flc-pager-previous",
            next: ".flc-pager-next"
        },
        listeners: {
            onCreate: [{
                funcName: "fluid.pager.bindLinkClick",
                args: ["{that}.previous", "{pager}.events.initiatePageChange", {relativePage: -1}]
            }, {
                funcName: "fluid.pager.bindLinkClick",
                args: ["{that}.next", "{pager}.events.initiatePageChange", {relativePage: +1}]
            }
            ]
        },
        modelListeners: {
            "{pager}.model": "fluid.pager.previousNext.update({that}, {that}.options.styles.disabled, {change}.value)"
        }
    });

    fluid.pager.previousNext.update = function (that, disabledStyle, newModel) {
        that.previous.toggleClass(disabledStyle, newModel.pageIndex === 0);
        that.next.toggleClass(disabledStyle, newModel.pageIndex === newModel.pageCount - 1);
    };

    fluid.defaults("fluid.pager.pagerBar", {
        gradeNames: ["fluid.viewComponent"],
        components: {
            pageList: {
                type: "fluid.pager.pageList",
                container: "{pagerBar}.container",
                options: {
                    selectors: {
                        pageLinks: "{pagerBar}.options.selectors.pageLinks"
                    },
                    styles: "{pagerBar}.options.styles"
                }
            },
            previousNext: {
                type: "fluid.pager.previousNext",
                container: "{pagerBar}.container",
                options: {
                    selectors: {
                        previous: "{pagerBar}.options.selectors.previous",
                        next: "{pagerBar}.options.selectors.next"
                    },
                    styles: "{pagerBar}.options.styles"
                }
            }
        },
        events: {
            initiatePageChange: null,
            onModelChange: null
        },

        selectors: {
            pageLinks: ".flc-pager-pageLink",
            pageLinkSkip: ".flc-pager-pageLink-skip",
            previous: ".flc-pager-previous",
            next: ".flc-pager-next"
        },

        styles: {
            currentPage: "fl-pager-currentPage",
            disabled: "fl-pager-disabled"
        },

        strings: {
            currentPageIndexMsg: "Current page"
        }
    });

    fluid.pager.summaryAria = function (element) {
        element.attr({
            "aria-relevant": "all",
            "aria-atomic": "false",
            "aria-live": "assertive",
            "role": "status"
        });
    };

    fluid.defaults("fluid.pager.summary", {
        gradeNames: ["fluid.viewComponent"],
        listeners: {
            onCreate: {
                funcName: "fluid.pager.summaryAria",
                args: "{that}.container"
            }
        },
        modelListeners: {
            "{pager}.model": {
                funcName: "fluid.pager.summary.onModelChange",
                args: ["{that}.container", "{that}.options.strings.message", "{change}.value"]
            }
        }
    });

    fluid.pager.summary.onModelChange = function (node, message, newModel) {
        var text = fluid.stringTemplate(message, {
            first: newModel.pageIndex * newModel.pageSize + 1,
            last: fluid.pager.computePageLimit(newModel),
            total: newModel.totalRange,
            currentPage: newModel.pageIndex + 1
        });
        node.text(text);
    };

    fluid.defaults("fluid.pager.directPageSize", {
        gradeNames: ["fluid.viewComponent"],
        listeners: {
            onCreate: {
                "this": "{that}.container",
                method: "change",
                args: {
                    expander: {
                        funcName: "fluid.pager.directPageSize.onChange",
                        args: ["{pager}.events.initiatePageSizeChange", "{that}.container"]
                    }
                }
            }
        },
        modelListeners: {
            "{pager}.model.pageSize": "fluid.pager.updateNodeValue({that}.container, {change}.value)"
        }
    });

    fluid.pager.directPageSize.onChange = function (initiatePageSizeChange, node) {
        // Annoying function-returning function since with current framework this must be an onCreate listener to perform jQuery binding -
        // replace with "new renderer decorator system" (FLUID-5047)
        return function () {
            initiatePageSizeChange.fire(node.val() || 1);
        };
    };

    // Although this is much better with the new ChangeApplier, it still also needs to be replaced with a FLUID-5047 view-binding system
    fluid.pager.updateNodeValue = function (node, value) {
        node.val(value);
    };

    fluid.pager.initiatePageChangeListener = function (that, arg) {
        var newPageIndex = arg.pageIndex;
        if (arg.relativePage !== undefined) {
            newPageIndex = that.model.pageIndex + arg.relativePage;
        }
        that.applier.change("pageIndex", newPageIndex);
    };

    /*******************
     * Pager Component *
     *******************/

    fluid.defaults("fluid.pager", {
        gradeNames: ["fluid.viewComponent"],
        events: {
            initiatePageChange: null,
            initiatePageSizeChange: null,
            onModelChange: null,
            onRenderPageLinks: null,
            afterRender: null
        },
        model: {
            pageIndex: 0,
            pageSize: 1,
            totalRange: {
                expander: {
                    func: "{that}.acquireDefaultRange"
                }
            }
        },
        selectors: {
            pagerBar: ".flc-pager-top, .flc-pager-bottom",
            summary: ".flc-pager-summary",
            pageSize: ".flc-pager-page-size"
        },

        strings: {
            last: " (last)"
        },

        markup: {
            rangeAnnotation: " %first
—
 %last "
        },
        distributeOptions: {
            source: "{that}.options.pageList",
            removeSource: true,
            target: "{that fluid.pager.pageList}"
        },
        pageList: {
            type: "fluid.pager.renderedPageList",
            options: {
                pageStrategy: fluid.pager.gappedPageStrategy(3, 1)
            }
        },
        modelRelay: [{
            target: "pageCount",
            singleTransform: {
                type: "fluid.transforms.free",
                args: {
                    "totalRange": "{that}.model.totalRange",
                    "pageSize": "{that}.model.pageSize"
                },
                func: "fluid.pager.computePageCount"
            }
        }, {
            target: "pageIndex",
            singleTransform: {
                type: "fluid.transforms.limitRange",
                input: "{that}.model.pageIndex",
                min: 0,
                max: "{that}.model.pageCount",
                excludeMax: 1
            }
        }],
        modelListeners: {
            "": "{that}.events.onModelChange.fire({change}.value, {change}.oldValue, {that})"
        },
        listeners: {
            "initiatePageChange.updatePageIndex": {
                funcName: "fluid.pager.initiatePageChangeListener",
                args: ["{that}", "{arguments}.0"]
            },
            "initiatePageSizeChange.updateModel": {
                changePath: "pageSize",
                value: "{arguments}.0"
            }
        },
        invokers: {
            acquireDefaultRange: {
                // TODO: problem here - pagerBar, etc. are dynamic components and so cannot be constructed gingerly
                // This is why current (pre-FLUID-4925) framework must construct components before invokers
                funcName: "fluid.identity",
                args: "{that}.pagerBar.pageList.defaultModel.totalRange"
            }
        },
        dynamicComponents: {
            summary: {
                sources: "{that}.dom.summary",
                type: "fluid.pager.summary",
                container: "{source}",
                options: {
                    strings: {
                        message: "Viewing page %currentPage. Showing records %first - %last of %total items."
                    },
                    events: {
                        onModelChange: "{pager}.events.onModelChange"
                    }
                }
            },
            pageSize: {
                sources: "{that}.dom.pageSize",
                type: "fluid.pager.directPageSize",
                container: "{source}"
            },
            pagerBar: {
                sources: "{that}.dom.pagerBar",
                type: "fluid.pager.pagerBar",
                container: "{source}",
                options: {
                    strings: "{pager}.options.strings",
                    events: {
                        initiatePageChange: "{pager}.events.initiatePageChange",
                        onModelChange: "{pager}.events.onModelChange"
                    }
                }
            }
        }
    });

})(jQuery, fluid_3_0_0);


// "Web" is the first identifier in one of the default lookup chains.
// let's make sure the walk-up-the-chain code works if the AST members
// end before the lookup chain ends
Web("web");

var i = new Image();

while(0)
{
  $Debug.Write("nope");
}
for(var p in i)
{
  $Debug.Write(p);
}
for(var n=0; n < i.length; ++n)
{
  $Debug.Write(n);
}
do
{
  $Debug.Write(i);
}while(0);

try
{
  i.src = "foo";
}
catch(e)
{
  $Debug.Write(e);
}
finally
{
  n = i;
}

// ends in semicolon, no braces
if ( !i )
    debugger;

// no semicolon, other statement on next line
if ( !i )
{
    debugger
    i = null;
}
// no semicolon, next token a right-brace
if ( !i ) { debugger }

// this is a call to the Atlas framework debug namespace.
// it should get stripped out with the -d option
Web.Debug.Write("foo");
Web.Debug.ASSERT("foo")();

// an msn framework is also in the defaults
Msn.Debug.Assert("message");

// same for this one
$Debug.Write("arf");

// also trim any calls into the Debug namespace
Debug.assertParam("foo");
Debug.assertType("foo");
Debug.assert("foo");
Debug.failIf("foo");
Debug.fail("foo");
Debug.writeLine("foo");

// also trim WAssert calls
WAssert("blah");

// but this one is used as a constructor -- it should get
// replaced with an empty object constructor
var foo = new $Debug.DebugWindow(1);

///#DEBUG
// we will skip these statements if we are stripping debug statements
alert("DEBUG!");
///#ENDDEBUG

//@cc_on
//@if(@DEBUG)
foo(bar);
//@end

// some random calls that won't normally be stripped, but we'll use command-line switch
// to turn some on or off
FooBar.Write("this");
AckBar.Assert("wakka-wakka");


{{title "Allocation " this.model.name}}
<AllocationSubnav @allocation={{this.model}} />

  {{#if this.error}}

{{this.error.title}}

{{this.error.description}}

          <button data-test-inline-error-close class="button is-danger" onclick={{action this.onDismiss}} type="button">Okay</button>

  {{/if}}

      Allocation {{this.model.name}}
      {{this.model.clientStatus}}

      {{#if this.model.isRunning}}

          <Exec::OpenButton @job={{this.model.job}} @allocation={{this.model}} />

        <TwoStepButton
          data-test-stop
          @alignRight={{true}}
          @idleText="Stop"
          @cancelText="Cancel"
          @confirmText="Yes, Stop"
          @confirmationMessage="Are you sure? This will reschedule the allocation on a different client."
          @awaitingConfirmation={{this.stopAllocation.isRunning}}
          @disabled={{or this.stopAllocation.isRunning this.restartAllocation.isRunning}}
          @onConfirm={{perform this.stopAllocation}} />
        <TwoStepButton
          data-test-restart
          @alignRight={{true}}
          @idleText="Restart"
          @cancelText="Cancel"
          @confirmText="Yes, Restart"
          @confirmationMessage="Are you sure? This will restart the allocation in-place."
          @awaitingConfirmation={{this.restartAllocation.isRunning}}
          @disabled={{or this.stopAllocation.isRunning this.restartAllocation.isRunning}}
          @onConfirm={{perform this.restartAllocation}} />
      {{/if}}

    {{this.model.id}}
    <CopyButton @clipboardText={{this.model.id}} />

      Allocation Details
      Job
        <LinkTo @route="jobs.job" @model={{this.model.job}} @query={{hash jobNamespace=this.model.job.namespace.id}} data-test-job-link>{{this.model.job.name}}</LinkTo>

      Client
        <LinkTo @route="clients.client" @model={{this.model.node}} data-test-client-link>{{this.model.node.shortId}}</LinkTo>

      Resource Utilization

      {{#if this.model.isRunning}}

            <PrimaryMetric @resource={{this.model}} @metric="cpu" />

            <PrimaryMetric @resource={{this.model}} @metric="memory" />

      {{else}}

Allocation isn't running

Only running allocations utilize resources.

      {{/if}}

  <LifecycleChart @taskStates={{this.model.states}} />

      Tasks

      {{#if this.sortedStates.length}}
        <ListTable
          @source={{this.sortedStates}}
          @sortProperty={{this.sortProperty}}
          @sortDescending={{this.sortDescending}}
          @class="is-striped" as |t|>
          <t.head>

            <t.sort-by @prop="name">Name</t.sort-by>
            <t.sort-by @prop="state">State</t.sort-by>
            Last Event
            <t.sort-by @prop="events.lastObject.time">Time</t.sort-by>
            Volumes
            CPU
            Memory
          </t.head>
          <t.body as |row|>
            <TaskRow
              @data-test-task-row={{row.model.name}}
              @task={{row.model}}
              @onClick={{action "taskClick" row.model.allocation row.model}} />
          </t.body>
        </ListTable>
      {{else}}

No Tasks

Allocations will not have tasks until they are in a running state.

      {{/if}}

  {{#if this.ports.length}}

        Ports

        <ListTable @source={{this.ports}} as |t|>
          <t.head>
            Name
            Host Address
            Mapped Port
          </t.head>
          <t.body as |row|>

              {{row.model.label}}

                {{row.model.hostIp}}:{{row.model.value}}

              {{row.model.to}}

          </t.body>
        </ListTable>

  {{/if}}

  {{#if this.services.length}}

        Services

        <ListTable @source={{this.services}} as |t|>
          <t.head>
            Name
            Port
            Tags
            Connect?
            Upstreams
          </t.head>
          <t.body as |row|>

              {{row.model.name}}
              {{row.model.portLabel}}
              {{join ", " row.model.tags}}
              {{if row.model.connect "Yes" "No"}}

                {{#each row.model.connect.sidecarService.proxy.upstreams as |upstream|}}
                  {{upstream.destinationName}}:{{upstream.localBindPort}}
                {{/each}}

          </t.body>
        </ListTable>

  {{/if}}

  {{#if this.model.hasRescheduleEvents}}

        Reschedule Events

        <RescheduleEventTimeline @allocation={{this.model}} />

  {{/if}}

  {{#if this.model.wasPreempted}}

Preempted By

        {{#if this.preempter}}

                  {{this.preempter.clientStatus}}

                {{this.preempter.name}}
                <LinkTo @route="allocations.allocation" @model={{this.preempter}} data-test-allocation-id>{{this.preempter.shortId}}</LinkTo>

              Job
                <LinkTo @route="jobs.job" @model={{this.preempter.job}} @query={{hash jobNamespace=this.preempter.job.namespace.id}} data-test-job-link>{{this.preempter.job.name}}</LinkTo>

              Priority
                {{this.preempter.job.priority}}

              Client
                <LinkTo @route="clients.client" @model={{this.preempter.node}} data-test-client-link>{{this.preempter.node.shortId}}</LinkTo>

              Reserved CPU
                {{this.preempter.resources.cpu}} MHz

              Reserved Memory
                {{this.preempter.resources.memory}} MiB

        {{else}}

Allocation is gone

This allocation has been stopped and garbage collected.

        {{/if}}

  {{/if}}

  {{#if (and this.model.preemptedAllocations.isFulfilled this.model.preemptedAllocations.length)}}

Preempted Allocations

        <ListTable
          @source={{this.model.preemptedAllocations}}
          @class="allocations is-isolated" as |t|>
          <t.head>

            ID
            Task Group
            Created
            Modified
            Status
            Version
            Node
            CPU
            Memory
          </t.head>
          <t.body as |row|>
            <AllocationRow @allocation={{row.model}} @context="job" @data-test-allocation={{row.model.id}} />
          </t.body>
        </ListTable>

  {{/if}}


/* This file is part of the ScriptDev2 Project. See AUTHORS file for Copyright information
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/* ScriptData
SDName: Boss_Amanitar
SD%Complete: 80
SDComment: Mushrooms summoning may need improvements;
SDCategory: Ahn'kahet
EndScriptData */

#include "precompiled.h"
#include "ahnkahet.h"

enum
{
    SPELL_BASH                      = 57094,
    SPELL_VENOM_BOLT_VOLLEY         = 57088,
    SPELL_ENTANGLING_ROOTS          = 57095,
    SPELL_MINI                      = 57055,
    SPELL_REMOVE_MUSHROOM_POWER     = 57283,                // purpose unk - this spell may remove the Mini aura from all players

    // Mushroom entries
    NPC_HEALTHY_MUSHROOM            = 30391,
    NPC_POISONOUS_MUSHROOM          = 30435,

    // Mushroom spells
    SPELL_POISON_CLOUD              = 57061,
    SPELL_POTENT_FUNGUS             = 56648,
    SPELL_POISON_MUSHROOM_VISUAL    = 56741,
    SPELL_POWER_MUSHROOM_VISUAL     = 56740,
    SPELL_MUSHROOM_FORM             = 31690,
};

static const float aMushroomPos[3] = {362.8f, -869.16f, -75.03f};

/*######
## boss_amanitar
######*/

struct boss_amanitarAI : public ScriptedAI
{
    boss_amanitarAI(Creature* pCreature) : ScriptedAI(pCreature)
    {
        m_pInstance = (ScriptedInstance*)pCreature->GetInstanceData();
        Reset();
    }

    ScriptedInstance* m_pInstance;

    uint32 m_uiBashTimer;
    uint32 m_uiVenomBoltTimer;
    uint32 m_uiRootsTimer;
    uint32 m_uiMiniTimer;
    uint32 m_uiMushroomTimer;

    void Reset() override
    {
        m_uiBashTimer       = urand(7000, 10000);
        m_uiVenomBoltTimer  = urand(10000, 15000);
        m_uiRootsTimer      = 20000;
        m_uiMiniTimer       = urand(20000, 25000);
        m_uiMushroomTimer   = urand(10000, 20000);
    }

    void Aggro(Unit* /*pWho*/) override
    {
        DoSummonMushrooms(true);

        if (m_pInstance)
            m_pInstance->SetData(TYPE_AMANITAR, IN_PROGRESS);
    }

    void JustDied(Unit* /*pKiller*/) override
    {
        if (m_pInstance)
            m_pInstance->SetData(TYPE_AMANITAR, DONE);
    }

    void JustReachedHome() override
    {
        if (m_pInstance)
            m_pInstance->SetData(TYPE_AMANITAR, FAIL);
    }

    void JustSummoned(Creature* pSummoned) override
    {
        if (pSummoned->GetEntry() == NPC_POISONOUS_MUSHROOM)
            pSummoned->CastSpell(pSummoned, SPELL_POISON_MUSHROOM_VISUAL, true);
        else if (pSummoned->GetEntry() == NPC_HEALTHY_MUSHROOM)
            pSummoned->CastSpell(pSummoned, SPELL_POWER_MUSHROOM_VISUAL, true);

        // ToDo: research if the mushrooms should have a grow effect!
        pSummoned->CastSpell(pSummoned, SPELL_MUSHROOM_FORM, true);
    }

    void SummonedCreatureJustDied(Creature* pSummoned) override
    {
        if (pSummoned->GetEntry() == NPC_POISONOUS_MUSHROOM)
            pSummoned->CastSpell(pSummoned, SPELL_POISON_CLOUD, true);
        else if (pSummoned->GetEntry() == NPC_HEALTHY_MUSHROOM)
            pSummoned->CastSpell(pSummoned, SPELL_POTENT_FUNGUS, true);
    }

    void DoSummonMushrooms(bool bIsFirstSummon)
    {
        // This implementation may not be 100% accurate;
        // On aggro boss summons about 20 mushrooms; On timer it summons about 5 mushrooms per turn
        // There is a 33% chance that the mushroom will be healthy
        // The summon position is based on the center of the area coords

        float fX, fY, fZ;
        uint32 uiMaxMushrooms = bIsFirstSummon ? 20 : 5;

        for (uint8 i = 0; i < uiMaxMushrooms; ++i)
        {
            uint32 uiMushroomEntry = roll_chance_i(33) ? NPC_HEALTHY_MUSHROOM : NPC_POISONOUS_MUSHROOM;
            m_creature->GetRandomPoint(aMushroomPos[0], aMushroomPos[1], aMushroomPos[2], 30.0f, fX, fY, fZ);
            m_creature->SummonCreature(uiMushroomEntry, fX, fY, fZ, 0.0f, TEMPSUMMON_DEAD_DESPAWN, 0);
        }
    }

    void UpdateAI(const uint32 uiDiff) override
    {
        if (!m_creature->SelectHostileTarget() || !m_creature->getVictim())
            return;

        if (m_uiBashTimer < uiDiff)
        {
            if (DoCastSpellIfCan(m_creature->getVictim(), SPELL_BASH) == CAST_OK)
                m_uiBashTimer = urand(8000, 13000);
        }
        else
            m_uiBashTimer -= uiDiff;

        if (m_uiVenomBoltTimer < uiDiff)
        {
            if (DoCastSpellIfCan(m_creature, SPELL_VENOM_BOLT_VOLLEY) == CAST_OK)
                m_uiVenomBoltTimer = urand(15000, 20000);
        }
        else
            m_uiVenomBoltTimer -= uiDiff;

        if (m_uiRootsTimer < uiDiff)
        {
            if (Unit* pTarget = m_creature->SelectAttackingTarget(ATTACKING_TARGET_RANDOM, 0))
            {
                if (DoCastSpellIfCan(pTarget, SPELL_ENTANGLING_ROOTS) == CAST_OK)
                    m_uiRootsTimer = urand(20000, 25000);
            }
        }
        else
            m_uiRootsTimer -= uiDiff;

        if (m_uiMiniTimer < uiDiff)
        {
            if (DoCastSpellIfCan(m_creature, SPELL_MINI) == CAST_OK)
                m_uiMiniTimer = 30000;
        }
        else
            m_uiMiniTimer -= uiDiff;

        if (m_uiMushroomTimer < uiDiff)
        {
            DoSummonMushrooms(false);
            m_uiMushroomTimer = urand(10000, 20000);
        }
        else
            m_uiMushroomTimer -= uiDiff;

        // ToDo: Research if he requires out of combat area evade check

        DoMeleeAttackIfReady();
    }
};

CreatureAI* GetAI_boss_amanitar(Creature* pCreature)
{
    return new boss_amanitarAI(pCreature);
}

/*######
## npc_amanitar_mushroom
######*/

struct npc_amanitar_mushroomAI : public Scripted_NoMovementAI
{
    npc_amanitar_mushroomAI(Creature* pCreature) : Scripted_NoMovementAI(pCreature) { Reset(); }

    void Reset() override { }

    void AttackStart(Unit* /*pWho*/) override { }
    void MoveInLineOfSight(Unit* /*pWho*/) override { }
    void UpdateAI(const uint32 /*uiDiff*/) override { }
};

CreatureAI* GetAI_npc_amanitar_mushroom(Creature* pCreature)
{
    return new npc_amanitar_mushroomAI(pCreature);
}

void AddSC_boss_amanitar()
{
    Script* pNewScript;

    pNewScript = new Script;
    pNewScript->Name = "boss_amanitar";
    pNewScript->GetAI = &GetAI_boss_amanitar;
    pNewScript->RegisterSelf();

    pNewScript = new Script;
    pNewScript->Name = "npc_amanitar_mushroom";
    pNewScript->GetAI = &GetAI_npc_amanitar_mushroom;
    pNewScript->RegisterSelf();
}


/*********************************************************************/
/* Copyright 2009, 2010 The University of Texas at Austin.           */
/* All rights reserved.                                              */
/*                                                                   */
/* Redistribution and use in source and binary forms, with or        */
/* without modification, are permitted provided that the following   */
/* conditions are met:                                               */
/*                                                                   */
/*   1. Redistributions of source code must retain the above         */
/*      copyright notice, this list of conditions and the following  */
/*      disclaimer.                                                  */
/*                                                                   */
/*   2. Redistributions in binary form must reproduce the above      */
/*      copyright notice, this list of conditions and the following  */
/*      disclaimer in the documentation and/or other materials       */
/*      provided with the distribution.                              */
/*                                                                   */
/*    THIS  SOFTWARE IS PROVIDED  BY THE  UNIVERSITY OF  TEXAS AT    */
/*    AUSTIN  ``AS IS''  AND ANY  EXPRESS OR  IMPLIED WARRANTIES,    */
/*    INCLUDING, BUT  NOT LIMITED  TO, THE IMPLIED  WARRANTIES OF    */
/*    MERCHANTABILITY  AND FITNESS FOR  A PARTICULAR  PURPOSE ARE    */
/*    DISCLAIMED.  IN  NO EVENT SHALL THE UNIVERSITY  OF TEXAS AT    */
/*    AUSTIN OR CONTRIBUTORS BE  LIABLE FOR ANY DIRECT, INDIRECT,    */
/*    INCIDENTAL,  SPECIAL, EXEMPLARY,  OR  CONSEQUENTIAL DAMAGES    */
/*    (INCLUDING, BUT  NOT LIMITED TO,  PROCUREMENT OF SUBSTITUTE    */
/*    GOODS  OR  SERVICES; LOSS  OF  USE,  DATA,  OR PROFITS;  OR    */
/*    BUSINESS INTERRUPTION) HOWEVER CAUSED  AND ON ANY THEORY OF    */
/*    LIABILITY, WHETHER  IN CONTRACT, STRICT  LIABILITY, OR TORT    */
/*    (INCLUDING NEGLIGENCE OR OTHERWISE)  ARISING IN ANY WAY OUT    */
/*    OF  THE  USE OF  THIS  SOFTWARE,  EVEN  IF ADVISED  OF  THE    */
/*    POSSIBILITY OF SUCH DAMAGE.                                    */
/*                                                                   */
/* The views and conclusions contained in the software and           */
/* documentation are those of the authors and should not be          */
/* interpreted as representing official policies, either expressed   */
/* or implied, of The University of Texas at Austin.                 */
/*********************************************************************/

#define ASSEMBLER
#include "common.h"

#define STACK	16
#define ARGS	 0

#define STACK_M	 4 + STACK + ARGS(%esi)
#define STACK_N	 8 + STACK + ARGS(%esi)
#define STACK_K	12 + STACK + ARGS(%esi)
#define STACK_ALPHA_R	16 + STACK + ARGS(%esi)
#define STACK_ALPHA_I	20 + STACK + ARGS(%esi)
#define STACK_A	24 + STACK + ARGS(%esi)
#define STACK_B	28 + STACK + ARGS(%esi)
#define STACK_C	32 + STACK + ARGS(%esi)
#define STACK_LDC	36 + STACK + ARGS(%esi)
#define STACK_OFFT	40 + STACK + ARGS(%esi)

#define POSINV	 0(%esp)
#define ALPHA_R	16(%esp)
#define ALPHA_I	32(%esp)
#define K	48(%esp)
#define N	52(%esp)
#define M	56(%esp)
#define A	60(%esp)
#define C	64(%esp)
#define J	68(%esp)
#define OLD_STACK 72(%esp)
#define OFFSET  76(%esp)
#define KK	80(%esp)
#define KKK	84(%esp)
#define BUFFER 128(%esp)

#define B	%edi
#define	LDC	%ebp
#define AA	%edx
#define BB	%ecx

#ifdef PENTIUM4
#define PREFETCH prefetcht0
#define PREFETCHSIZE 168
#endif

#ifdef PENTIUMM
#define PREFETCH prefetcht0
#define PREFETCHSIZE 168
#endif

#if defined(NN) || defined(NT) || defined(TN) || defined(TT) || \
    defined(RN) || defined(RT) || defined(CN) || defined(CT)
#define ADDSUB	addps
#else
#define ADDSUB	subps
#endif

#define KERNEL1(address) \
	mulps	%xmm0, %xmm2; \
	PREFETCH  (PREFETCHSIZE +  0) * SIZE + 1 * (address) * SIZE(AA); \
	addps	%xmm2, %xmm4; \
	movshdup  0 * SIZE + 2 * (address) * SIZE(BB), %xmm2; \
	mulps	%xmm0, %xmm2; \
	ADDSUB	%xmm2, %xmm5; \
	movsldup  4 * SIZE + 2 * (address) * SIZE(BB), %xmm2; \
	mulps	%xmm0, %xmm2; \
	addps	%xmm2, %xmm6; \
	movshdup  4 * SIZE + 2 * (address) * SIZE(BB), %xmm2; \
	mulps	%xmm0, %xmm2; \
	movaps	  4 * SIZE + 1 * (address) * SIZE(AA), %xmm0; \
	ADDSUB	%xmm2, %xmm7; \
	movsldup  8 * SIZE + 2 * (address) * SIZE(BB), %xmm2

#define KERNEL2(address) \
	mulps	%xmm0, %xmm2; \
	addps	%xmm2, %xmm4; \
	movshdup  8 * SIZE + 2 * (address) * SIZE(BB), %xmm2; \
	mulps	%xmm0, %xmm2; \
	ADDSUB	%xmm2, %xmm5; \
	movsldup 12 * SIZE + 2 * (address) * SIZE(BB), %xmm2; \
	mulps	%xmm0, %xmm2; \
	addps	%xmm2, %xmm6; \
	movshdup 12 * SIZE + 2 * (address) * SIZE(BB), %xmm2; \
	mulps	%xmm0, %xmm2; \
	movaps	  8 * SIZE + 1 * (address) * SIZE(AA), %xmm0; \
	ADDSUB	%xmm2, %xmm7; \
	movsldup 32 * SIZE + 2 * (address) * SIZE(BB), %xmm2

#define KERNEL3(address) \
	mulps	%xmm0, %xmm3; \
	addps	%xmm3, %xmm4; \
	movshdup 16 * SIZE + 2 * (address) * SIZE(BB), %xmm3; \
	mulps	%xmm0, %xmm3; \
	ADDSUB	%xmm3, %xmm5; \
	movsldup 20 * SIZE + 2 * (address) * SIZE(BB), %xmm3; \
	mulps	%xmm0, %xmm3; \
	addps	%xmm3, %xmm6; \
	movshdup 20 * SIZE + 2 * (address) * SIZE(BB), %xmm3; \
	mulps	%xmm0, %xmm3; \
	movaps	 12 * SIZE + 1 * (address) * SIZE(AA), %xmm0; \
	ADDSUB	%xmm3, %xmm7; \
	movsldup 24 * SIZE + 2 * (address) * SIZE(BB), %xmm3

#define KERNEL4(address) \
	mulps	%xmm0, %xmm3; \
	addps	%xmm3, %xmm4; \
	movshdup 24 * SIZE + 2 * (address) * SIZE(BB), %xmm3; \
	mulps	%xmm0, %xmm3; \
	ADDSUB	%xmm3, %xmm5; \
	movsldup 28 * SIZE + 2 * (address) * SIZE(BB), %xmm3; \
	mulps	%xmm0, %xmm3; \
	addps	%xmm3, %xmm6; \
	movshdup 28 * SIZE + 2 * (address) * SIZE(BB), %xmm3; \
	mulps	%xmm0, %xmm3; \
	movaps	 32 * SIZE + 1 * (address) * SIZE(AA), %xmm0; \
	ADDSUB	%xmm3, %xmm7; \
	movsldup 48 * SIZE + 2 * (address) * SIZE(BB), %xmm3

#define KERNEL5(address) \
	mulps	%xmm1, %xmm2; \
	addps	%xmm2, %xmm4; \
	movshdup 32 * SIZE + 2 * (address) * SIZE(BB), %xmm2; \
	mulps	%xmm1, %xmm2; \
	ADDSUB	%xmm2, %xmm5; \
	movsldup 36 * SIZE + 2 * (address) * SIZE(BB), %xmm2; \
	mulps	%xmm1, %xmm2; \
	addps	%xmm2, %xmm6; \
	movshdup 36 * SIZE + 2 * (address) * SIZE(BB), %xmm2; \
	mulps	%xmm1, %xmm2; \
	movaps	 20 * SIZE + 1 * (address) * SIZE(AA), %xmm1; \
	ADDSUB	%xmm2, %xmm7; \
	movsldup 40 * SIZE + 2 * (address) * SIZE(BB), %xmm2

#define KERNEL6(address) \
	mulps	%xmm1, %xmm2; \
	addps	%xmm2, %xmm4; \
	movshdup 40 * SIZE + 2 * (address) * SIZE(BB), %xmm2; \
	mulps	%xmm1, %xmm2; \
	ADDSUB	%xmm2, %xmm5; \
	movsldup 44 * SIZE + 2 * (address) * SIZE(BB), %xmm2; \
	mulps	%xmm1, %xmm2; \
	addps	%xmm2, %xmm6; \
	movshdup 44 * SIZE + 2 * (address) * SIZE(BB), %xmm2; \
	mulps	%xmm1, %xmm2; \
	movaps	 24 * SIZE + 1 * (address) * SIZE(AA), %xmm1; \
	ADDSUB	%xmm2, %xmm7; \
	movsldup 64 * SIZE + 2 * (address) * SIZE(BB), %xmm2

#define KERNEL7(address) \
	mulps	%xmm1, %xmm3; \
	addps	%xmm3, %xmm4; \
	movshdup 48 * SIZE + 2 * (address) * SIZE(BB), %xmm3; \
	mulps	%xmm1, %xmm3; \
	ADDSUB	%xmm3, %xmm5; \
	movsldup 52 * SIZE + 2 * (address) * SIZE(BB), %xmm3; \
	mulps	%xmm1, %xmm3; \
	addps	%xmm3, %xmm6; \
	movshdup 52 * SIZE + 2 * (address) * SIZE(BB), %xmm3; \
	mulps	%xmm1, %xmm3; \
	movaps	 28 * SIZE + 1 * (address) * SIZE(AA), %xmm1; \
	ADDSUB	%xmm3, %xmm7; \
	movsldup 56 * SIZE + 2 * (address) * SIZE(BB), %xmm3

#define KERNEL8(address) \
	mulps	%xmm1, %xmm3; \
	addps	%xmm3, %xmm4; \
	movshdup 56 * SIZE + 2 * (address) * SIZE(BB), %xmm3; \
	mulps	%xmm1, %xmm3; \
	ADDSUB	%xmm3, %xmm5; \
	movsldup 60 * SIZE + 2 * (address) * SIZE(BB), %xmm3; \
	mulps	%xmm1, %xmm3; \
	addps	%xmm3, %xmm6; \
	movshdup 60 * SIZE + 2 * (address) * SIZE(BB), %xmm3; \
	mulps	%xmm1, %xmm3; \
	movaps	 48 * SIZE + 1 * (address) * SIZE(AA), %xmm1; \
	ADDSUB	%xmm3, %xmm7; \
	movsldup 80 * SIZE + 2 * (address) * SIZE(BB), %xmm3

	PROLOGUE

	pushl	%ebp
	pushl	%edi
	pushl	%esi
	pushl	%ebx

	PROFCODE

	movl	%esp, %esi	# save old stack

	subl	$128 + LOCAL_BUFFER_SIZE, %esp
	andl	$-1024, %esp	# align stack

	STACK_TOUCHING

	movl	STACK_M, %ebx
	movl	STACK_N, %eax
	movl	STACK_K, %ecx
	movl	STACK_A, %edx

	movl	%ebx, M
	movl	%eax, N
	movl	%ecx, K
	movl	%edx, A
	movl	%esi, OLD_STACK

	movl	STACK_B, %edi
	movl	STACK_C, %ebx
#ifdef TRMMKERNEL
	movss	STACK_OFFT, %xmm4
#endif

	movss	STACK_ALPHA_R, %xmm0
	movss	STACK_ALPHA_I, %xmm1

	pxor	%xmm7, %xmm7
	cmpeqps	%xmm7, %xmm7
	pslld	$31, %xmm7	# Generate mask

	shufps	$0, %xmm0, %xmm0
	movaps	 %xmm0,  0 + ALPHA_R

	movss	 %xmm1,  4 + ALPHA_I
	movss	 %xmm1, 12 + ALPHA_I
	xorps	 %xmm7, %xmm1
	movss	 %xmm1,  0 + ALPHA_I
	movss	 %xmm1,  8 + ALPHA_I

	movl	%ebx, C
	movl	STACK_LDC, LDC

#ifdef TRMMKERNEL
	movss	%xmm4, OFFSET
	movss	%xmm4, KK
#ifndef LEFT
	negl	KK
#endif
#endif

	sall	$ZBASE_SHIFT, LDC
	movl	%eax, J			# j = n
	sarl	$1, J
	jle	.L100
	ALIGN_4

.L01:
#if defined(TRMMKERNEL) && defined(LEFT)
	movl	OFFSET, %eax
	movl	%eax, KK
#endif

/* Copying to Sub Buffer */
	leal	BUFFER, %ecx

	movl	K, %eax
	sarl	$2, %eax
	jle	.L03
	ALIGN_4

.L02:
	movddup	 0 * SIZE(B), %xmm0
	movddup	 2 * SIZE(B), %xmm1
	movddup	 4 * SIZE(B), %xmm2
	movddup	 6 * SIZE(B), %xmm3
	movddup	 8 * SIZE(B), %xmm4
	movddup	10 * SIZE(B), %xmm5
	movddup	12 * SIZE(B), %xmm6
	movddup	14 * SIZE(B), %xmm7

	movaps	%xmm0,  0 * SIZE(BB)
	movaps	%xmm1,  4 * SIZE(BB)
	movaps	%xmm2,  8 * SIZE(BB)
	movaps	%xmm3, 12 * SIZE(BB)
	movaps	%xmm4, 16 * SIZE(BB)
	movaps	%xmm5, 20 * SIZE(BB)
	movaps	%xmm6, 24 * SIZE(BB)
	movaps	%xmm7, 28 * SIZE(BB)

#	prefetcht1	128 * SIZE(%ecx)
	prefetcht0	112 * SIZE(%edi)

	addl	$16 * SIZE, B
	addl	$32 * SIZE, BB

	decl	%eax
	jne	.L02
	ALIGN_4

.L03:
	movl	K, %eax
	andl	$3, %eax
	BRANCH
	jle	.L05
	ALIGN_4

.L04:
	movddup	0 * SIZE(B), %xmm0
	movddup	2 * SIZE(B), %xmm1

	movaps	%xmm0,  0 * SIZE(BB)
	movaps	%xmm1,  4 * SIZE(BB)

	addl	$4 * SIZE, B
	addl	$8 * SIZE, BB
	decl	%eax
	jne	.L04
	ALIGN_4

.L05:
	movl	C,  %esi
	movl	A,  %edx
	movl	M,  %ebx
	sarl	$1, %ebx
	jle	.L30
	ALIGN_4

.L10:
#if !defined(TRMMKERNEL) || \
	(defined(TRMMKERNEL) &&  defined(LEFT) &&  defined(TRANSA)) || \
	(defined(TRMMKERNEL) && !defined(LEFT) && !defined(TRANSA))

	leal	BUFFER, BB	# boffset1 = boffset
#else
	leal	BUFFER, BB	# boffset1 = boffset
	movl	KK, %eax
	leal	(, %eax,   8), %eax
	leal	(AA, %eax, 2), AA
	leal	(BB, %eax, 4), BB
#endif

	movaps	 0 * SIZE(AA), %xmm0
	pxor	%xmm4, %xmm4
	movaps	16 * SIZE(AA), %xmm1
	pxor	%xmm5, %xmm5
	movsldup  0 * SIZE(BB), %xmm2
	pxor	%xmm6, %xmm6
	movsldup 16 * SIZE(BB), %xmm3
	pxor	%xmm7, %xmm7

	prefetchnta 4 * SIZE(%esi)
	prefetchnta 4 * SIZE(%esi, LDC)

#ifndef TRMMKERNEL
	movl	K, %eax
#elif (defined(LEFT) && !defined(TRANSA)) || (!defined(LEFT) && defined(TRANSA))
	movl	K, %eax
	subl	KK, %eax
	movl	%eax, KKK
#else
	movl	KK, %eax
#ifdef LEFT
	addl	$2, %eax
#else
	addl	$2, %eax
#endif
	movl	%eax, KKK
#endif

#if 1
	andl	$-8, %eax
	sall	$4, %eax
	je	.L15
.L1X:
	KERNEL1(32 *  0)
	KERNEL2(32 *  0)
	KERNEL3(32 *  0)
	KERNEL4(32 *  0)
	KERNEL5(32 *  0)
	KERNEL6(32 *  0)
	KERNEL7(32 *  0)
	KERNEL8(32 *  0)
	cmpl	$128 *  1, %eax
	jle	.L12
	KERNEL1(32 *  1)
	KERNEL2(32 *  1)
	KERNEL3(32 *  1)
	KERNEL4(32 *  1)
	KERNEL5(32 *  1)
	KERNEL6(32 *  1)
	KERNEL7(32 *  1)
	KERNEL8(32 *  1)
	cmpl	$128 *  2, %eax
	jle	.L12
	KERNEL1(32 *  2)
	KERNEL2(32 *  2)
	KERNEL3(32 *  2)
	KERNEL4(32 *  2)
	KERNEL5(32 *  2)
	KERNEL6(32 *  2)
	KERNEL7(32 *  2)
	KERNEL8(32 *  2)
	cmpl	$128 *  3, %eax
	jle	.L12
	KERNEL1(32 *  3)
	KERNEL2(32 *  3)
	KERNEL3(32 *  3)
	KERNEL4(32 *  3)
	KERNEL5(32 *  3)
	KERNEL6(32 *  3)
	KERNEL7(32 *  3)
	KERNEL8(32 *  3)
	cmpl	$128 *  4, %eax
	jle	.L12
	KERNEL1(32 *  4)
	KERNEL2(32 *  4)
	KERNEL3(32 *  4)
	KERNEL4(32 *  4)
	KERNEL5(32 *  4)
	KERNEL6(32 *  4)
	KERNEL7(32 *  4)
	KERNEL8(32 *  4)
	cmpl	$128 *  5, %eax
	jle	.L12
	KERNEL1(32 *  5)
	KERNEL2(32 *  5)
	KERNEL3(32 *  5)
	KERNEL4(32 *  5)
	KERNEL5(32 *  5)
	KERNEL6(32 *  5)
	KERNEL7(32 *  5)
	KERNEL8(32 *  5)
	cmpl	$128 *  6, %eax
	jle	.L12
	KERNEL1(32 *  6)
	KERNEL2(32 *  6)
	KERNEL3(32 *  6)
	KERNEL4(32 *  6)
	KERNEL5(32 *  6)
	KERNEL6(32 *  6)
	KERNEL7(32 *  6)
	KERNEL8(32 *  6)
	cmpl	$128 *  7, %eax
	jle	.L12
	KERNEL1(32 *  7)
	KERNEL2(32 *  7)
	KERNEL3(32 *  7)
	KERNEL4(32 *  7)
	KERNEL5(32 *  7)
	KERNEL6(32 *  7)
	KERNEL7(32 *  7)
	KERNEL8(32 *  7)
#if 1
	cmpl	$128 *  8, %eax
	jle	.L12
	KERNEL1(32 *  8)
	KERNEL2(32 *  8)
	KERNEL3(32 *  8)
	KERNEL4(32 *  8)
	KERNEL5(32 *  8)
	KERNEL6(32 *  8)
	KERNEL7(32 *  8)
	KERNEL8(32 *  8)
	cmpl	$128 *  9, %eax
	jle	.L12
	KERNEL1(32 *  9)
	KERNEL2(32 *  9)
	KERNEL3(32 *  9)
	KERNEL4(32 *  9)
	KERNEL5(32 *  9)
	KERNEL6(32 *  9)
	KERNEL7(32 *  9)
	KERNEL8(32 *  9)
	cmpl	$128 * 10, %eax
	jle	.L12
	KERNEL1(32 * 10)
	KERNEL2(32 * 10)
	KERNEL3(32 * 10)
	KERNEL4(32 * 10)
	KERNEL5(32 * 10)
	KERNEL6(32 * 10)
	KERNEL7(32 * 10)
	KERNEL8(32 * 10)
	cmpl	$128 * 11, %eax
	jle	.L12
	KERNEL1(32 * 11)
	KERNEL2(32 * 11)
	KERNEL3(32 * 11)
	KERNEL4(32 * 11)
	KERNEL5(32 * 11)
	KERNEL6(32 * 11)
	KERNEL7(32 * 11)
	KERNEL8(32 * 11)
	cmpl	$128 * 12, %eax
	jle	.L12
	KERNEL1(32 * 12)
	KERNEL2(32 * 12)
	KERNEL3(32 * 12)
	KERNEL4(32 * 12)
	KERNEL5(32 * 12)
	KERNEL6(32 * 12)
	KERNEL7(32 * 12)
	KERNEL8(32 * 12)
	cmpl	$128 * 13, %eax
	jle	.L12
	KERNEL1(32 * 13)
	KERNEL2(32 * 13)
	KERNEL3(32 * 13)
	KERNEL4(32 * 13)
	KERNEL5(32 * 13)
	KERNEL6(32 * 13)
	KERNEL7(32 * 13)
	KERNEL8(32 * 13)
	cmpl	$128 * 14, %eax
	jle	.L12
	KERNEL1(32 * 14)
	KERNEL2(32 * 14)
	KERNEL3(32 * 14)
	KERNEL4(32 * 14)
	KERNEL5(32 * 14)
	KERNEL6(32 * 14)
	KERNEL7(32 * 14)
	KERNEL8(32 * 14)
	cmpl	$128 * 15, %eax
	jle	.L12
	KERNEL1(32 * 15)
	KERNEL2(32 * 15)
	KERNEL3(32 * 15)
	KERNEL4(32 * 15)
	KERNEL5(32 * 15)
	KERNEL6(32 * 15)
	KERNEL7(32 * 15)
	KERNEL8(32 * 15)
#else
	addl	$128 * 4  * SIZE, BB
	addl	$128 * 2  * SIZE, AA
	subl	$128 * 8, %eax
	jg	.L1X
	jmp	.L15
#endif

.L12:
	leal	(AA, %eax, 1), AA
	leal	(BB, %eax, 2), BB
	ALIGN_4
#else
	sarl	$3, %eax
	je	.L15
	ALIGN_4

.L11:
	KERNEL1(32 *  7)
	KERNEL2(32 *  7)
	KERNEL3(32 *  7)
	KERNEL4(32 *  7)
	KERNEL5(32 *  7)
	KERNEL6(32 *  7)
	KERNEL7(32 *  7)
	KERNEL8(32 *  7)

	addl	$32 * SIZE, AA
	addl	$64 * SIZE, BB
	decl   %eax
	jne    .L11
	ALIGN_4
#endif

.L15:
#ifndef TRMMKERNEL
	movl	K, %eax
#else
	movl	KKK, %eax
#endif
	movaps	ALPHA_R, %xmm1
	movaps	ALPHA_I, %xmm3
	andl	$7, %eax		# if (k & 1)
	BRANCH
	je .L14
	ALIGN_4

.L13:
	mulps	%xmm0, %xmm2
	addps	%xmm2, %xmm4
	movshdup  0 * SIZE(BB), %xmm2
	mulps	%xmm0, %xmm2
	ADDSUB	%xmm2, %xmm5
	movsldup  4 * SIZE(BB), %xmm2
	mulps	%xmm0, %xmm2
	addps	%xmm2, %xmm6
	movshdup  4 * SIZE(BB), %xmm2
	mulps	%xmm0, %xmm2
	movaps	  4 * SIZE(AA), %xmm0
	ADDSUB	%xmm2, %xmm7
	movsldup  8 * SIZE(BB), %xmm2

	addl	$4 * SIZE, AA
	addl	$8 * SIZE, BB
	decl	%eax
	jg	.L13
	ALIGN_4

.L14:
#if defined(NN) || defined(NT) || defined(TN) || defined(TT) || \
    defined(NR) || defined(NC) || defined(TR) || defined(TC)

	shufps	$0xb1, %xmm5, %xmm5
	shufps	$0xb1, %xmm7, %xmm7

	addsubps	%xmm5, %xmm4
	addsubps	%xmm7, %xmm6

	movaps	%xmm4, %xmm5
	movaps	%xmm6, %xmm7

	shufps	$0xb1, %xmm4, %xmm4
	shufps	$0xb1, %xmm6, %xmm6
#else
	shufps	$0xb1, %xmm4, %xmm4
	shufps	$0xb1, %xmm6, %xmm6

	addsubps	%xmm4, %xmm5
	addsubps	%xmm6, %xmm7

	movaps	%xmm5, %xmm4
	movaps	%xmm7, %xmm6

	shufps	$0xb1, %xmm5, %xmm5
	shufps	$0xb1, %xmm7, %xmm7
#endif

	mulps	%xmm1, %xmm5
	mulps	%xmm3, %xmm4
	mulps	%xmm1, %xmm7
	mulps	%xmm3, %xmm6

	addps	%xmm5, %xmm4
	addps	%xmm7, %xmm6

#ifndef TRMMKERNEL
	shufps	$0xe4, %xmm0, %xmm0
	movsd	0 * SIZE(%esi), %xmm0
	movhps	2 * SIZE(%esi), %xmm0

	shufps	$0xe4, %xmm2, %xmm2
	movsd	0 * SIZE(%esi, LDC), %xmm2
	movhps	2 * SIZE(%esi, LDC), %xmm2

	addps	%xmm0, %xmm4
	addps	%xmm2, %xmm6
#endif

	movsd	%xmm4, 0 * SIZE(%esi)
	movhps	%xmm4, 2 * SIZE(%esi)
	movsd	%xmm6, 0 * SIZE(%esi, LDC)
	movhps	%xmm6, 2 * SIZE(%esi, LDC)

#if (defined(TRMMKERNEL) &&  defined(LEFT) &&  defined(TRANSA)) || \
    (defined(TRMMKERNEL) && !defined(LEFT) && !defined(TRANSA))
	movl	K, %eax
	subl	KKK, %eax
	leal	(,%eax, 8), %eax
	leal	(AA, %eax, 2), AA
	leal	(BB, %eax, 4), BB
#endif

#if defined(TRMMKERNEL) && defined(LEFT)
	addl	$2, KK
#endif

	addl	$4 * SIZE, %esi		# coffset += 4
	decl	%ebx			# i --
	jg	.L10
	ALIGN_4

.L30:
	movl	M,  %ebx
	andl	$1, %ebx
	jle	.L99
	ALIGN_4

.L40:
#if !defined(TRMMKERNEL) || \
	(defined(TRMMKERNEL) &&  defined(LEFT) &&  defined(TRANSA)) || \
	(defined(TRMMKERNEL) && !defined(LEFT) && !defined(TRANSA))

	leal	BUFFER, BB	# boffset1 = boffset
#else
	leal	BUFFER, BB	# boffset1 = boffset
	movl	KK, %eax
	leal	(, %eax,    8), %eax
	leal	(AA, %eax,  1), AA
	leal	(BB,  %eax, 4), BB
#endif

	movddup	  0 * SIZE(AA), %xmm0
	pxor	%xmm4, %xmm4
	movddup	  8 * SIZE(AA), %xmm1
	pxor	%xmm5, %xmm5
	movsd     0 * SIZE(BB), %xmm2
	movsd    16 * SIZE(BB), %xmm3

#ifndef TRMMKERNEL
	movl	K, %eax
#elif (defined(LEFT) && !defined(TRANSA)) || (!defined(LEFT) && defined(TRANSA))
	movl	K, %eax
	subl	KK, %eax
	movl	%eax, KKK
#else
	movl	KK, %eax
#ifdef LEFT
	addl	$1, %eax
#else
	addl	$2, %eax
#endif
	movl	%eax, KKK
#endif
	sarl	$3, %eax
	je	.L42
	ALIGN_4

.L41:
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm0, %xmm2
	PREFETCH  (PREFETCHSIZE +  0) * SIZE(AA)
	addps	%xmm2, %xmm4
	movsd     4 * SIZE(BB), %xmm2
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm0, %xmm2
	movddup	  2 * SIZE(AA), %xmm0
	addps	%xmm2, %xmm5
	movsd     8 * SIZE(BB), %xmm2
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm0, %xmm2
	addps	%xmm2, %xmm4
	movsd    12 * SIZE(BB), %xmm2
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm0, %xmm2
	movddup	  4 * SIZE(AA), %xmm0
	addps	%xmm2, %xmm5
	movsd    32 * SIZE(BB), %xmm2
	shufps	$0x50, %xmm3, %xmm3
	mulps	%xmm0, %xmm3
	addps	%xmm3, %xmm4
	movsd    20 * SIZE(BB), %xmm3
	shufps	$0x50, %xmm3, %xmm3
	mulps	%xmm0, %xmm3
	movddup	  6 * SIZE(AA), %xmm0
	addps	%xmm3, %xmm5
	movsd    24 * SIZE(BB), %xmm3
	shufps	$0x50, %xmm3, %xmm3
	mulps	%xmm0, %xmm3
	addps	%xmm3, %xmm4
	movsd    28 * SIZE(BB), %xmm3
	shufps	$0x50, %xmm3, %xmm3
	mulps	%xmm0, %xmm3
	movddup	 16 * SIZE(AA), %xmm0
	addps	%xmm3, %xmm5
	movsd    48 * SIZE(BB), %xmm3
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm1, %xmm2
	addps	%xmm2, %xmm4
	movsd    36 * SIZE(BB), %xmm2
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm1, %xmm2
	movddup	 10 * SIZE(AA), %xmm1
	addps	%xmm2, %xmm5
	movsd    40 * SIZE(BB), %xmm2
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm1, %xmm2
	addps	%xmm2, %xmm4
	movsd    44 * SIZE(BB), %xmm2
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm1, %xmm2
	movddup	 12 * SIZE(AA), %xmm1
	addps	%xmm2, %xmm5
	movsd    64 * SIZE(BB), %xmm2
	shufps	$0x50, %xmm3, %xmm3
	mulps	%xmm1, %xmm3
	addps	%xmm3, %xmm4
	movsd    52 * SIZE(BB), %xmm3
	shufps	$0x50, %xmm3, %xmm3
	mulps	%xmm1, %xmm3
	movddup	 14 * SIZE(AA), %xmm1
	addps	%xmm3, %xmm5
	movsd    56 * SIZE(BB), %xmm3
	shufps	$0x50, %xmm3, %xmm3
	mulps	%xmm1, %xmm3
	addps	%xmm3, %xmm4
	movsd    60 * SIZE(BB), %xmm3
	shufps	$0x50, %xmm3, %xmm3
	mulps	%xmm1, %xmm3
	movddup	 24 * SIZE(AA), %xmm1
	addps	%xmm3, %xmm5
	movsd    80 * SIZE(BB), %xmm3

	addl	$16 * SIZE, AA
	addl	$64 * SIZE, BB
	decl	%eax
	jne	.L41
	ALIGN_4

.L42:
#ifndef TRMMKERNEL
	movl	K, %eax
#else
	movl	KKK, %eax
#endif
	movaps	ALPHA_R, %xmm1
	movaps	ALPHA_I, %xmm3
	andl	$7, %eax		# if (k & 1)
	BRANCH
	je .L44
	ALIGN_4

.L43:
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm0, %xmm2
	addps	%xmm2, %xmm4
	movsd     4 * SIZE(BB), %xmm2
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm0, %xmm2
 	movddup	  2 * SIZE(AA), %xmm0
	addps	%xmm2, %xmm5
	movsd     8 * SIZE(BB), %xmm2

	addl	$2 * SIZE, AA
	addl	$8 * SIZE, BB
	decl	%eax
	jg	.L43
	ALIGN_4

.L44:
	movaps	%xmm4, %xmm6
	movlhps	%xmm5, %xmm4
	movhlps %xmm6, %xmm5

#if  defined(NR) || defined(NC) || defined(TR) || defined(TC) || \
     defined(RR) || defined(RC) || defined(CR) || defined(CC)
	cmpeqps	%xmm7, %xmm7
	pslld	$31,   %xmm7
	xorps	%xmm7, %xmm5
#endif

#if defined(NN) || defined(NT) || defined(TN) || defined(TT) || \
    defined(NR) || defined(NC) || defined(TR) || defined(TC)
	shufps	$0xb1, %xmm5, %xmm5

	addsubps	%xmm5, %xmm4

	movaps	%xmm4, %xmm5

	shufps	$0xb1, %xmm4, %xmm4
#else
	shufps	$0xb1, %xmm4, %xmm4

	addsubps	%xmm4, %xmm5

	movaps	%xmm5, %xmm4

	shufps	$0xb1, %xmm5, %xmm5
#endif

	mulps	%xmm1, %xmm5
	mulps	%xmm3, %xmm4

	addps	%xmm5, %xmm4

#ifndef TRMMKERNEL
	movsd	0 * SIZE(%esi), %xmm0
	movhps	0 * SIZE(%esi, LDC), %xmm0

	addps	%xmm0, %xmm4
#endif

	movsd	%xmm4, 0 * SIZE(%esi)
	movhps	%xmm4, 0 * SIZE(%esi, LDC)

#if (defined(TRMMKERNEL) &&  defined(LEFT) &&  defined(TRANSA)) || \
    (defined(TRMMKERNEL) && !defined(LEFT) && !defined(TRANSA))
	movl	K, %eax
	subl	KKK, %eax
	leal	(,%eax, 8), %eax
	leal	(AA, %eax, 1), AA
	leal	(BB, %eax, 4), BB
#endif

#if defined(TRMMKERNEL) && defined(LEFT)
	addl	$1, KK
#endif
	ALIGN_4

.L99:
#if defined(TRMMKERNEL) && !defined(LEFT)
	addl	$2, KK
#endif

	leal	(LDC, LDC), %eax
	addl	%eax, C			# c += 2 * ldc
	decl	J			# j --
	jg	.L01
	ALIGN_4

.L100:
	movl	N, %eax
	andl	$1, %eax
	jle	.L999
	ALIGN_4

.L101:
#if defined(TRMMKERNEL) && defined(LEFT)
	movl	OFFSET, %eax
	movl	%eax, KK
#endif

/* Copying to Sub Buffer */
	leal	BUFFER, %ecx

	movl	K, %eax
	sarl	$3, %eax
	jle	.L103
	ALIGN_4

.L102:
	movddup	 0 * SIZE(B), %xmm0
	movddup	 2 * SIZE(B), %xmm1
	movddup	 4 * SIZE(B), %xmm2
	movddup	 6 * SIZE(B), %xmm3
	movddup	 8 * SIZE(B), %xmm4
	movddup 10 * SIZE(B), %xmm5
	movddup	12 * SIZE(B), %xmm6
	movddup	14 * SIZE(B), %xmm7

	movaps	%xmm0,  0 * SIZE(BB)
	movaps	%xmm1,  4 * SIZE(BB)
	movaps	%xmm2,  8 * SIZE(BB)
	movaps	%xmm3, 12 * SIZE(BB)
	movaps	%xmm4, 16 * SIZE(BB)
	movaps	%xmm5, 20 * SIZE(BB)
	movaps	%xmm6, 24 * SIZE(BB)
	movaps	%xmm7, 28 * SIZE(BB)

	prefetcht0	 104 * SIZE(B)

	addl	$16 * SIZE, B
	addl	$32 * SIZE, BB
	decl	%eax
	jne	.L102
	ALIGN_4

.L103:
	movl	K, %eax
	andl	$7, %eax
	BRANCH
	jle	.L105
	ALIGN_4

.L104:
	movddup	0 * SIZE(B), %xmm0

	movaps	%xmm0,  0 * SIZE(BB)

	addl	$ 2 * SIZE, %edi
	addl	$ 4 * SIZE, %ecx
	decl	%eax
	jne	.L104
	ALIGN_4

.L105:
	movl	C,  %esi
	movl	A,  AA
	movl	M,  %ebx
	sarl	$1, %ebx
	jle	.L130
	ALIGN_4

.L110:
#if !defined(TRMMKERNEL) || \
	(defined(TRMMKERNEL) &&  defined(LEFT) &&  defined(TRANSA)) || \
	(defined(TRMMKERNEL) && !defined(LEFT) && !defined(TRANSA))

	leal	BUFFER, BB	# boffset1 = boffset
#else
	leal	BUFFER, BB	# boffset1 = boffset
	movl	KK, %eax
	leal	(, %eax,    8), %eax
	leal	(AA, %eax,  2), AA
	leal	(BB,  %eax, 2), BB
#endif

	movaps	  0 * SIZE(AA), %xmm0
	pxor	%xmm4, %xmm4
	movaps	 16 * SIZE(AA), %xmm1
	pxor	%xmm5, %xmm5
	movsldup  0 * SIZE(BB), %xmm2
	pxor	%xmm6, %xmm6
	movsldup 16 * SIZE(BB), %xmm3
	pxor	%xmm7, %xmm7

#ifdef PENTIUM4
	prefetchnta 4 * SIZE(%esi)
#endif

#ifndef TRMMKERNEL
	movl	K, %eax
#elif (defined(LEFT) && !defined(TRANSA)) || (!defined(LEFT) && defined(TRANSA))
	movl	K, %eax
	subl	KK, %eax
	movl	%eax, KKK
#else
	movl	KK, %eax
#ifdef LEFT
	addl	$2, %eax
#else
	addl	$1, %eax
#endif
	movl	%eax, KKK
#endif
	sarl	$3, %eax
	je	.L112
	ALIGN_4

.L111:
	mulps	%xmm0, %xmm2
	addps	%xmm2, %xmm4
	PREFETCH  (PREFETCHSIZE +  0) * SIZE(AA)
	movshdup  0 * SIZE(BB), %xmm2
	mulps	%xmm0, %xmm2
	movaps	  4 * SIZE(AA), %xmm0
	ADDSUB	%xmm2, %xmm5
	movsldup  4 * SIZE(BB), %xmm2
	mulps	%xmm0, %xmm2
	addps	%xmm2, %xmm4
	movshdup  4 * SIZE(BB), %xmm2
	mulps	%xmm0, %xmm2
	movaps	  8 * SIZE(AA), %xmm0
	ADDSUB	%xmm2, %xmm5
	movsldup  8 * SIZE(BB), %xmm2
	mulps	%xmm0, %xmm2
	addps	%xmm2, %xmm4
	movshdup  8 * SIZE(BB), %xmm2
	mulps	%xmm0, %xmm2
	movaps	 12 * SIZE(AA), %xmm0
	ADDSUB	%xmm2, %xmm5
	movsldup 12 * SIZE(BB), %xmm2
	mulps	%xmm0, %xmm2
	addps	%xmm2, %xmm4
	movshdup 12 * SIZE(BB), %xmm2
	mulps	%xmm0, %xmm2
	movaps	 32 * SIZE(AA), %xmm0
	ADDSUB	%xmm2, %xmm5
	movsldup 32 * SIZE(BB), %xmm2
	mulps	%xmm1, %xmm3
	addps	%xmm3, %xmm4
	movshdup 16 * SIZE(BB), %xmm3
	mulps	%xmm1, %xmm3
	movaps	 20 * SIZE(AA), %xmm1
	ADDSUB	%xmm3, %xmm5
	movsldup 20 * SIZE(BB), %xmm3
	mulps	%xmm1, %xmm3
	addps	%xmm3, %xmm4
	movshdup 20 * SIZE(BB), %xmm3
	mulps	%xmm1, %xmm3
	movaps	 24 * SIZE(AA), %xmm1
	ADDSUB	%xmm3, %xmm5
	movsldup 24 * SIZE(BB), %xmm3
	mulps	%xmm1, %xmm3
	addps	%xmm3, %xmm4
	movshdup 24 * SIZE(BB), %xmm3
	mulps	%xmm1, %xmm3
	movaps	 28 * SIZE(AA), %xmm1
	ADDSUB	%xmm3, %xmm5
	movsldup 28 * SIZE(BB), %xmm3
	mulps	%xmm1, %xmm3
	addps	%xmm3, %xmm4
	movshdup 28 * SIZE(BB), %xmm3
	mulps	%xmm1, %xmm3
	movaps	 48 * SIZE(AA), %xmm1
	ADDSUB	%xmm3, %xmm5
	movsldup 48 * SIZE(BB), %xmm3

	addl	$32 * SIZE, AA
	addl	$32 * SIZE, BB
	decl	%eax
	jne	.L111
	ALIGN_4

.L112:
#ifndef TRMMKERNEL
	movl	K, %eax
#else
	movl	KKK, %eax
#endif
	movaps	ALPHA_R, %xmm1
	movaps	ALPHA_I, %xmm3
	andl	$7, %eax		# if (k & 1)
	BRANCH
	je .L114
	ALIGN_4

.L113:
	mulps	%xmm0, %xmm2
	addps	%xmm2, %xmm4
	movshdup  0 * SIZE(BB), %xmm2
	mulps	%xmm0, %xmm2
	movaps	  4 * SIZE(AA), %xmm0
	ADDSUB	%xmm2, %xmm5
	movsldup  4 * SIZE(BB), %xmm2

	addl	$ 4 * SIZE, AA
	addl	$ 4 * SIZE, BB
	decl	%eax
	jg	.L113
	ALIGN_4

.L114:
#if defined(NN) || defined(NT) || defined(TN) || defined(TT) || \
    defined(NR) || defined(NC) || defined(TR) || defined(TC)

	shufps	$0xb1, %xmm5, %xmm5

	addsubps	%xmm5, %xmm4

	movaps	%xmm4, %xmm5

	shufps	$0xb1, %xmm4, %xmm4
#else
	shufps	$0xb1, %xmm4, %xmm4

	addsubps	%xmm4, %xmm5

	movaps	%xmm5, %xmm4

	shufps	$0xb1, %xmm5, %xmm5
#endif

	mulps	%xmm1, %xmm5
	mulps	%xmm3, %xmm4

	addps	%xmm5, %xmm4

#ifndef TRMMKERNEL
	movsd	0 * SIZE(%esi), %xmm0
	movhps	2 * SIZE(%esi), %xmm0

	addps	%xmm0, %xmm4
#endif

	movsd	%xmm4, 0 * SIZE(%esi)
	movhps	%xmm4, 2 * SIZE(%esi)

#if (defined(TRMMKERNEL) &&  defined(LEFT) &&  defined(TRANSA)) || \
    (defined(TRMMKERNEL) && !defined(LEFT) && !defined(TRANSA))
	movl	K, %eax
	subl	KKK, %eax
	leal	(,%eax, 8), %eax
	leal	(AA, %eax, 2), AA
	leal	(BB, %eax, 2), BB
#endif

#if defined(TRMMKERNEL) && defined(LEFT)
	addl	$2, KK
#endif

	addl	$4 * SIZE, %esi		# coffset += 4
	decl	%ebx			# i --
	jg	.L110
	ALIGN_4

.L130:
	movl	M,  %ebx
	andl	$1, %ebx
	jle	.L999
	ALIGN_4

.L140:
#if !defined(TRMMKERNEL) || \
	(defined(TRMMKERNEL) &&  defined(LEFT) &&  defined(TRANSA)) || \
	(defined(TRMMKERNEL) && !defined(LEFT) && !defined(TRANSA))

	leal	BUFFER, BB	# boffset1 = boffset
#else
	leal	BUFFER, BB	# boffset1 = boffset
	movl	KK, %eax
	leal	(, %eax,    8), %eax
	leal	(AA, %eax,  1), AA
	leal	(BB,  %eax, 2), BB
#endif

	movddup	  0 * SIZE(AA), %xmm0
	pxor	%xmm4, %xmm4
	movddup	  8 * SIZE(AA), %xmm1
	pxor	%xmm5, %xmm5
	movsd     0 * SIZE(BB), %xmm2
	movsd    16 * SIZE(BB), %xmm3

#ifndef TRMMKERNEL
	movl	K, %eax
#elif (defined(LEFT) && !defined(TRANSA)) || (!defined(LEFT) && defined(TRANSA))
	movl	K, %eax
	subl	KK, %eax
	movl	%eax, KKK
#else
	movl	KK, %eax
#ifdef LEFT
	addl	$1, %eax
#else
	addl	$1, %eax
#endif
	movl	%eax, KKK
#endif
	sarl	$3, %eax
	je	.L142
	ALIGN_4

.L141:
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm0, %xmm2
	PREFETCH  (PREFETCHSIZE +  0) * SIZE(AA)
	movddup	  2 * SIZE(AA), %xmm0
	addps	%xmm2, %xmm4
	movsd     4 * SIZE(BB), %xmm2
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm0, %xmm2
	movddup	  4 * SIZE(AA), %xmm0
	addps	%xmm2, %xmm5
	movsd     8 * SIZE(BB), %xmm2
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm0, %xmm2
	movddup	  6 * SIZE(AA), %xmm0
	addps	%xmm2, %xmm4
	movsd    12 * SIZE(BB), %xmm2
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm0, %xmm2
	movddup	 16 * SIZE(AA), %xmm0
	addps	%xmm2, %xmm5
	movsd    32 * SIZE(BB), %xmm2
	shufps	$0x50, %xmm3, %xmm3
	mulps	%xmm1, %xmm3
	movddup	 10 * SIZE(AA), %xmm1
	addps	%xmm3, %xmm4
	movsd    20 * SIZE(BB), %xmm3
	shufps	$0x50, %xmm3, %xmm3
	mulps	%xmm1, %xmm3
	movddup	 12 * SIZE(AA), %xmm1
	addps	%xmm3, %xmm5
	movsd    24 * SIZE(BB), %xmm3
	shufps	$0x50, %xmm3, %xmm3
	mulps	%xmm1, %xmm3
	movddup	 14 * SIZE(AA), %xmm1
	addps	%xmm3, %xmm4
	movsd    28 * SIZE(BB), %xmm3
	shufps	$0x50, %xmm3, %xmm3
	mulps	%xmm1, %xmm3
	movddup	 24 * SIZE(AA), %xmm1
	addps	%xmm3, %xmm5
	movsd    48 * SIZE(BB), %xmm3

	addl	$ 16 * SIZE, AA
	addl	$ 32 * SIZE, BB
	decl	%eax
	jne	.L141
	ALIGN_4

.L142:
#ifndef TRMMKERNEL
	movl	K, %eax
#else
	movl	KKK, %eax
#endif
	movaps	ALPHA_R, %xmm1
	movaps	ALPHA_I, %xmm3
	andl	$7, %eax		# if (k & 1)
	BRANCH
	je .L144
	ALIGN_4

.L143:
	shufps	$0x50, %xmm2, %xmm2
	mulps	%xmm0, %xmm2
	movddup	  2 * SIZE(AA), %xmm0
	addps	%xmm2, %xmm4
	movsd     4 * SIZE(BB), %xmm2

	addl	$2 * SIZE, AA
	addl	$4 * SIZE, BB
	decl	%eax
	jg	.L143
	ALIGN_4

.L144:
	addps	%xmm5, %xmm4

	movhlps %xmm4, %xmm5

#if  defined(NR) || defined(NC) || defined(TR) || defined(TC) || \
     defined(RR) || defined(RC) || defined(CR) || defined(CC)
	cmpeqps	%xmm7, %xmm7
	pslld	$31,   %xmm7
	xorps	%xmm7, %xmm5
#endif

#if defined(NN) || defined(NT) || defined(TN) || defined(TT) || \
    defined(NR) || defined(NC) || defined(TR) || defined(TC)
	shufps	$0xb1, %xmm5, %xmm5

	addsubps	%xmm5, %xmm4

	movaps	%xmm4, %xmm5

	shufps	$0xb1, %xmm4, %xmm4
#else
	shufps	$0xb1, %xmm4, %xmm4

	addsubps	%xmm4, %xmm5

	movaps	%xmm5, %xmm4

	shufps	$0xb1, %xmm5, %xmm5
#endif

	mulps	%xmm1, %xmm5
	mulps	%xmm3, %xmm4

	addps	%xmm5, %xmm4

#ifndef TRMMKERNEL
	movsd	0 * SIZE(%esi), %xmm0
	addps	%xmm0, %xmm4
#endif
	movsd	%xmm4, 0 * SIZE(%esi)
	ALIGN_4

.L999:
	movl	OLD_STACK, %esp
	popl	%ebx
	popl	%esi
	popl	%edi
	popl	%ebp
	ret

	EPILOGUE


Tous vos flux en un seul endroit