// ----------------------------------------------------------------------------
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ----------------------------------------------------------------------------

using System;
using System.Collections;
using System.Text;
using System.IO;
using SharpSAT;
using Microsoft.Singularity;

//using Microsoft.Zap;

namespace SharpSATTest
{
    public class SATSolverTests : SATCommon
    {
        // a helper function   
        static private string getToken(string [] tokens, ref int index)
        {
            for (; index < tokens.Length;) {
                ++index;
                if (tokens[index - 1] != String.Empty)
                    return tokens[index -1];
            }
            return null;
        }

        // this demonstrate the structure interface   
        static void test_structure_solver (string[] args)
        {
            SATSolver solver = new SATSolver();
            int a = solver.CreatePI();
            int b = solver.CreatePI();
            int c = solver.CreatePI();
            //int d = solver.CreatePI();
            int xor_a_b = solver.Xor(a, b);
            int not_a = solver.Not(a);
            int not_b = solver.Not(b);
            int and_a_not_b = solver.And (a, not_b);
            int and_b_not_a = solver.And (b, not_a);
            int xor_a_b_p = solver.Or(and_a_not_b, and_b_not_a);
            int miter = solver.Xor(xor_a_b, xor_a_b_p);
            SATStatus r = solver.TestSAT(miter);
            WriteLine(String.Format("Test Right {0}", r.ToString()));

            int and_a_b = solver.And (a, b);
            int xor_a_b_wrong = solver.Or (and_a_not_b, and_a_b);
            int miter1 = solver.Xor(xor_a_b, xor_a_b_wrong);
            r = solver.TestSAT(miter1);
            WriteLine(String.Format("Test Wrong {0}", r.ToString()));

            int and_a_b_c = solver.And (solver.And(a,b), c);

            int flag = solver.AllocFlag ();
            solver.MarkTransitiveFanins(and_a_b_c, flag);
            for (int i = 0, sz = solver.NumPI(); i < sz; ++i) {
                int node_id = solver.NthPI(i);
                bool flagset = solver.IsNodeFlagSet (node_id, flag);
                if (flagset)
                    WriteLine(String.Format("PI {0} is in the transitive fanin", i));
                else
                    WriteLine(String.Format("PI {0} is NOT in the transitive fanin", i));
            }
            solver.ClearFlag(flag);


            MyHashtable nameMap = new MyHashtable();
            nameMap.Add(0, "PrimaryIn 0");
            WriteLine(solver.TreeToString(miter1, nameMap));
            WriteLine(solver.TreeToString(xor_a_b, nameMap));

            WriteLine("Press Return...");
            System.Console.ReadLine();
        }

