Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Security.Principal
Imports System.Net
Imports System.ServiceModel.Activation

Imports System.Data.SqlClient
Imports System.Data

' NOTE: You can use the "Rename" command on the context menu to change the class name "SVSService" in code, svc and config file together.

<HashHeaderInspection> _
<AspNetCompatibilityRequirements(RequirementsMode:=AspNetCompatibilityRequirementsMode.Allowed)> _
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerSession)> _
Public Class SVSService
    Implements ISVSService

    Private curr_user As LoginResponse

    Public Sub New()
        curr_user = New LoginResponse
        curr_user.authtoken = "00000000-0000-0000-0000-000000000000"
        curr_user.username = "Unknown"
        curr_user.isteller = False
        curr_user.idteller = ""
    End Sub

    Public Shared Sub Configure(config As ServiceConfiguration)
        DAL.Instance.InitConnectionsStr(
            ConfigurationManager.ConnectionStrings("SignDB_ConnectStr").ConnectionString,
            ConfigurationManager.ConnectionStrings("SignSecDB_ConnectStr").ConnectionString,
            30)
    End Sub

    Public Function Login(username As String, password As String) As LoginResponse Implements ISVSService.Login
        Dim user As New LoginResponse
        user.status = False
        Log.Instance.Info("User Login: " + username)
#If Not Debug Then
        If Not DAL.Instance.AuthenticatedLDAP(username, password) Then  ' authentifcate with LDAP
            Log.Instance.Info("Authenticated LDAP Failed: " + username)
            user.failedlogonattempt = 1
            Return user
        End If
