from System import Convert
from System.Globalization import CultureInfo
from System import DateTime

from System import Uri
from System.Net import WebException
from System.Net import WebRequest
from System.Net import HttpWebRequest
from System.Net import HttpStatusCode
from System.IO import StreamReader
from System.Text import UTF8Encoding
from System.Text import StringBuilder
from System.Guid import NewGuid

from orangescape.model.server.azure.util.StorageServiceUtil import CreateAuthorizationHeader
from orangescape.model.server.azure.util.NetDomUtil import xml2dict, dict2xml
from orangescape.model.server.util.listutil import split_seq

from storageconfig import PrimaryAccessKey
from storageconfig import Account
from storageconfig import TableEndPoint
from storageconfig import msVersion


import logging
operator_mapping = {'=':' eq ', '!=':' ne ', '>':' gt ', '<':' lt ', '>=':' ge ', '<=':' le '}
batch_count = 100

class storagemediatorservice:
    def __init__(self):
        self.Key = Convert.FromBase64String(PrimaryAccessKey)

    def __doGetRequest__(self, urlPath):
        requestMethod = "GET"
        dateInRfc1123Format = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture)

        index = urlPath.find('?')
        resource = urlPath[:index] if index > 0 else urlPath

        canonicalizedResource = "/%s/%s"%(Account, resource)
        stringToSign = "%s\n\n\n%s\n%s"%(requestMethod, dateInRfc1123Format, canonicalizedResource)
        authorizationHeader = CreateAuthorizationHeader(stringToSign, self.Key);

        uri = Uri(TableEndPoint + urlPath)
        request = WebRequest.Create(uri)
        request.Method = requestMethod

        request.Headers.Add("x-ms-date", dateInRfc1123Format);
        request.Headers.Add("x-ms-version", msVersion);
        request.Headers.Add("Authorization", authorizationHeader);

        request.Headers.Add("Accept-Charset", "UTF-8");

        request.Headers.Add("DataServiceVersion", "1.0;NetFx");
        request.Headers.Add("MaxDataServiceVersion", "1.0;NetFx");
        logging.info('GET: '+ urlPath)
        try:
            response = request.GetResponse()
            dataStream = response.GetResponseStream()
            reader = StreamReader(dataStream)
            responseFromServer = reader.ReadToEnd()
            logging.info('GET response: '+ responseFromServer)
            records = xml2dict(responseFromServer)
            return records
        except WebException, e:
            if e.Response and e.Response.StatusCode == HttpStatusCode.NotFound:
                return None
            raise

    def __delete__(self, urlPath):
        requestMethod = 'DELETE'
        dateInRfc1123Format = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture)
        contentType = "application/atom+xml";

        canonicalizedResource = "/%s/%s"%(Account, urlPath)
        stringToSign = "%s\n%s\n%s\n%s\n%s"%(requestMethod, '', contentType,  dateInRfc1123Format, canonicalizedResource)
        authorizationHeader = CreateAuthorizationHeader(stringToSign, self.Key);
        utf8Encoding = UTF8Encoding()
        uri = Uri(TableEndPoint + urlPath)
        request = WebRequest.Create(uri)

        request.ContentLength = 0;
        request.ContentType = contentType;
        request.Method = requestMethod

        request.Headers.Add("x-ms-date", dateInRfc1123Format);
        request.Headers.Add("x-ms-version", msVersion);
        request.Headers.Add("Authorization", authorizationHeader);
        request.Headers.Add("Accept-Charset", "UTF-8");
        request.Headers.Add("If-Match", "*");

        request.Headers.Add("DataServiceVersion", "1.0;NetFx");
        request.Headers.Add("MaxDataServiceVersion", "1.0;NetFx");
        print 'DELETE: ', urlPath
        try:
            response = request.GetResponse()
            dataStream = response.GetResponseStream()
            reader = StreamReader(dataStream)
            if response.StatusCode==HttpStatusCode.NoContent:
                return None
            return None
        except WebException, e:
            print e.Response.StatusCode
            print e.Response.StatusDescription
            if e.Response.StatusCode == HttpStatusCode.NotFound:
                return None
            if e.Response.StatusCode == HttpStatusCode.Conflict:
                raise
            raise

    def __upload__(self, urlPath, content, requestMethod, contentType = "application/atom+xml", accept = None):
        dateInRfc1123Format = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture)

        canonicalizedResource = "/%s/%s"%(Account, urlPath)
        stringToSign = "%s\n%s\n%s\n%s\n%s"%(requestMethod, '', contentType,  dateInRfc1123Format, canonicalizedResource)
        authorizationHeader = CreateAuthorizationHeader(stringToSign, self.Key);
        utf8Encoding = UTF8Encoding()
        content = utf8Encoding.GetBytes(content)
        uri = Uri(TableEndPoint + urlPath)
        request = WebRequest.Create(uri)

        request.ContentLength = content.Length;
        request.ContentType = contentType;
        request.Method = requestMethod

        request.Headers.Add("x-ms-date", dateInRfc1123Format);
        request.Headers.Add("x-ms-version", msVersion);
        request.Headers.Add("Authorization", authorizationHeader);
        request.Headers.Add("Accept-Charset", "UTF-8");
        if accept:
            request.Accept = accept
        if requestMethod=='PUT':
            request.Headers.Add("If-Match", "*");

        request.Headers.Add("DataServiceVersion", "1.0;NetFx");
        request.Headers.Add("MaxDataServiceVersion", "1.0;NetFx");

        requestStream = request.GetRequestStream()
        requestStream.Write(content, 0, content.Length)
        try:
            response = request.GetResponse()
            dataStream = response.GetResponseStream()
            reader = StreamReader(dataStream)
            responseFromServer = reader.ReadToEnd()
            if response.StatusCode==HttpStatusCode.NoContent:
                return None
            return responseFromServer
        except WebException, e:
            print e.Response.StatusCode
            print e.Response.StatusDescription
            if e.Response.StatusCode == HttpStatusCode.NotFound:
                return None
            if e.Response.StatusCode == HttpStatusCode.Conflict:
                raise
            raise

    def get(self, SheetId, MetadataId):
        urlPath = "%s(PartitionKey='%s',RowKey='%s')"%(self.getTableName(MetadataId), self.getPK(SheetId), SheetId)
        res = self.__doGetRequest__(urlPath)
        if res and len(res)>0:
            return res[0]
        return None

    def put(self, entity):
        if entity.has_key('Timestamp'):
            urlPath = "%s(PartitionKey='%s',RowKey='%s')"%(self.getTableName(entity['SheetMetadataId']), self.getPK(entity['RowKey']), entity['RowKey'])
            self.__upload__(urlPath, dict2xml(entity), 'PUT')
            responseEntity = entity
        else:
            urlPath = self.getTableName(entity['SheetMetadataId'])
            response = self.__upload__(urlPath, dict2xml(entity), 'POST')
            if response:
                responseEntity = xml2dict(response)
            else:
                responseEntity = None
        return responseEntity

    def delete(self, entity):
        if entity.has_key('Timestamp'):
            urlPath = "%s(PartitionKey='%s',RowKey='%s')"%(entity['SheetMetadataId'], self.getPK(entity['RowKey']), entity['RowKey'])
            self.__delete__(urlPath)

    def getByIdList(self, MetadataId, idList):
        baseQuery = [('and', 'and', [('PartitionKey', '=', self.getPK(id)), ('RowKey', '=', id)]) for id in idList]
        limit = 1000
        urlPath = "%s()?$filter=%s&$top=%s"%(self.getTableName(MetadataId), self.constructqs(baseQuery, 'or'), limit)
        return self.__doGetRequest__(urlPath)

    def get_multi(self, idDict):

        return response

    def batch(self, entities, deletedList):
        uBatches, dBatches = self.__split_batch__(entities, deletedList)
        responseEntities = []

        for batchList in uBatches:
            for sBatch in split_seq(batchList, batch_count):
                mixedContent = self.__batch_update__(sBatch)
                start_split = mixedContent.split('<?xml')
                start_split.pop(0)
                entityList = [xml2dict('<?xml%s</entry>'%(sp.split('</entry>')[0]))[0] for sp in start_split]
                responseEntities.extend(entityList)

        for batchList in dBatches:
            if len(batchList) > batch_count:
                for sBatch in split_seq(batchList, batch_count):
                    self.__batch_delete__(sBatch)
            else:
                self.__batch_delete__(batchList)

        return responseEntities

    def __batch_update__(self, entities):
        batchId = 'batch_'+NewGuid().ToString()
        changeSetId = 'changeset_'+NewGuid().ToString()
        content_buff = StringBuilder('--'+batchId)
        content_buff.Append('\nContent-Type: multipart/mixed; boundary=%s'%(changeSetId))
        content_buff.Append('\n')

        contentId = 1
        if entities:
            for entity in entities:
                content_buff.Append(self.__create_update_changeset_content__(changeSetId, entity, contentId))
                contentId+=1

        content_buff.Append('\n--%s--'%(changeSetId))
        content_buff.Append('\n--%s--'%(batchId))

        contentType = 'multipart/mixed; boundary=%s'%(batchId)
        response = self.__upload__('$batch', content_buff.ToString(), 'POST', contentType, 'application/atom+xml,application/xml')
        return response

    def __batch_delete__(self, entities):
        batchId = 'batch_'+NewGuid().ToString()
        changeSetId = 'changeset_'+NewGuid().ToString()
        content_buff = StringBuilder('--'+batchId)
        content_buff.Append('\nContent-Type: multipart/mixed; boundary=%s'%(changeSetId))
        content_buff.Append('\n')

        contentId = 1
        if entities:
            for entity in entities:
                content_buff.Append(self.__create_delete_changeset_content__(changeSetId, entity, contentId))
                contentId+=1
        content_buff.Append('\n--%s--'%(changeSetId))
        content_buff.Append('\n--%s--'%(batchId))

        contentType = 'multipart/mixed; boundary=%s'%(batchId)
        response = self.__upload__('$batch', content_buff.ToString(), 'POST', contentType, 'application/atom+xml,application/xml')

    def __split_batch__(self, entities, deletedList):
        """Need to use some python build-in function to split the dict. using itertools"""
        uBatches, dBatches = {}, {}
        for entity in entities:
            uBatches.setdefault(entity['SheetMetadataId']+self.getPK(entity['RowKey']), list()).append(entity)

        for entity in deletedList:
            dBatches.setdefault(entity['SheetMetadataId']+self.getPK(entity['RowKey']), list()).append(entity)

        return uBatches.values(), dBatches.values()

    def getPK(self, id):
        if id:
            if id.startswith('Company_'):
                return id[8:11]
            if len(id) > 6:
                return id[3:6]
        return 'ora'

    def __create_update_changeset_content__(self, changeSetId, entity, contentId):
        requestMethod = 'POST'

        changeset_buff = StringBuilder('\n--%s'%(changeSetId))
        changeset_buff.Append('\nContent-Type: application/http')
        changeset_buff.Append('\nContent-Transfer-Encoding: binary')
        changeset_buff.Append('\n')

        xmlContent = dict2xml(entity)
        urlPath = self.getTableName(entity['SheetMetadataId'])
        if entity.has_key('Timestamp'):
            requestMethod = 'PUT'
            urlPath = "%s(PartitionKey='%s',RowKey='%s')"%(self.getTableName(entity['SheetMetadataId']), self.getPK(entity['RowKey']), entity['RowKey'])
        changeset_buff.Append('\n%s %s HTTP/1.1'%(requestMethod, TableEndPoint + urlPath))

        changeset_buff.Append('\nContent-ID: %s'%(contentId))
        if requestMethod == 'PUT':
            changeset_buff.Append('\nIf-Match: *')
        changeset_buff.Append('\nContent-Type: application/atom+xml;type=entry')
        changeset_buff.Append('\nContent-Length: %s'%(len(xmlContent)))
        changeset_buff.Append('\n')
        changeset_buff.Append('\n%s'%(xmlContent))

        return changeset_buff

    def __create_delete_changeset_content__(self, changeSetId, entity, contentId):
        requestMethod = 'DELETE'

        changeset_buff = StringBuilder('\n--%s'%(changeSetId))
        changeset_buff.Append('\nContent-Type: application/http')
        changeset_buff.Append('\nContent-Transfer-Encoding: binary')
        changeset_buff.Append('\n')

        urlPath = "%s(PartitionKey='%s',RowKey='%s')"%(self.getTableName(entity['SheetMetadataId']), self.getPK(entity['RowKey']), entity['RowKey'])
        changeset_buff.Append('\n%s %s HTTP/1.1'%(requestMethod, TableEndPoint + urlPath))

        changeset_buff.Append('\nContent-ID: %s'%(contentId))
        changeset_buff.Append('\nIf-Match: *')
        changeset_buff.Append('\n')

        return changeset_buff

    def createTables(self, tables):
        for tableId in tables:
            self.__createTable__(tableId)

    def __createTable__(self, tableId):
        urlPath = 'Tables'
        tableDict = {'TableName' : tableId}
        try:
            responseEntity = self.__upload__(urlPath, dict2xml(tableDict), 'POST')
            return responseEntity
        except WebException, e:
            logging.error('Error while creating table %s. %s'%(tableId, e))
        return None

    def getTables(self):
        urlPath = "Tables"
        return self.__doGetRequest__(urlPath)

    def query(self, MetadataId, baseQuery, limit=1000):
        if baseQuery is None:
            baseQuery = []
        urlPath = "%s()?$filter=%s&$top=%s"%(self.getTableName(MetadataId), self.constructqs(baseQuery), limit)
        return self.__doGetRequest__(urlPath)

    def count(self, MetadataId, baseQuery):
        limit = 1000
        if baseQuery is None:
            baseQuery = []
        urlPath = "%s()?$filter=%s&$top=%s"%(self.getTableName(MetadataId), self.constructqs(baseQuery), limit)
        return len(self.__doGetRequest__(urlPath))

    def constructqs(self, baseQuery, bool_operator='and'):
        buff = list()
        for lhs, operator, value in baseQuery:
            f = list()
            if operator.lower()=='in' and isinstance(value, list):
                if len(value)>0:
                    eqFilter = "%s eq '%s'" if isinstance(value[0], str) else "%s eq %s"
                    inQryStr = ' or '.join([eqFilter%(lhs, str(val)) for val in set(value)])
                    f.append('('+inQryStr+')')
            elif isinstance(value, list):
                f.append('('+self.constructqs(value, lhs)+')')
            elif operator.lower()=='like' and isinstance(value, str):
                likeFilter = "%s ge '%s' and %s le '%s'"%(lhs, str(value), lhs, str(value))
                f.append('('+likeFilter+')')
            else:
                f.append(lhs)
                f.append(operator_mapping[operator])
                if isinstance(value, str):
                    f.append("'%s'"%str(value))
                else:
                    f.append("%s"%str(value))
            buff.append(''.join(f))
        opr = ' '+bool_operator+' '
        return opr.join(buff)

    def getTableName(self, metadataId):
        return metadataId.replace('_', '')