        // this demonstrate the clause interface, it reads in a CNF file and
        // Solve it, just as a regular SAT solver should do   
        static void test_clause_solver (string[] args)
        {
            SATSolver solver = new SATSolver();

            // NB: on Singularity, args[0] is the exe image name
            if (args.Length != 2) {
                WriteLine("Simple SAT Solver using the C# interface");
                WriteLine("Usage: sharpSat CNF_file ");
                return;
            }
            StreamReader input = null;
            try {
                input = new StreamReader(args[1]);
            }
            catch (Exception e) {
                WriteLine("Error opening file '" + args[1] + "' :" + e.ToString());
                return;
            }

            string line;
            WriteLine(String.Format("Solving {0}", args[1]));
            while (true) {
                try{
                    line = input.ReadLine();
                }
                catch (Exception e){
                    WriteLine("Error reading file '" + args[1] + "' :" + e.ToString());
                    line = null;
                }
                if (line == null)
                    break;
                string [] tokens = line.Split(new char[] {' ','\t'});
                int index = 0;
                string token = getToken (tokens, ref index);
                if (token == "c")
                    continue;
                if (token == "p") {
                    token = getToken (tokens, ref index);
                    if (token != "cnf") {
                        WriteLine("Unrecognized Header");
                        return;
                    }
                    else {
                        token = getToken (tokens, ref index);
                        solver.SetNumVariables (int.Parse(token));
                        continue;
                    }
                }
                else {
                    ArrayList lits = new ArrayList();
                    while (token != null && int.Parse(token) != 0) {
                        lits.Add (int.Parse(token));
                        token = getToken (tokens, ref index);
                    }


                    if (lits.Count > 0) {
                        int [] clause = new int [lits.Count];
                        for (int k = 0; k < lits.Count; ++k) {
                            clause[k] = (int)lits[k];
                            if (clause[k] > 0)
                                clause[k] += clause[k];
                            else
                                clause[k] = 1 - clause[k] - clause[k];
                        }
                        solver.AddClause ( clause );
                    }
                }
            }
            input.Close();
            SATStatus result = solver.Solve();
            StringBuilder sb = new StringBuilder();
            if (result == SATStatus.SATISFIABLE) {
                WriteLine("SAT");
                for (int i = 1; i <= solver.GetNumVariables(); ++i) {
                    if (solver.VariableValue(i) == 1) {
                        sb.Append(String.Format("{0} ", i));
                    }
                    else if (solver.VariableValue(i) == 0) {
                        sb.Append(String.Format("-{0} ", i));
                    }
                    else {
                        sb.Append(String.Format("({0}) ", i));
                    }
                    if (i % 10 == 0) {
                        WriteLine(sb.ToString());
                        sb.Length = 0;
                    }
                }
                WriteLine(sb.ToString());
            }
            else if (result == SATStatus.UNSATISFIABLE)
                WriteLine("UNSAT");

            WriteLine(String.Format("Num Variables            {0}", solver.num_variables()));
            WriteLine(String.Format("Num Orig. Clauses        {0}", solver.stats.num_orig_clauses ));
            WriteLine(String.Format("Num Learned Clauses      {0}", solver.stats.num_learned_clauses));
            WriteLine(String.Format("Num Learned Literals     {0}", solver.stats.num_learned_literals));
            WriteLine(String.Format("Num Garbage Collection   {0}", solver.stats.num_garbage_collections));
            WriteLine(String.Format("Num Deleted Clauses      {0}", solver.stats.num_deleted_clauses ));
            WriteLine(String.Format("Num Deleted Literals     {0}", solver.stats.num_deleted_literals ));
            WriteLine(String.Format("Num Decisions            {0}", solver.stats.num_decisions ));
            WriteLine(String.Format("Num Backtracks           {0}", solver.stats.num_backtracks ));
            WriteLine(String.Format("Num Implications         {0}", solver.stats.num_implications ));
            WriteLine(String.Format("Total Runtime            {0}", solver.stats.total_cpu_time ));
            WriteLine(String.Format("Instance is              {0}", SATCommon.StatusToString(result)));
        }

        public static void log_execution(string [] args)
        {
            SATSolver solver = new SATSolver();
            if (args.Length != 1) {
                WriteLine("Simple SAT Solver using the C# interface");
                WriteLine("Usage: sharpSat CNF_file ");
                return;
            }
            FileInfo file = new FileInfo (args[0]);
            StreamReader input = file.OpenText();
            string line;
            IntVector nodes = new IntVector(4);
            while (true) {
                line = input.ReadLine();
                if (line == null)
                    break;
                string [] tokens = line.Split(new char[] {' ','\t'});
                int index = 0;
                string token = getToken (tokens, ref index);
                int k = int.Parse (token);
                token = getToken (tokens, ref index);
                sharp_assert (token == "=");
                token = getToken (tokens, ref index);
                if (token == "INIT_VARS") {
                    solver.SetNumVariables(k);
                    solver.ConvertVarsToPI();
                    nodes.resize ( k + k + 2);
                    for (int i = 0; i < k + k + 2; ++i)
                        nodes[i] = i;
                }
                else if (token == "CONSTRAINT") {
                    solver.Constraint(nodes[k]);
                    SATStatus status = solver.Solve();
                    if (status == SATStatus.UNSATISFIABLE)
                        WriteLine("UNSAT");
                    else
                        WriteLine("SAT");
                }
                else if (token == "PI") {
                    continue;
                }
                else if (token == "CL") {
                    IntVector lits = new IntVector(4);
                    token = getToken(tokens, ref index);
                    while (token != null) {
                        lits.push_back (int.Parse(token));
                        token = getToken(tokens, ref index);
                    }
                    solver.AddClause(lits.ToArray());
                }
                else if (token == "AND") {
                    token = getToken (tokens, ref index);
                    int i1 = int.Parse (token);
                    token = getToken (tokens, ref index);
                    int i2 = int.Parse (token);
                    int r = solver.And (nodes[i1], nodes[i2]);
                    if (nodes.size() < k + 2)
                        nodes.resize (k + 2);
                    nodes[k] = r;
                    nodes [k ^ 1] = (r^1);
                }
                else if (token == "XOR") {
                    token = getToken (tokens, ref index);
                    int i1 = int.Parse (token);
                    token = getToken (tokens, ref index);
                    int i2 = int.Parse (token);
                    int r = solver.Xor (nodes[i1], nodes[i2]);
                    if (nodes.size() < k + 1)
                        nodes.resize (k + 1);
                    nodes[k] = r;
                    nodes [k ^ 1] = (r^1);
                }
                else
                    fatal ("Unrecognized Symbol");
            }
        }

