#!/usr/bin/env python
#
# Copyright 2007 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#


"""A request-local environment and logging stream."""








import abc
import cgi
import collections
import os
import sys
import threading

import six

_sys_stderr = sys.stderr


class RequestEnvironment(threading.local):
  """A thread local request environment.

  A thread local request environment that provides an error stream errors and a
  dict of the request environment environ as would be found in os.environ. A
  single error stream is shared between threads of a single request, but each
  thread accesses an independent copy of environ created when
  CloneRequestEnvironment is called. A request environment of one thread can be
  installed in another thread as follows:
    1. Call CloneRequestEnvironment in the first thread.
    2. Call the returned callable from the other thread.
  """

  def __init__(self):
    super(RequestEnvironment, self).__init__()
    self.Reset()

  def Reset(self):
    """Resets the error stream and environment for this request."""
    self.errors = _sys_stderr
    self.environ = {}
    self.runtime_stats = {}

  def Init(self, errors, environ, runtime_stats=None):
    self.errors = errors

    if environ is not None:
      self.environ = environ

    if runtime_stats is None:
      self.runtime_stats = {}
    else:
      self.runtime_stats = runtime_stats

  def CloneRequestEnvironment(self):
    """Returns a callable that will install the environment in another thread.

    Returns:
      A callable that will duplicate the request environment of this thread in
      another thread that calls it.
    """
    errors = self.errors
    environ = dict(self.environ)
    runtime_stats = self.runtime_stats
    return lambda: self.Init(errors, environ, runtime_stats)

  def Clear(self):
    """Clears the thread locals."""
    self.__dict__.clear()
    self.Reset()

  def IncrementStat(self, metric, code):
    """Increments a tracked runtime statistic."""
    m = self.runtime_stats.get(metric)
    if m is None: return
    m[code] = m.get(code, 0) + 1


class RequestLocalStream(object):
  """A stream that delegates to a RequestEnvironment stream."""

  def __init__(self, request):
    self._request = request

  def close(self):
    pass

  def flush(self):
    self._request.errors.flush()

  def write(self, data):
    self._request.errors.write(data)

  def writelines(self, data):
    self._request.errors.writelines(data)

  def isatty(self):
    return False


class RequestLocalEnviron(collections.abc.MutableMapping):
  """A MutableMapping that delegates to a RequestEnvironment environ."""

  def __init__(self, request):
    self._request = request

  def __len__(self):
    return len(self._request.environ)

  def __iter__(self):
    return iter(self._request.environ)

  def __getitem__(self, key):
    return self._request.environ[key]

  def __setitem__(self, key, value):
    self._request.environ[key] = value

  def __delitem__(self, key):
    del self._request.environ[key]

  def __repr__(self):
    return repr(self._request.environ)

  def has_key(self, key):
    return key in self._request.environ

  def copy(self):
    return dict(self._request.environ)



  def viewitems(self):
    return collections.ItemsView(self)

  def viewkeys(self):
    return collections.KeysView(self)

  def viewvalues(self):
    return collections.ValuesView(self)


current_request = RequestEnvironment()





def PatchOsEnviron(os_module=os):
  """Replace os.environ by a RequestLocalEnviron instance.

  This is called from init.py when it modifies the execution
  environment (in the wider sense of the word).

  Args:
    os_module: An optional module to patch. Defaults to os.
  """
  os_module.environ = RequestLocalEnviron(current_request)


  cached_class = cgi.FieldStorage
  six.moves.reload_module(cgi)




  class FieldStorage(six.with_metaclass(abc.ABCMeta, cgi.FieldStorage, object)):
    pass

  FieldStorage.register(cached_class)

  cgi.FieldStorage = FieldStorage
