using System;
using System.Configuration;
using System.Data;
using System.Collections.Specialized;
using System.Collections;
using System.Data.SqlClient;
using System.IO;
using System.Web.Services;
using System.Threading;
using System.Timers;
using System.Web;
using edu.jhu.Cas.Servers;
using edu.jhu.Cas.Users;
using edu.jhu.Cas.Util;
using net.ivoa.data;
using net.ivoa.VOTable;
using System.Security.Principal;
using System.Text.RegularExpressions;
using System.Text;

using edu.jhu.Cas.Jobs;

namespace edu.jhu.Cas.Services {
    public abstract class ConditionFactory {
        enum CType {
            INT,
            FLOAT,
            STRING,
            DATETIME
        }
        abstract class CMap {
            public abstract string[] KEYS { get;}
            public abstract string[] COLUMNS { get;}
            public abstract CType[] TYPES { get;}
            public int Key2ID(string key) {
                string lkey = key.ToLower().Trim();
                int col_index = -1;
                for (int i = 0; i < KEYS.Length; i++)
                    if (lkey.CompareTo(KEYS[i]) == 0)
                        col_index = i;
                if (col_index == -1)
                    throw new CasJobsException("'" + key + "' is not a valid key.");
                return col_index;
            }
            public string ID2Col(int id) {
                if (id < 0 || id >= KEYS.Length)
                    throw new CasJobsException(id + " is not a valid ID");
                return COLUMNS[id];
            }
            public CType ID2CType(int id) {
                if (id < 0 || id >= KEYS.Length)
                    throw new CasJobsException(id + " is not a valid ID");
                return TYPES[id];
            }
        }
        public string UserID, Name, Email, Privileges;
        public bool GVisible;
        public long WebServicesID;
        public int EmailLevel;
        public DateTime TimeCreated;
        class ServerMap : CMap {
            static readonly string[] keys = new string[]{
                "name",
                "timeout",
                "context",
                "dbuser",
                "dbmachine",
                "dbcatalog",
                "execcap",
                "gpriv",
                "dbpw"
            };
            static readonly string[] columns = new string[]{
                "[name]",
                "[queue]",
                "[target]",
                "[usr]",
                "[host]",
                "[cat]",
                "[execlimit]",
                "[privs]",
                "[password]"
            };
            static readonly CType[] types = new CType[]{
                CType.STRING,
                CType.INT,
                CType.STRING,
                CType.STRING,
                CType.STRING,
                CType.STRING,
                CType.INT,
                CType.STRING,
                CType.STRING
            };
            public override string[] KEYS { get { return keys; } }
            public override string[] COLUMNS { get { return columns; } }
            public override CType[] TYPES { get { return types; } }
        }
        class UserMap : CMap {
            static readonly string[] keys = new string[]{
                "userid",
                "name",
                "email",
                "privileges",
                "gvisible",
                "wsid",
                "emaillevel",
                "timecreated"
            };
            static readonly string[] columns = new string[]{
                "[userid]",
                "[name]",
                "[email]",
                "[privileges]",
                "[gvisible]",
                "[webservicesid]",
                "[emaillevel]",
                "[timecreated]"
            };
            static readonly CType[] types = new CType[]{
                CType.STRING,
                CType.STRING,
                CType.STRING,
                CType.STRING,
                CType.INT,
                CType.INT,
                CType.INT,
                CType.DATETIME
            };
            public override string[] KEYS { get { return keys; } }
            public override string[] COLUMNS { get { return columns; } }
            public override CType[] TYPES { get { return types; } }
        }
        class JobMap : CMap {
            static readonly string[] keys = new string[]{
                "jobid",
                "timesubmit",
                "timestart",
                "timeend",
                "status",
                "error",
                "context",
                "queue",
                "type",
                "taskname",
                "wsid"
            };
            static readonly string[] columns = new string[]{
                "[JobID]",
                "[TimeSubmit]",
                "[TimeStart]",
                "[TimeEnd]",
                "[Status]",
                "[Message]",
                "[Target]",
                "[Queue]",
                "[OutputType]",
                "[TaskName]",
                "[WebServicesID]"
            };
            static readonly CType[] types = new CType[]{
                CType.INT,
                CType.DATETIME,
                CType.DATETIME,
                CType.DATETIME,
                CType.INT,
                CType.STRING,
                CType.STRING,
                CType.INT,
                CType.STRING,
                CType.STRING,
                CType.INT
            };
            public override string[] KEYS { get { return keys; } }
            public override string[] COLUMNS { get { return columns; } }
            public override CType[] TYPES { get { return types; } }
        }
        public const char
            SEP_LVL1 = ';',
            SEP_LVL2 = ':',
            SEP_LVL3 = '|',
            COND_DESC = ','
        ;
        static readonly string COND_DESC_S = COND_DESC.ToString();
        static readonly string[] INVALID_STRINGS = new string[]{
            ";",
            "'",
            "--",
            "/*",
            "*/",
            "xp_"
        };
        public static string UserConditions2SQL(string conditions) { return conditions2SQL(conditions, new UserMap()); }
        public static string JobConditions2SQL(string conditions) { return conditions2SQL(conditions, new JobMap()); }
        public static string ServerConditions2SQL(string conditions) { return conditions2SQL(conditions, new ServerMap()); }
        static string conditions2SQL(string conditions, CMap cmap) {
            if (conditions == "") return "";
            string[] tokens1 = conditions.Split(new char[] { SEP_LVL1 }, StringSplitOptions.RemoveEmptyEntries);
            string key, l2;
            StringBuilder sb = new StringBuilder("\n");
            for (int i1 = 0; i1 < tokens1.Length; i1++) {
                sb.Append("(");
                l12keyl2(tokens1[i1], out key, out l2);
                int id = cmap.Key2ID(key);
                string[] tokens2 = l2.Split(new char[] { SEP_LVL3 }, StringSplitOptions.RemoveEmptyEntries);
                for (int i2 = 0; i2 < tokens2.Length; i2++) {
                    sb.Append("(" + l32SQL(id, tokens2[i2], cmap) + ")");
                    if (i2 < tokens2.Length - 1) sb.Append(" OR ");
                }
                sb.Append(")");
                if (i1 < tokens1.Length - 1) sb.AppendLine(" AND ");
            }
            return sb.ToString();
        }
        static string l32SQL(int id, string l3, CMap cmap) {
            l3 = l3.Trim();
            string noCond = l3.Replace(COND_DESC_S, "");
            string column = cmap.ID2Col(id);
            if (noCond.Length < l3.Length - 1)//there can be at most one COND_DESC per l3
                throw new CasJobsException("Substring '" + l3 + "' is improperly formatted.");
            if (l3.IndexOf(COND_DESC) == -1) {// 123
                return column + " = " + val2SQL(id, cmap, l3);
            } else if (l3.StartsWith(COND_DESC_S)) {// ,123
                return column + " <= " + val2SQL(id, cmap, noCond);
            } else if (l3.EndsWith(COND_DESC_S)) {// 123,
                return column + " >= " + val2SQL(id, cmap, noCond);
            } else {// 123,1234
                string[] tokens = l3.Split(COND_DESC);
                return column + " between " + val2SQL(id, cmap, tokens[0]) + " and " + val2SQL(id, cmap, tokens[1]);
            }
        }
        //validates input values
        static string val2SQL(int id, CMap cmap, string s) {
            CType ct = cmap.ID2CType(id);
            switch (ct) {
                case CType.FLOAT:
                    try {
                        return double.Parse(s).ToString();
                    } catch {
                        throw new CasJobsException("Floating point number '" + s + "' is improperly formatted");
                    }
                case CType.INT:
                    try {
                        return long.Parse(s).ToString();
                    } catch {
                        throw new CasJobsException("Integer type number '" + s + "' is improperly formatted");
                    }
                case CType.STRING:
                    ValidateString(s);
                    return "'" + s + "'";
                case CType.DATETIME:
                    try {
                        return "'" + DateTime.Parse(s).ToString() + "'";
                    } catch {
                        throw new CasJobsException("DateTime type value '" + s + "' is improperly formatted");
                    }
                default:
                    throw new Exception("val2SQL should never fail here");
            }
        }
        public static void ValidateString(string s) {
            foreach (string die in INVALID_STRINGS)
                if (s.IndexOf(die) > -1)
                    throw new CasJobsException("'" + s + "' contains an invalid substring");
        }
        // or L1 to Key and L2
        static void l12keyl2(string l1, out string key, out string l2) {
            int i = l1.IndexOf(SEP_LVL2);
            if (i == -1) throw new CasJobsException("Substring '" + l1 + "' is improperly formatted");
            key = l1.Substring(0, i).Trim();
            l2 = l1.Substring(i + 1).Trim();
        }
    }
}