        static Random rand;

        static int [] randclause (int begin, int end)
        {
            IntVector cls = new IntVector(4);
            while (cls.empty()) {
                double r = rand.NextDouble();
                int a = (int) (r * (end - begin)) + begin;
                r = rand.NextDouble();
                if (r < 0.5)
                    a = a + a;
                else
                    a = a + a + 1;

                r = rand.NextDouble();
                int b = (int) (r * (end - begin)) + begin;
                r = rand.NextDouble();
                if (r < 0.5)
                    b = b + b;
                else
                    b = b + b + 1;

                r = rand.NextDouble();
                int c = (int) (r * (end - begin)) + begin;
                r = rand.NextDouble();
                if (r < 0.5)
                    c = c + c;
                else
                    c = c + c + 1;

                if ((a >> 1) != (b >> 1)) {
                    cls.push_back (a);
                    cls.push_back (b);
                    if ((a >> 1) != (c >> 1) && (b >> 1) != (c >> 1))
                        cls.push_back (c);
                }
                else {
                    cls.push_back (b);
                    if ((b >> 1) != (c >> 1))
                        cls.push_back (c);
                }
            }
            return cls.ToArray();
        }

        static bool test_interpolant_clauses (int n_vars, int n_cls, int span, int randseed)
        {
            rand = new Random(randseed);
            int [] [] a_clauses;
            int [] [] b_clauses;
            a_clauses = new int [n_cls][];
            b_clauses = new int [n_cls][];
            for (int i = 0; i < n_cls; ++i)
                a_clauses[i] = randclause ( 1, n_vars/2 + span);
            for (int i = 0; i < n_cls; ++i)
                b_clauses[i] = randclause ( n_vars/2 - span, n_vars + 1);

            SATSolver solver = new SATSolver();
            solver.SetNumVariables(n_vars);
            int a_gid = solver.AllocGID();
            int b_gid = solver.AllocGID();
            //1. Test if A is satisfiable
            for (int i = 0; i < n_cls; ++i)
                solver.AddClause (a_clauses[i], a_gid);
            if (solver.Solve() != SATStatus.SATISFIABLE) {
                Write("A SAT");
                return false;
            }
            solver.DeleteGroup(a_gid);
            //1. Test if B is satisfiable
            for (int i = 0; i < n_cls; ++i)
                solver.AddClause (b_clauses[i], b_gid);
            if (solver.Solve() != SATStatus.SATISFIABLE) {
                Write("B SAT");
                return false;
            }
            //3. Generate Interpolant
            a_gid = solver.AllocGID();
            for (int i = 0; i < n_cls; ++i)
                solver.AddClause (a_clauses[i], a_gid);
            Write("Interpolant ");
            int interpolant = solver.GenInterpolantFromClauseGroups ( a_gid, b_gid);
            if (interpolant == -1) {
                Write("AB SAT" );
                return false;
            }
            solver.Reference(interpolant);
            //now test IB Unsatisfiable
            Write("IB ");
            solver.DeleteGroup (a_gid);
            sharp_assert (solver.stats.num_orig_clauses ==  n_cls);
            int c = solver.Constraint(interpolant);
            SATStatus status = solver.Solve();
            if (status != SATStatus.UNSATISFIABLE)
                sharp_assert (false);

            //4. test AI Satisfiable
            Write("AI ");
            solver.DeleteGroup (b_gid);
            a_gid = solver.AllocGID();
            for (int i = 0; i < n_cls; ++i)
                solver.AddClause (a_clauses[i], a_gid);
            status = solver.Solve();
            if (status != SATStatus.SATISFIABLE)
                assert(false);
            //5. test AI' Unsat (i.e. A=>I)
            Write("AI' ");
            solver.ReleaseConstraint (c);
            c = solver.Constraint(solver.Not(interpolant));
            status = solver.Solve();
            if (status != SATStatus.UNSATISFIABLE)
                sharp_assert (false);
            solver.ReleaseConstraint (c);
            return true;
        }

        static int build_struct (SATSolver solver, int [][] clauses)
        {
            int sig = solver.one();
            for (int i = 0; i < clauses.Length; ++i) {
                int s = solver.zero();
                foreach (int lit in clauses[i])
                    s = solver.Or(s, lit);
                sig = solver.And(sig, s);
            }
            return sig;
        }