#End If
        Log.Instance.Info("Authenticated LDAP: " + username)

        Try
            Dim dt As DataTable = DAL.Instance.GetUserInfo(username)
            If dt.Rows.Count > 0 Then
                Dim dbuser As String = dt.Rows(0).Item("LoginId").ToString.Trim

                If String.Equals(dbuser, username, StringComparison.CurrentCultureIgnoreCase) Then
                    user.username = username
                    user.lastlogontime = Date.Now.ToTimestamp
                    user.lastrequesttime = user.lastlogontime
                    user.userrole = dt.Rows(0).Item("UserRole").ToString
                    user.viewgroupid = dt.Rows(0).Item("ViewGroupID").ToString
                    user.accttypelist = dt.Rows(0).Item("AcctTypeList").ToString
                    user.functionid = Convert.ToInt32(dt.Rows(0).Item("FuncId"))
                    user.authtoken = DAL.Instance.InsertSession(user)
                    user.ip_address = ""    ' NLB presense, no need for IP/WorkstationId
                    user.workstation = ""   ' NLB presense, no need for IP/WorkstationId
                    'DAL.Instance.GetClientIpAddress(user.ip_address)
                    'DAL.Instance.GetWorkStationId(user.ip_address, user.workstation)
                    DAL.Instance.GetAppSetting("location", user.location)

                    Log.Instance.Info(String.Format("User Verified: {0}@{1} from [{2}]", username, user.ip_address, user.workstation))

                    If user.authtoken.Length > 0 Then
                        ' get acct viewgroup info
                        user.accviewgroup = New List(Of AcctViewGroupContract)()
                        Dim vgdt As DataTable = DAL.Instance.GetAccViewGroup()
                        For Each row In vgdt.Rows
                            Dim accvg As New AcctViewGroupContract
                            accvg.type = row("AcctType")
                            accvg.numlength = Integer.Parse(row("AcctNumLength"))
                            accvg.sortorder = Integer.Parse(row("SortOrder"))

                            If user.accttypelist.Contains(accvg.type) Then
                                user.accviewgroup.Add(accvg)
                                user.accttypesortedlist += accvg.type + ","
                            End If
                        Next
                        If user.accttypesortedlist.Length > 0 Then
                            user.accttypesortedlist = user.accttypesortedlist.Substring(0, user.accttypesortedlist.LastIndexOf(","))
                        End If

                        HttpContext.Current.Session("SessionStarted") = True
                        Log.Instance.Info(String.Format("User session created: {0} with token [{1}]", username, user.authtoken))
                        user.status = True
                    End If
                End If
            Else
                Log.Instance.Info("Unable to get userinfo for: " + username)
            End If
        Catch ex As Exception
            Log.Instance.Error(ex.Message)
            Log.Instance.Error("Exception Logging in: " + username)
        End Try
        Return user
    End Function

    Public Function GetLogin() As LoginResponse Implements ISVSService.GetLogin
        Dim user As New LoginResponse
        Try
            user.username = ServiceSecurityContext.Current.WindowsIdentity.Name
            DAL.Instance.GetAppSetting("location", user.location)
        Catch ex As Exception
            Log.Instance.Error("Error getting Windows UserId")
            Log.Instance.Error(ex.Message)
        End Try
        Return user
    End Function

    Public Function VerifyOverride(sessionid As String, type As String, number As String, username As String, password As String) As String Implements ISVSService.VerifyOverride
        Dim ret As String = ReturnCode.OK
        If Not CheckSessionId(sessionid, ret) Then
            Log.Instance.Info(ret + ": User: " + curr_user.username + " SID: " + sessionid)
            Return ret
        End If

        If Not username.Contains("\") Then
            username = String.Format("{0}\{1}", DAL.Instance.GetDomainId(curr_user.username), username)
        End If

        Log.Instance.Info("VerifyOverride: " + username)

#If Not Debug Then
        If Not DAL.Instance.AuthenticatedLDAP(username, password) Then  ' authentifcate with LDAP
            Log.Instance.Info("Authenticated LDAP Failed: " + username)
            Return ReturnCode.INVALID_USER
        End If
#End If

        If username.Equals(curr_user.username, StringComparison.CurrentCultureIgnoreCase) Then
            Log.Instance.Info("VerifyOverride cannot authenticate self: " + username)
            Return ReturnCode.NOACCESS
        End If

        Log.Instance.Info("VerifyOverride Authenticated LDAP: " + username)

        Dim dt As DataTable = DAL.Instance.GetUserInfo(username)
        If dt.Rows.Count > 0 Then
            Dim funcid As Integer = Convert.ToInt32(dt.Rows(0).Item("FuncId"))

            Log.Instance.Info("User Privelege" + funcid.ToString)

            If funcid = UserPrivilage.SUPERVISOR Then
                DAL.Instance.LogView(type, number, username,
                    curr_user.workstation, curr_user.ip_address, curr_user.idteller, "Supervisor override")
                Return ReturnCode.OK
            End If
        End If

        Log.Instance.Info("VerifyOverride user NoAccess: " + username)
        Return ReturnCode.NOACCESS
    End Function

    Public Function GetTellerView(encryptedpath As String) As LoginResponse Implements ISVSService.GetTellerView
        Dim keyFileLocation As String = ""
        Dim maxTimespanDiff As String = ""

        DAL.Instance.GetAppSetting("SVSViewer_key_location", keyFileLocation)
        DAL.Instance.GetAppSetting("SVSViewer_max_timespan", maxTimespanDiff)

        Dim cm As New SVSCrypto.CryptoModule
        Dim user As New LoginResponse
        Log.Instance.Info("GetTellerView from TM request received.")

        Try
            Dim keyFile As New System.IO.StreamReader(keyFileLocation)
            Dim key As String = cm.UnMassageKeyString(keyFile.ReadLine)
            Log.Instance.Info("GetTellerView: UnMassageKeyString OK")

            Dim directory As String = cm.DecryptTripleDES(key, encryptedpath)
            Dim param() As String = directory.Split({"/"c}, StringSplitOptions.RemoveEmptyEntries)
            If param.Length = 7 Then 'accType/accNbr/usrName/wksID/IP/timestamp/hash
                Dim remoteAddr As String = param(4)
                Dim timestamp As Long = CLng(param(5))
                Dim hashmsg As String = param(6)
                Log.Instance.Info("GetTellerView: DecryptTripleDES OK")

                Dim checkHashMsg() As Byte = cm.HashMessage(param(0) + "/" + param(1) + "/" + param(2) + "/" + param(3) + "/" + param(4) + "/" + param(5))
                If cm.CompareHash(cm.HexToByteArray(hashmsg), checkHashMsg) = True Then 'check hashing
                    'If remoteAddr.Equals(HttpContext.Current.Request.UserHostAddress) Then 'check IP
                    Dim clientTime As Date = DAL.Instance.FromUnixTime(timestamp / 1000)
                    If DateDiff(DateInterval.Minute, clientTime, DateTime.UtcNow) <= Integer.Parse(maxTimespanDiff) Then 'check timestamp
                        user = GetTellerView(param(0), param(1), param(2), param(3), param(4))
                    Else
                        user.status = False
                        user.retcode = ReturnCode.TIMESTAMP_ERROR
                        Log.Instance.Info("GetTellerView Error: " + user.retcode)
                        WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.RequestTimeout
                    End If
                    'Else
                    '    user.status = False
                    '    user.retcode = ReturnCode.REMOTE_IP_ERROR
                    '    Log.Instance.Info("GetTellerView Error: " + user.retcode)
                    '    WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized
                    'End If
                Else
                    user.status = False
                    user.retcode = ReturnCode.HASH_ERROR
                    Log.Instance.Info("GetTellerView Error: " + user.retcode)
                    WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.BadRequest
                End If
            Else
                user.status = False
                user.retcode = ReturnCode.DECRYPT_ERROR
                Log.Instance.Info("GetTellerView Error: " + user.retcode)
                WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.UnsupportedMediaType
            End If
        Catch ex As Exception
            user.status = False
            user.retcode = ex.Message
            Log.Instance.Error("GetTellerView Error: " + user.retcode)
            WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError
        End Try
        Log.Instance.Info("GetTellerView: " + user.retcode)
        Return user
    End Function

    Private Function GetTellerView(type As String, number As String, tellerid As String, workid As String, ipaddr As String) As LoginResponse
        Dim user As New LoginResponse
        user.status = False

        Try
            user.isteller = True
            user.username = tellerid
            user.lastlogontime = Date.Now.ToTimestamp
            user.lastrequesttime = user.lastlogontime
            user.ip_address = ipaddr
            user.workstation = workid
            user.functionid = UserPrivilage.NORMALUSER
            user.authtoken = DAL.Instance.InsertSession(user)
            DAL.Instance.GetAppSetting("location", user.location)

            Log.Instance.Info(String.Format("Teller User Verified: {0}@{1} from [{2}]", tellerid, user.ip_address, user.workstation))

            If user.authtoken.Length > 0 Then
                Dim host As String = "http://ccbauatsvs.asia2.ccb2.com/HKTellerWeb/"

                If DAL.Instance.GetAppSetting("teller_host", host) Then
                    user.tellerurl = String.Format("{0}#/view?" & _
                        "svs_web_viewer=SVS_SIGNATURE_WEB_VIEWER_FOR_TELLER_INTEGRATION_MODE" & _
                        "&origin=TM&location={1}&tellerid={2}&type={3}&number={4}&sessionid={5}",
                        host, user.location, user.username, type, number, user.authtoken)

                    HttpContext.Current.Session("SessionStarted") = True
                    user.status = True
                End If

                Log.Instance.Info("GetTellerView : " + user.tellerurl)
                WebOperationContext.Current.OutgoingResponse.Location = user.tellerurl
                WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Redirect
            Else
                Log.Instance.Info("GetTellerView : Unable to create Teller session.")
                WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.InternalServerError
            End If
        Catch ex As Exception
            Log.Instance.Error("Exception GetTellerView : " + tellerid)
            Log.Instance.Error(ex.Message)
        End Try
        Return user
    End Function

    Public Function GetSearchData(sessionid As String, type As String, param1 As String, param2 As String, param3 As String) _
        As SearchDataResponse Implements ISVSService.GetSearchData

        'Format user Data
        Dim p1 As String = If(param1 = "-1", "NULL", String.Format("""{0}""", param1))
        Dim p2 As String = If(param2 = "-1", "NULL", String.Format("""{0}""", param2))
        Dim p3 As String = If(param3 = "-1", "NULL", String.Format("""{0}""", param3))

        Dim dt As DataTable = DAL.Instance.GetSearchInfoByType(type, p1, p2, p3)
        Dim res As New SearchDataResponse With {.data = New List(Of SearchDataContract)}

        'No need to check session, as frontend will call IsSessionValid first
        'If Not CheckSessionId(sessionid, res.retcode) Then
        '    Log.Instance.Info(res.retcode + ": User: " + curr_user.username + " SID: " + sessionid)
        '    Return res
        'End If

        Try
            For i = 0 To dt.Rows.Count - 1
                If type = "srtimeprd" Then
                    Dim sr = (New SearchDataContract With { _
                                      .d1 = dt.Rows(i).Item(0).ToString, _
                                      .d2 = dt.Rows(i).Item(1).ToString, _
                                      .d3 = dt.Rows(i).Item(2).ToString, _
                                      .d4 = dt.Rows(i).Item(3).ToString, _
                                      .d5 = dt.Rows(i).Item(4).ToString, _
                                      .d6 = dt.Rows(i).Item(5).ToString, _
                                      .d7 = dt.Rows(i).Item(6).ToString, _
                                      .d8 = dt.Rows(i).Item(7).ToString, _
                                      .d9 = dt.Rows(i).Item(8).ToString, _
                                      .d10 = dt.Rows(i).Item(9).ToString, _
                                      .d11 = dt.Rows(i).Item(10).ToString _
                                    })
                    res.data.Add(sr)
                Else
                    Dim sr = (New SearchDataContract With { _
                                      .d1 = dt.Rows(i).Item(0).ToString, _
                                      .d2 = dt.Rows(i).Item(1).ToString, _
                                      .d3 = dt.Rows(i).Item(2).ToString, _
                                      .d4 = dt.Rows(i).Item(3).ToString _
                                    })
                    res.data.Add(sr)
                End If
            Next
        Catch ex As Exception
            Log.Instance.Error("Exception GetSearchData")
            Log.Instance.Error(ex.Message)
        End Try

        res.hasMore = False
        res.rows = dt.Rows.Count

        Dim max_tot As String = "1000"
        DAL.Instance.GetAppSetting("max_search_result", max_tot)

        If (res.rows >= max_tot) Then
            res.hasMore = True
        End If

        res.retcode = ReturnCode.OK
        Return res
    End Function

    Private Function FormatImageUrl(type As String, number As String, sigId As String) As String
        Dim sessionId As String = curr_user.authtoken

        Dim url As String
        url = String.Format("/sigimage/{0}/{1}/{2}/{3}?randint={4}", sessionId, type, number, sigId, DAL.Instance.GetRandom(0, 999999))
        Return url
    End Function

    Private Function FormatImageHistUrl(type As String, number As String, sigId As String, histdate As String) As String
        Dim sessionId As String = curr_user.authtoken

        Dim url As String
        url = String.Format("/sigimagehist/{0}/{1}/{2}/{3}/{4}?randint={5}", sessionId, type, number, sigId, histdate, DAL.Instance.GetRandom(0, 999999))
        Return url
    End Function

    'Private Function FormatImageUrl(dt As DataTable, idx As Integer) As String
    '    Dim sessionId As String = curr_user.authtoken
    '    Dim docType As String = dt.Rows(idx).Item("DocType").ToString
    '    Dim docId As String = dt.Rows(idx).Item("DocID").ToString
    '    Dim sigId As String = dt.Rows(idx).Item("SignID").ToString

    '    Dim url As String
    '    url = String.Format("/si/{0}/{1}/{2}/{3}", sessionId, docType, docId, sigId)
    '    'url = HttpUtility.UrlEncode(url)

    '    'As RAW context for <img src="..." />
    '    'Dim img = DAL.Instance.GetCustomerImage(docType, docId, sigId)
    '    'Dim url = String.Format("data:image/tiff;base64,{0}", Convert.ToBase64String(img))
    '    Return url
    'End Function

    Private Function GetCustomerList(type As String, number As String) As DataTable
        ' TODO: maybe put in store proc
        If type = "time" Then

            Dim sql As String = String.Format("SELECT * FROM CustProductLink WITH (NOLOCK) WHERE ProductID = '{0}'", number)
            Log.Instance.Info("GetCustomerList : " + sql)
            Dim dt As DataTable = DAL.Instance.ExecuteSQLReader(sql)

            Dim dtRet As New DataTable
            dtRet.Columns.Add("CIFNo")

            If dt.Rows.Count > 0 Then
                dtRet.Rows.Add(dt.Rows(0).Item("CIFNo1").ToString)
                dtRet.Rows.Add(dt.Rows(0).Item("CIFNo2").ToString)
                dtRet.Rows.Add(dt.Rows(0).Item("CIFNo3").ToString)
                dtRet.Rows.Add(dt.Rows(0).Item("CIFNo4").ToString)
                dtRet.Rows.Add(dt.Rows(0).Item("CIFNo5").ToString)
                dtRet.Rows.Add(dt.Rows(0).Item("CIFNo6").ToString)
                dtRet.Rows.Add(dt.Rows(0).Item("CIFNo7").ToString)
                dtRet.Rows.Add(dt.Rows(0).Item("CIFNo8").ToString)
                dtRet.Rows.Add(dt.Rows(0).Item("CIFNo9").ToString)
                dtRet.Rows.Add(dt.Rows(0).Item("CIFNo10").ToString)
            End If

            ' remove any empty / null rows
            For i As Integer = dtRet.Rows.Count - 1 To 0 Step -1
                Dim row As DataRow = dtRet.Rows(i)
                If row.Item(0) Is Nothing Then
                    dtRet.Rows.Remove(row)
                ElseIf row.Item(0).ToString = "" Then
                    dtRet.Rows.Remove(row)
                End If
            Next

            Return dtRet
        Else
            Dim sql As String = String.Format(
                "SELECT CIFNo FROM CustAcctLink WITH (NOLOCK) WHERE AcctType = '{0}' AND AcctNo = '{1}' GROUP BY CIFNo", type, number)
            Log.Instance.Info("GetCustomerList : " + sql)
            Return DAL.Instance.ExecuteSQLReader(sql)
        End If
    End Function

    Private Function GetCustomerData(number As String, ByRef dt As DataTable, ByRef res As AccountDataResponse) As Integer
        dt = DAL.Instance.GetDataInfoByType("cif", number, res)
        res.data.requestType = "cif"
        If dt.Rows.Count > 0 Then
            res.data.acctType = "C"c    ' Customer
            res.data.cifNo = number
            res.data.docId = dt.Rows(0).Item("DocID").ToString
            res.data.docType = dt.Rows(0).Item("DocType").ToString
            res.data.acctEngName = dt.Rows(0).Item("CustNameEng").ToString
            res.data.acctChiName = dt.Rows(0).Item("CustNameChi").ToString
            res.data.packageBanking = dt.Rows(0).Item("PackageBanking").ToString
            res.data.AIO = dt.Rows(0).Item("AllInOneInd").ToString
            res.data.custType = dt.Rows(0).Item("CustType").ToString
            res.data.suspend = dt.Rows(0).Item("Suspend").ToString
            res.data.underMaint = dt.Rows(0).Item("UnderMaint").ToString
        End If
        Return dt.Rows.Count
    End Function

    Private Function GetCustomerSignature(number As String, ByRef dt As DataTable, ByRef res As AccountDataResponse) As Integer
        dt = DAL.Instance.GetSignatureByType("cif", number, res)
        res.data.requestType = "cif"
        If dt.Rows.Count > 0 Then
            res.data.acctType = "C"c    ' Customer
            res.data.cifNo = number
            res.data.acctEngName = dt.Rows(0).Item("CustNameEng").ToString
            res.data.acctChiName = dt.Rows(0).Item("CustNameChi").ToString
            res.data.packageBanking = dt.Rows(0).Item("PackageBanking").ToString
            res.data.AIO = dt.Rows(0).Item("AllInOneInd").ToString
            res.data.custType = dt.Rows(0).Item("CustType").ToString
            res.data.suspend = dt.Rows(0).Item("Suspend").ToString
            res.data.underMaint = dt.Rows(0).Item("UnderMaint").ToString
        End If
        Return dt.Rows.Count
    End Function

    Private Function GetAccountDataInfo(type As String, number As String, ByRef dt As DataTable, ByRef res As AccountDataResponse) As Integer
        dt = DAL.Instance.GetDataInfoByType(type, number, res)
        res.data.requestType = type

        If dt.Rows.Count > 0 Then
            res.data.acctType = "A"c    ' account/time products
            res.data.productId = dt.Rows(0).Item("ProductID").ToString
            res.data.refAcctPrefix = dt.Rows(0).Item("RefAcctType").ToString
            res.data.refAcctNo = dt.Rows(0).Item("RefAcctNo").ToString
            res.data.signInst = dt.Rows(0).Item("SignInst").ToString
            res.data.suspend = dt.Rows(0).Item("Suspend").ToString
            res.data.underMaint = dt.Rows(0).Item("UnderMaint").ToString

            If type = "time" Then
                ' does not show acctinfo becasue time product may link to multiple acct and/or cif numbers
                ' no status as TP has no status and may link to multiple acct with different status
                'res.data.acctStatus = "Active"
                res.data.acctRemark = ""
            Else
                res.data.acctPrefix = dt.Rows(0).Item("AcctType").ToString
                res.data.acctNo = dt.Rows(0).Item("AcctNo").ToString
                res.data.acctStatus = dt.Rows(0).Item("AcctStatusCode").ToString
                res.data.acctViewFlag = dt.Rows(0).Item("ViewFlag").ToString
                res.data.acctViewOverridable = dt.Rows(0).Item("Overridable").ToString
                res.data.acctEngName = dt.Rows(0).Item("AcctNameEng").ToString
                res.data.acctChiName = dt.Rows(0).Item("AcctNameChi").ToString
                res.data.acctRemark = dt.Rows(0).Item("AcctRemark").ToString
            End If

            ' For acct previlages and views
            res.data.acctViewLevel = Math.Max(UserViewLevel.VIEW_ALLOW, res.data.acctViewLevel)

        End If

        Return dt.Rows.Count
    End Function

    Private Function GetAccountDataSign(type As String, number As String, ByRef dt As DataTable, ByRef res As AccountDataResponse) As Integer
        dt = DAL.Instance.GetSignatureByType(type, number, res)
        res.data.requestType = type

        If dt.Rows.Count > 0 Then
            res.data.acctType = "A"c    ' account/time products
            'res.data.acctPrefix = dt.Rows(0).Item("AcctType").ToString
            'res.data.acctNo = dt.Rows(0).Item("AcctNo").ToString
            'res.data.refAcctPrefix = dt.Rows(0).Item("RefAcctType").ToString
            'res.data.refAcctNo = dt.Rows(0).Item("RefAcctNo").ToString
            res.data.signInst = dt.Rows(0).Item("SignInst").ToString
            res.data.suspend = dt.Rows(0).Item("Suspend").ToString
            res.data.underMaint = dt.Rows(0).Item("UnderMaint").ToString
            res.data.productId = dt.Rows(0).Item("ProductID").ToString

            If type = "time" Then
                ' does not show acctinfo becasue time product may link to multiple acct and/or cif numbers
                ' no status as TP has no status and may link to multiple acct with different status
                'res.data.acctStatus = "Active"
            Else
                res.data.acctStatus = dt.Rows(0).Item("AcctStatusCode").ToString
                res.data.acctViewFlag = dt.Rows(0).Item("ViewFlag").ToString
                res.data.acctViewOverridable = dt.Rows(0).Item("Overridable").ToString
                res.data.acctEngName = dt.Rows(0).Item("AcctNameEng").ToString
                res.data.acctChiName = dt.Rows(0).Item("AcctNameChi").ToString
                res.data.acctRemark = dt.Rows(0).Item("AcctRemark").ToString
            End If

            ' For acct previlages and views
            res.data.acctViewLevel = Math.Max(UserViewLevel.VIEW_ALLOW, res.data.acctViewLevel)

        End If

            Return dt.Rows.Count
    End Function

    Private Function AddSignatures(sType As String, sNumber As String, dt As DataTable, ByRef sig As List(Of SignatureDataContract)) As Integer
        Log.Instance.Info("Adding signatures for Account: " + sNumber)
        Try
            For i = 0 To dt.Rows.Count - 1
                Dim typeT = ""

                If dt.Rows(i).Item("AuthorizedSigner").ToString = "Y" Then
                    typeT = "A"
                ElseIf dt.Rows(i).Item("WithChop").ToString = "Y" Then
                    typeT = "C"
                Else
                    typeT = ""
                End If

                Dim newSign = (New SignatureDataContract With { _
                        .docId = dt.Rows(i).Item("DocID").ToString, _
                        .docType = dt.Rows(i).Item("DocType").ToString, _
                        .signType = dt.Rows(i).Item("signType").ToString, _
                        .id = dt.Rows(i).Item("SignID").ToString, _
                        .signName = dt.Rows(i).Item("SignNameEng").ToString, _
                        .chineseName = dt.Rows(i).Item("SignNameChi").ToString, _
                        .title = dt.Rows(i).Item("Title").ToString, _
                        .remark = dt.Rows(i).Item("Remark").ToString, _
                        .groupId = dt.Rows(i).Item("GroupID").ToString, _
                        .supersedeCif = dt.Rows(i).Item("SupersedeCIFNo").ToString().Trim(), _
                        .stype = sType, _
                        .snumber = sNumber, _
                        .type = typeT _
                    })

                newSign.signLevel = sType
                If sType <> "cif" And sType <> "time" Then
                    newSign.signLevel = "acct"
                End If

                If newSign.supersedeCif.Length > 0 Then
                    newSign.type = "S"
                    newSign.typeString = String.Format("Supercede CIF {0}", newSign.supersedeCif)
                End If

                If newSign.type = "A" Then
                    newSign.typeString = "Authorized Signer"
                ElseIf newSign.type = "C" Then
                    newSign.typeString = "Company Chop"
                End If

                Dim dtEff As DateTime = dt.Rows(i).Item("EffDate")
                newSign.effDate = DAL.Instance.ToUnixTime(dtEff)
                newSign.creationDate = newSign.effDate ' signatures have no creation date, using eff

                Dim dtExp As DateTime = dt.Rows(i).Item("ExpDate")
                newSign.expDate = DAL.Instance.ToUnixTime(dtExp)

                If newSign.signType = "H" Then
                    Dim dtHist As DateTime = dt.Rows(i).Item("HistCreateDateTime")
                    newSign.historyCreationDate = dtHist.Ticks.ToString
                    newSign.imageUrl = FormatImageHistUrl(sType, sNumber, newSign.id, newSign.historyCreationDate)
                Else
                    'newSign.imageUrl = FormatImageUrl(dt, i)                           ' uses DocType/DocId for searching
                    newSign.imageUrl = FormatImageUrl(sType, sNumber, newSign.id)       ' uses CIF/ACCT for searching
                End If

                sig.Add(newSign)
            Next
        Catch ex As Exception
            Log.Instance.Error("Exception AddSignatures")
            Log.Instance.Error(ex.Message)
        End Try
        Return sig.Count
    End Function

    Private Function IsSupsendedOrUnderMaint(type As String, number As String, ByRef res As AccountDataResponse) As Boolean
        If res.data.suspend = "Y" Or res.data.underMaint = "Y" Then
            res.retcode = ReturnCode.SUSPEND_UNDERMAINT
            ' if suspend, check who/where suspend it
            Try
                If res.data.suspend = "Y" Then
                    Dim dt As DataTable = DAL.Instance.GetSuspendInfo(type, number)
                    If dt.Rows.Count > 0 Then
                        res.data.suspend_uid = dt.Rows(0).Item("UserId").ToString()
                        res.data.suspend_bcode = dt.Rows(0).Item("BranchCode").ToString()
                    End If
                End If
            Catch ex As Exception
                Log.Instance.Error("IsSupsendedOrUnderMaint: " + number)
                Log.Instance.Error(ex.Message)
            End Try
            Return True
        End If
        Return False
    End Function

    Public Function IsSessionValid(sessionid As String) As String Implements ISVSService.IsSessionValid
        Dim ret As String = ReturnCode.INVALID_USER
        CheckSessionId(sessionid, ret)
        Log.Instance.Info(ret + ": User: " + curr_user.username + " SID: " + sessionid)
        Return ret
    End Function

    Public Function CheckSessionId(sessionId As String, ByRef ret As String) As Boolean
        If DAL.Instance.IsStressTestMode Then
            curr_user.authtoken = sessionId

            If String.Equals(sessionId, "TELLER_DEBUG_SID") Then
                curr_user.isteller = True
            End If

            ret = ReturnCode.OK
            Return True
        End If

        Dim dt As DataTable = DAL.Instance.GetSessionInfo(sessionId, False)

        If dt.Rows.Count = 0 Then
            dt = DAL.Instance.GetSessionInfo(sessionId, True)
        End If

        If dt.Rows.Count > 0 Then
            curr_user.authtoken = sessionId
            curr_user.username = dt.Rows(0).Item("userId").ToString
            curr_user.workstation = dt.Rows(0).Item("workstationId").ToString
            curr_user.ip_address = dt.Rows(0).Item("ip").ToString

            Dim dtlastreq As DateTime = dt.Rows(0).Item("lastRequestTime")
            curr_user.lastrequesttime = DAL.Instance.ToUnixTime(dtlastreq)
            DAL.Instance.GetAppSetting("location", curr_user.location)

            If IsNumeric(curr_user.username) Then
                curr_user.isteller = True
                curr_user.idteller = curr_user.username
            Else
                curr_user.userid = DAL.Instance.GetUserId(curr_user.username)
                curr_user.domainid = DAL.Instance.GetDomainId(curr_user.username)
            End If

            ' TODO.... need to recheck IIS session state
            Dim state As Boolean = Convert.ToBoolean(HttpContext.Current.Session("SessionStarted"))
            'If (DAL.Instance.IsSessionTimeout(curr_user.lastrequesttime) Or Not state) Then
            If (DAL.Instance.IsSessionTimeout(curr_user.lastrequesttime)) Then
                ret = ReturnCode.SESSION_EXPIRED
                Return False
            End If

            DAL.Instance.UpdateSession(sessionId)
            Log.Instance.Info("CheckSessionId OK: " + String.Format("{0}@{1} - {2}", curr_user.username, curr_user.workstation, sessionId))
            ret = ReturnCode.OK
            Return True
        End If
        ret = ReturnCode.INVALID_SESSION
        Return False
    End Function

    Private Function CheckAcctStatusAndUserAccess(sessionId As String, ByRef res As AccountDataResponse) As Boolean
        If DAL.Instance.IsStressTestMode Then
            res.identity.functionid = Math.Max(UserPrivilage.NORMALUSER, res.identity.functionid)
            res.data.acctViewLevel = Math.Max(UserViewLevel.VIEW_ALLOW, res.data.acctViewLevel)
            Return True
        End If

        ' No view allowed for this user, return immediately
        If res.data.acctViewLevel = UserViewLevel.VIEW_DENIED Then
            Return False
        End If

        Dim dt As DataTable = DAL.Instance.GetSessionInfo(sessionId, curr_user.isteller)
        If dt.Rows.Count > 0 Then
            res.identity.sessionId = sessionId
            res.identity.requestUser = dt.Rows(0).Item("userId").ToString

            If curr_user.isteller Then
                res.identity.functionid = UserPrivilage.NORMALUSER
            Else
                res.identity.functionid = Convert.ToInt32(dt.Rows(0).Item("FuncId"))
            End If

            Dim acc_status As String = res.data.acctStatus
            Dim acc_viewflag As String = res.data.acctViewFlag
            Dim acc_viewOverridable As String = res.data.acctViewOverridable

            If acc_viewflag = "N" Then
                If res.identity.functionid = UserPrivilage.SUPERVISOR Then
                    If acc_viewOverridable = "Y" Then
                        res.data.acctViewLevel = UserViewLevel.VIEW_REQUIRE_SUPERVISOR_CREDENTIAL
                        Return True
                    Else
                        res.data.acctViewLevel = UserViewLevel.VIEW_DENIED
                        Return False
                    End If
                Else ' Normal User
                    If curr_user.isteller Then
                        res.data.acctViewLevel = UserViewLevel.VIEW_DENIED
                        Return False
                    End If

                    If acc_viewOverridable = "Y" Then
                        res.data.acctViewLevel = UserViewLevel.VIEW_REQUIRE_SUPERVISOR_CREDENTIAL
                        Return True
                    Else
                        res.data.acctViewLevel = UserViewLevel.VIEW_DENIED
                        Return False
                    End If
                End If
            End If

            ' recheck UserViewLevel, do not override previous settings
            res.data.acctViewLevel = Math.Max(UserViewLevel.VIEW_ALLOW, res.data.acctViewLevel)
            Return True
        End If

        Return False
    End Function

    Private Function AppendToCIFList(number As String, name As String, doctype As String, docid As String, pkg As String, aio As String, custtype As String, ByRef ciflist As String) As Boolean
        Try
            ' Append CIFNo, CustName, Id, Pkg, AIO, CustType
            ciflist += String.Format("{0}  {1}", number, name) + vbCrLf
            ciflist += String.Format("ID DOC      {0} {1}", doctype, docid) + vbCrLf
            ciflist += String.Format("PB Type     {0}", pkg) + vbCrLf
            ciflist += String.Format("AIO: {0}      CustType: {1}", aio, custtype) + vbCrLf

        Catch ex As Exception
            Log.Instance.Error("Error appending to CIFList")
            Log.Instance.Error(ex.Message)
            Return False
        End Try
        Return True
    End Function

    Private Function IsSignatureExpired(expdate As String) As Boolean
        If IsNothing(expdate) Or expdate = "0" Or expdate = "" Or expdate.Length = 0 Then
            Return False
        End If

        Dim dt As DateTime = DAL.Instance.FromUnixTime(expdate)
        If (dt <= DateTime.Now) Then
            Return True
        End If
        Return False
    End Function

    Private Function HasSuperCedeCIF(number As String, sigs As List(Of SignatureDataContract)) As Boolean
        For Each sig As SignatureDataContract In sigs
            If number = sig.supersedeCif Then
                If Not IsSignatureExpired(sig.expDate) Then
                    Return True
                End If
            End If
        Next
        Return False
    End Function

    Private Function GetAccountDataSignatures(type As String, number As String, ByRef dt As DataTable, ByRef res As AccountDataResponse) As Boolean
        If GetAccountDataInfo(type, number, dt, res) = 0 Then   ' Get Acct Data
            Log.Instance.Info("Account does not exist: " + number)
            Return False
        End If

        If IsSupsendedOrUnderMaint(type, number, res) Then    ' Check for Suspend or Under Maintenance flag
            Log.Instance.Info("Account Suspended or Undermaint: " + number)
            Return False
        End If

        If Not CheckAcctStatusAndUserAccess(curr_user.authtoken, res) Then      ' Check Status and User Privileges
            Log.Instance.Info("User has no access: " + curr_user.username)
            res.data.acctViewLevel = UserViewLevel.VIEW_DENIED
            res.retcode = ReturnCode.NOACCESS
            Return False
        End If

        ' Refer to reference account if exist
        If (res.data.refAcctPrefix.Length > 0 And res.data.refAcctPrefix.Length > 0) Then
            Dim refRes = New AccountDataResponse With {.data = New AccountDataContract}
            If GetAccountDataInfo(res.data.refAcctPrefix, res.data.refAcctNo, dt, refRes) > 0 Then
                type = res.data.refAcctPrefix
                number = res.data.refAcctNo
                res.data.suspend = refRes.data.suspend
                res.data.underMaint = refRes.data.underMaint
                res.data.acctViewLevel = Math.Max(res.data.acctViewLevel, refRes.data.acctViewLevel)
            End If
        End If

        If IsSupsendedOrUnderMaint(type, number, res) Then            ' Check for Suspend or Under Maintenance flag
            Log.Instance.Info("Account Suspended or Undermaint: " + number)
            Return False
        End If

        If Not CheckAcctStatusAndUserAccess(curr_user.authtoken, res) Then      ' Check Status and User Privileges
            Log.Instance.Info("User has no access: " + curr_user.username)
            res.data.acctViewLevel = UserViewLevel.VIEW_DENIED
            res.retcode = ReturnCode.NOACCESS
            Return False
        End If

        ' Special case for Time Deposit (TD) Acct with ProductId existing
        If type = "TD" And res.data.productId.Length > 0 Then
            Return GetAccountDataSignatures("time", res.data.productId, dt, res)
        End If

        If GetAccountDataSign(type, number, dt, res) > 0 Then      ' Get Acct Sign
            AddSignatures(type, number, dt, res.data.signatures)    ' Add Account related signatures
        End If

        Dim dtCust = GetCustomerList(type, number)      ' Get Customer Num from Acct Numbers

        For Each row As DataRow In dtCust.Rows          ' Add Customer related signatures
            Dim cifNo As String = row.Item("CIFNo").ToString().Trim()
            Dim dtCustInfo = New DataTable
            Dim resCustInfo = New AccountDataResponse With {.data = New AccountDataContract}

            ' Check CIF Suspend or UnderMaint flag
            If GetCustomerData(cifNo, dtCustInfo, resCustInfo) > 0 Then
                If IsSupsendedOrUnderMaint("cif", cifNo, resCustInfo) Then
                    Log.Instance.Info("CIF Suspended or Undermaint: " + cifNo)
                    res.data.suspend = resCustInfo.data.suspend
                    res.data.underMaint = resCustInfo.data.underMaint
                    res.data.smFromChildCif = 1
                    Return False
                End If
            Else
                Continue For    ' No custinfo found, so no need to find signatures
            End If

            ' Add CIFNo + CIFName + docId + docType into CIF List
            AppendToCIFList(cifNo, resCustInfo.data.acctEngName, resCustInfo.data.docType,
                            resCustInfo.data.docId, resCustInfo.data.packageBanking,
                            resCustInfo.data.AIO, resCustInfo.data.custType, res.data.cifList)

            '' Check if supercedeCIF, if so, do not add CIF signatures
            'If HasSuperCedeCIF(cifNo, res.data.signatures) Then
            '    Log.Instance.Info("Has supercede CIF: " + cifNo)
            '    Continue For
            'End If

            Dim dtCustSign = New DataTable
            Dim resCustSign = New AccountDataResponse With {.data = New AccountDataContract}

            If GetCustomerSignature(cifNo, dtCustSign, resCustSign) > 0 Then
                AddSignatures("cif", cifNo, dtCustSign, res.data.signatures)  ' No issue! Add CIF related sigs
            End If
        Next row
        Return True
    End Function

    Public Function GetAccountData(sessionid As String, type As String, number As String) As AccountDataResponse Implements ISVSService.GetAccountData

        Dim dt As DataTable = New DataTable
        Dim res As New AccountDataResponse With {.data = New AccountDataContract}
        res.identity = New IdentityDataContract With {.requestUser = curr_user.username, .requestTimestamp = Date.Now.ToTimestamp}
        res.data.signatures = New List(Of SignatureDataContract)
        res.status = True

        If Not CheckSessionId(sessionid, res.retcode) Then
            Log.Instance.Info(res.retcode + ": User: " + curr_user.username + " SID: " + sessionid)
            Return res
        End If

        If type = "cif" Then
            If GetCustomerData(number, dt, res) > 0 Then
                If IsSupsendedOrUnderMaint(type, number, res) Then
                    Log.Instance.Info("CIF Suspended or Undermaint: " + number)
                    Return res
                End If

                If GetCustomerSignature(number, dt, res) > 0 Then
                    AddSignatures(type, number, dt, res.data.signatures)
                End If
            End If
        Else
            GetAccountDataSignatures(type, number, dt, res)
        End If

        ' sort it where chops appear at top
        'res.data.signatures = res.data.signatures.OrderBy(Function(a) a, New SignatureComparer()).ToList

        ' Add logging when user has viewed some signatures
        If res.data.signatures.Count > 0 And res.data.acctViewLevel = UserViewLevel.VIEW_ALLOW Then
            DAL.Instance.LogView(type, number, curr_user.username, curr_user.workstation, curr_user.ip_address, curr_user.idteller, "")
        End If
        Return res

    End Function

    'Public Function GetSignatureHistoryData(sessionid As String, type As String, number As String, signID As String) As SignatureHistoryDataResponse Implements ISVSService.GetSignatureHistoryData
    '    Dim res As New SignatureHistoryDataResponse
    '    res.identity = New IdentityDataContract With {.requestUser = curr_user.username, .requestTimestamp = Date.Now.ToTimestamp}
    '    res.data = New SignatureHistoryDataContract
    '    res.data.signatures = New List(Of SignatureDataContract)
    '    Dim dt As DataTable = DAL.Instance.GetSignatureHistory(type, number, signID)
    '    res.status = True

    '    If Not CheckSessionId(sessionid, res.retcode) Then
    '        Log.Instance.Info(res.retcode + ": User: " + curr_user.username + " SID: " + sessionid)
    '        Return res
    '    End If

    '    Try
    '        If dt.Rows.Count > 0 Then
    '            For i = 0 To dt.Rows.Count - 1
    '                res.data.signatures.Add(New SignatureDataContract With { _
    '                        .docId = dt.Rows(i).Item("DocID").ToString, _
    '                        .docType = dt.Rows(i).Item("DocType").ToString, _
    '                        .id = dt.Rows(i).Item("SignID").ToString, _
    '                        .imageUrl = FormatImageUrl(dt, i), _
    '                        .creationDate = Integer.Parse(dt.Rows(i).Item("HistCreateDateTime").ToString), _
    '                        .effDate = Integer.Parse(dt.Rows(i).Item("EffDate").ToString), _
    '                        .expDate = Integer.Parse(dt.Rows(i).Item("ExpDate").ToString), _
    '                        .title = dt.Rows(i).Item("Title").ToString, _
    '                        .signName = dt.Rows(i).Item("SignNameEng").ToString, _
    '                        .chineseName = dt.Rows(i).Item("SignNameChi").ToString, _
    '                        .remark = dt.Rows(i).Item("Remark").ToString, _
    '                        .supersedeCif = dt.Rows(i).Item("SupersedeCIFNo").ToString().Trim(), _
    '                        .groupId = dt.Rows(i).Item("GroupID").ToString, _
    '                        .type = "" _
    '                    })
    '            Next
    '        End If

    '        res.data.signatures = res.data.signatures.OrderByDescending(Function(x) x.creationDate).ToList
    '    Catch ex As Exception
    '        Log.Instance.Info("Exception GetSignatureHistoryData")
    '    End Try
    '    Return res
    'End Function

    Public Function GetImageHistoryData(sessionid As String, type As String, number As String, signID As String) As SignatureHistoryDataResponse Implements ISVSService.GetImageHistoryData
        Dim res As New SignatureHistoryDataResponse
        res.identity = New IdentityDataContract With {.requestUser = curr_user.username, .requestTimestamp = Date.Now.ToTimestamp}
        res.data = New SignatureHistoryDataContract
        res.data.signatures = New List(Of SignatureDataContract)
        Dim dt As DataTable = DAL.Instance.GetImageHistory(type, number, signID)
        res.status = True

        If Not CheckSessionId(sessionid, res.retcode) Then
            Log.Instance.Info(res.retcode + ": User: " + curr_user.username + " SID: " + sessionid)
            Return res
        End If

        Try
            If dt.Rows.Count > 0 Then
                For i = 0 To dt.Rows.Count - 1

                    Dim newHist = (New SignatureDataContract With { _
                            .docId = dt.Rows(i).Item("DocID").ToString, _
                            .docType = dt.Rows(i).Item("DocType").ToString, _
                            .id = dt.Rows(i).Item("SignID").ToString, _
                            .title = dt.Rows(i).Item("Title").ToString, _
                            .signName = dt.Rows(i).Item("SignNameEng").ToString, _
                            .chineseName = dt.Rows(i).Item("SignNameChi").ToString, _
                            .remark = dt.Rows(i).Item("Remark").ToString, _
                            .supersedeCif = dt.Rows(i).Item("SupersedeCIFNo").ToString().Trim(), _
                            .groupId = dt.Rows(i).Item("GroupID").ToString, _
                            .stype = type, _
                            .snumber = number, _
                            .type = "" _
                        })

                    Dim dtCreate As DateTime = dt.Rows(i).Item("HistCreateDateTime")
                    newHist.creationDate = DAL.Instance.ToUnixTime(dtCreate)

                    Dim dtEff As DateTime = dt.Rows(i).Item("EffDate")
                    newHist.effDate = DAL.Instance.ToUnixTime(dtEff)

                    Dim dtExp As DateTime = dt.Rows(i).Item("ExpDate")
                    newHist.expDate = DAL.Instance.ToUnixTime(dtExp)

                    Dim dtHist As DateTime = dt.Rows(i).Item("HistoryCreateTime")
                    newHist.historyCreationDate = dtHist.Ticks.ToString
                    newHist.imageUrl = FormatImageHistUrl(type, number, newHist.id, newHist.historyCreationDate)
                    res.data.signatures.Add(newHist)
                Next
            End If

            res.data.signatures = res.data.signatures.OrderByDescending(Function(x) x.creationDate).ToList
        Catch ex As Exception
            Log.Instance.Info("Exception GetImageHistoryData")
        End Try
        Return res
    End Function

    Public Function GetImageData(ByVal sessionId As String, ByVal docType As String, ByVal docId As String, ByVal sigId As String) As IO.Stream Implements ISVSService.GetImageData
        Dim ctx As OutgoingWebResponseContext = WebOperationContext.Current.OutgoingResponse
        ctx.Headers.Add(System.Net.HttpResponseHeader.CacheControl, "public")

        '' check user session
        'Dim ret As String = ReturnCode.OK
        'If Not CheckSessionId(sessionId, ret) Then
        '    Log.Instance.Info(res + ": User: " + curr_user.username + " SID: " + sessionId)
        '    ctx.StatusCode = System.Net.HttpStatusCode.RequestTimeout
        '    Return Nothing
        'End If

        Dim img = DAL.Instance.GetCustomerImage(docType, docId, sigId)

        If DAL.Instance.DecryptImage(img) Then
            ctx.ContentType = "image/tiff"
            ctx.LastModified = Date.Today
            ctx.StatusCode = System.Net.HttpStatusCode.OK
            Return New System.IO.MemoryStream(img)
        Else
            Log.Instance.Info("DecryptImage Failed.")
        End If

        ctx.StatusCode = System.Net.HttpStatusCode.InternalServerError
        Return Nothing
    End Function

    Public Function GetImageSig(ByVal sessionId As String, ByVal type As String, ByVal number As String, ByVal sigId As String) As IO.Stream Implements ISVSService.GetImageSig
        Dim ctx As OutgoingWebResponseContext = WebOperationContext.Current.OutgoingResponse
        ctx.Headers.Add(System.Net.HttpResponseHeader.CacheControl, "public")

        '' check user session
        'Dim ret As String = ReturnCode.OK
        'If Not CheckSessionId(sessionId, ret) Then
        '    Log.Instance.Info(ret + ": User: " + curr_user.username + " SID: " + sessionId)
        '    ctx.StatusCode = System.Net.HttpStatusCode.RequestTimeout
        '    Return Nothing
        'End If

        Dim img = DAL.Instance.GetSignatureImage(type, number, sigId)

        If DAL.Instance.DecryptImage(img) Then
            ctx.ContentType = "image/tiff"
            ctx.LastModified = Date.Today
            ctx.StatusCode = System.Net.HttpStatusCode.OK
            Return New System.IO.MemoryStream(img)
        Else
            Log.Instance.Info("DecryptImage Failed.")
        End If

        ctx.StatusCode = System.Net.HttpStatusCode.InternalServerError
        Return Nothing
    End Function

    Public Function GetImageSigHist(ByVal sessionId As String, ByVal type As String, ByVal number As String, ByVal sigId As String, ByVal histdate As String) As IO.Stream Implements ISVSService.GetImageSigHist
        Dim ctx As OutgoingWebResponseContext = WebOperationContext.Current.OutgoingResponse
        ctx.Headers.Add(System.Net.HttpResponseHeader.CacheControl, "public")

        Dim dtHist As DateTime = New DateTime(histdate)
        Dim img = DAL.Instance.GetSignatureImageHist(type, number, sigId, dtHist.ToString("yyyy-MM-dd HH:mm:ss.fff"))

        If DAL.Instance.DecryptImage(img) Then
            ctx.ContentType = "image/tiff"
            ctx.LastModified = Date.Today
            ctx.StatusCode = System.Net.HttpStatusCode.OK
            Return New System.IO.MemoryStream(img)
        Else
            Log.Instance.Info("DecryptImageHist Failed.")
        End If

        ctx.StatusCode = System.Net.HttpStatusCode.InternalServerError
        Return Nothing
    End Function

End Class