        static bool test_interpolant_structure (int n_vars, int n_cls, int span, int randseed)
        {
            rand = new Random(randseed);
            int [] [] a_clauses;
            int [] [] b_clauses;
            a_clauses = new int [n_cls][];
            b_clauses = new int [n_cls][];
            for (int i = 0; i < n_cls; ++i)
                a_clauses[i] = randclause ( 1, n_vars/2 + span);
            for (int i = 0; i < n_cls; ++i)
                b_clauses[i] = randclause ( n_vars/2 - span, n_vars + 1);

            SATSolver solver = new SATSolver();
            solver.SetNumVariables(n_vars);
            solver.ConvertVarsToPI();
            int s_a = build_struct(solver, a_clauses);
            int s_b = build_struct(solver, b_clauses);
            solver.Reference(s_a);
            solver.Reference(s_b);
            //1. Test if A is satisfiable
            int c_a = solver.Constraint(s_a);
            if (solver.Solve() != SATStatus.SATISFIABLE) {
                Write("A SAT");
                return false;
            }
            solver.ReleaseConstraint(c_a);
            //2. Test if B is satisfiable
            int c_b = solver.Constraint(s_b);
            if (solver.Solve() != SATStatus.SATISFIABLE) {
                Write("B SAT");
                return false;
            }
            solver.ReleaseConstraint(c_b);
            //now get interpolant I
            Write("Interpolant ");
            int interpolant = solver.GenInterpolantFromSignals ( s_a, s_b);
            if (interpolant == -1) {
                Write("AB SAT");
                return false;
            }
            solver.Reference (interpolant);
            //3. test IB Unsatisfiable
            Write("IB ");
            c_b = solver.Constraint(s_b);
            int c_i = solver.Constraint(interpolant);
            SATStatus status = solver.Solve();
            if (status != SATStatus.UNSATISFIABLE)
                sharp_assert (false);
            solver.ReleaseConstraint (c_b);
            //4. test AI Satisfiable
            Write("AI ");
            c_a = solver.Constraint (s_a);
            status = solver.Solve();
            if (status != SATStatus.SATISFIABLE)
                assert(false);
            //5. test AI' Unsat (i.e. A=>I)
            Write("AI' ");
            solver.ReleaseConstraint (c_i);
            solver.Constraint(solver.Not(interpolant));
            status = solver.Solve();
            if (status != SATStatus.UNSATISFIABLE)
                sharp_assert (false);
            return true;
        }

        static int build_irregular_struct (SATSolver solver, int [][] clauses,int randseed)
        {
            Random rand = new Random(randseed);

            int sig = solver.one();
            for (int i = 0; i < clauses.Length; ++i) {
                int s = solver.zero();
                foreach (int lit in clauses[i]) {
                    if (rand.NextDouble() > 0.2)
                        s = solver.Or(s, lit);
                    else
                        s = solver.Xor(s, lit);
                }

                if (rand.NextDouble() > 0.15)
                    sig = solver.And(sig, s);
                else
                    sig = solver.Xor(sig, s);
            }
            return sig;
        }

        static bool test_existential_quantification ( int n_cls, int n_vars, int n_qvar, int rseed)
        {
            rand = new Random(rseed);
            int [] [] clauses = new int [n_cls][];
            for (int i = 0; i < n_cls; ++i)
                clauses[i] = randclause ( 1, n_vars + 1);
            int [] qvars = new int [n_qvar];
            for (int i = 1; i <= n_qvar; ++i)
                qvars[i-1] = i + i;

            SATSolver solver = new SATSolver();
            solver.SetNumVariables(n_vars);
            solver.ConvertVarsToPI();
            int s = build_irregular_struct(solver, clauses, 1);
            int q1 = solver.expand_exist_quantify(s, qvars);
            //int q2 = solver.enum_exist_quantify(s, qvars);
            int q2 = solver.enum_exist_quantify_smart(s, qvars);

            SATStatus status = solver.solve();
            sharp_assert (status == SATStatus.SATISFIABLE);
            int notequ = solver.Xor(q1, q2);
            solver.Constraint(notequ);
            status = solver.Solve();
            sharp_assert (status == SATStatus.UNSATISFIABLE);
            return true;
        }

        public static bool UnitTest()
        {
            SATSolver solver = new SATSolver();

            int ab = solver.NthPI(0);
            int bc = solver.NthPI(1);
            int ac = solver.NthPI(2);

            int not_ab = solver.Not(ab);
            int not_bc = solver.Not(bc);
            solver.Not(ac);

            int fafb = solver.NthPI(3);
            int not_fafb = solver.Not(fafb);

            int fbfc = solver.NthPI(4);
            int not_fbfc = solver.Not(fbfc);

            // ((a=b && b != c) || (a!=b && b=c) || (a!=b && a=c)) && f(a)!=f(b) && f(b)!=f(c)
            int d1 = solver.And(ab, not_bc);
            int d2 = solver.And(bc, not_ab);
            int d3 = solver.And(ac, not_ab);

            int c1 = solver.Or(d1,d2);
            int c2 = solver.Or(c1,d3);

            int c3 = solver.And(c2,not_fafb);
            int c4 = solver.And(c3, not_fbfc);

            int formula = c4;
            int[] ret = solver.FindSatAssignment(formula);
            if (ret == null) return false;

            int[] clause = new int[2];
            clause[0] = solver.Not(ab);
            clause[1] = fafb;
            solver.AddClause(clause);
            ret = solver.FindSatAssignment(formula);
            if (ret == null) return false;
            return true;
        }

        public static void print_model(int [] m)
        {
            WriteLine("Model Is " );
            foreach (int e in m) {
                if ((e & 1) == 1)
                    Write("-");
                Write( (e/2) + " ");
            }
            WriteLine("");
        }

        public static bool test_small_model()
        {
            SATSolver solver = new SATSolver();

            int a = solver.NthPI(0);
            int b = solver.NthPI(1);
            int c = solver.NthPI(2);
            int d = solver.NthPI(3);

            int ab = solver.And(a, b);
            int cd = solver.And(c, d);
            int abcd = solver.And(ab, cd);
            int constraint = solver.Constraint(abcd);
            solver.Solve();
            int [] model = solver.FindSmallModel();
            print_model (model);
            solver.ReleaseConstraint(constraint);

            int not_abcd = solver.Not(abcd);
            constraint = solver.Constraint(not_abcd);
            solver.Solve();
            model = solver.FindSmallModel();
            print_model (model);
            solver.ReleaseConstraint(constraint);

            int e = solver.CreatePI();
            int f = solver.CreatePI();
            int exf = solver.Xor (e, f);
            int and_exf_abcd = solver.And(exf, abcd);
            constraint = solver.Constraint(and_exf_abcd);
            solver.Solve();
            model = solver.FindSmallModel();
            print_model (model);
            solver.ReleaseConstraint(constraint);

            int not_and_exf_abcd = solver.Not(and_exf_abcd);
            constraint = solver.Constraint(not_and_exf_abcd);
            solver.Solve();
            model = solver.FindSmallModel();
            print_model (model);
            solver.ReleaseConstraint(constraint);
            return true;
        }

        public static void test_quantification_edge_case ()
        {
            int n_cls = 100;
            int n_vars = 50;
            int n_qvar = 10;
            rand = new Random(10);
            int [] [] clauses = new int [n_cls][];
            for (int i = 0; i < n_cls; ++i)
                clauses[i] = randclause ( 1, n_vars + 1);
            int [] qvars = new int [n_qvar];
            for (int i = 1; i <= n_qvar; ++i)
                qvars[i-1] = i + i;

            SATSolver solver = new SATSolver();
            solver.SetNumVariables(n_vars);
            solver.ConvertVarsToPI();
            build_irregular_struct(solver, clauses, 1);

            int t = solver.GetOne();
            int f = solver.GetZero();
            int s1 = solver.And (t, 10);
            int s2 = solver.And (f, 10);
            int [] bounded = new int[2];
            bounded[0] = 2;
            bounded[1] = 3;
            int result = solver.EnumExistQuantify (s1, bounded);
            result = solver.EnumExistQuantify (s2, bounded);
            WriteLine(result.ToString());
        }

        private static void Write(string s)
        {
            Console.Write(s);
            DebugStub.Print(s);
        }
        private static void WriteLine(string s)
        {
            Console.WriteLine(s);
            DebugStub.WriteLine(s);
        }

        public static void Main(string [] args)
        {
//          bool r = UnitTest();
//          sharp_assert (r == true);
//
//          NetList net = new NetList();
//          net.read_blif(args[0]);
//          net.write_blif(args[0] + ".out");

//          test_clause_solver(args);
//          return;
//            WriteLine("Test Clause and Structure Based Interpolant Generation");
//            for (int i=0; i< 100; ++i)
//            {
//                Write(String.Format("Test {0}\tCLAUSE: ", i));
//                r = test_interpolant_clauses (100, 200, 45, i) ;
//                if (r == false)
//                    Write("*");
//                Write("\t STURCT:");
//                r = test_interpolant_structure (100, 200, 45, i) ;
//                if (r == false)
//                    Write("*");
//                WriteLine("");
//            }
//            for (int i=0 ; i < 100; ++i)
//            {
//                WriteLine("Test {0} ", i);
//                test_existential_quantification(50, 30, 20, i);
//            }
//            test_small_model();
//            test_quantification_edge_case();
            test_clause_solver (args);
        }
    }
}
