/*
   Copyright (c) 2016, Adrian Rossiter

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

      The above copyright notice and this permission notice shall be included
      in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  IN THE SOFTWARE.
*/

/*
   Name: prism_tens.cc
   Description: make a prism tensegrity sphere
   Project: Antiprism - http://www.antiprism.com
*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <string>
#include <vector>
#include <algorithm>
#include <utility>

#include "../base/antiprism.h"

using std::string;
using std::vector;
using std::map;
using std::make_pair;

vec3d line_point(vector<vec3d> pts, int idx, double t)
{
   return pts[(idx+1)%pts.size()]*t + pts[idx%pts.size()]*(1-t);
}

int mod(int a, int b) { int m = a%b; return (m>=0) ? m : m+b; }

class strut {
   public:
      int f_idx;
      int sz;
      int start;
      int v_num;

      strut(): f_idx(-1) {}
      int get_con(int off=0) const { return 2*(start + mod(v_num+off, sz));
}

      operator bool() const { return f_idx != -1; }
      void dump() { fprintf(stderr, "f_idx=%d, sz=%d, v_num=%d, start=%d\n", f_idx, sz, v_num, start);
      }
};



class geom_maps {
   private:
      map<pair<int, int>, pair<int, int> > e2f_off;
      vector<int> starts;
      const geom_if &geom;

   public:
      geom_maps(const geom_if &geom): geom(geom)
      {
         int cnt = 0;
         for(int f_idx=0; f_idx<(int)geom.faces().size(); f_idx++) {
            const vector<int> &face = geom.faces(f_idx);
            const int fsz = face.size();
            starts.push_back(cnt);
            cnt += fsz;
            for(int i=0; i<fsz; i++)
               e2f_off[make_pair(face[i], face[(i+1)%fsz])] =
                  make_pair(f_idx, i);
         }
      }

      int get_start(int f_idx) { return starts[f_idx]; }

      strut get_neighbour_strut(int f_idx, int v_num) const
      {
         strut s;
         const vector<int> &face = geom.faces(f_idx);
         int off0 = mod(v_num, face.size());
         int off1 = mod(v_num+1, face.size());
         map<pair<int, int>, pair<int, int> >::const_iterator mi =
            e2f_off.find(make_pair(face[off1], face[off0]));
         if(mi != e2f_off.end()) {
            s.f_idx = mi->second.first;
            s.v_num = mi->second.second;
            s.sz = geom.faces(s.f_idx).size();
            s.start = starts[s.f_idx];
         }
         return s;
      }
};


void make_dome_prism(const col_geom_v &geom, col_geom_v &dome, double radius,
      double twist0, double twist1)
{
   geom_maps maps(geom);

   strut s0;
   for(unsigned int f_idx=0; f_idx<geom.faces().size(); f_idx++) {
      const vector<int> &face = geom.faces(f_idx);
      const int fsz = face.size();
      s0.start = dome.verts().size()/2;
      s0.f_idx = f_idx;
      s0.sz = fsz;
      vector<vec3d> pts;
      for(int i=0; i<fsz; i++)
         pts.push_back(geom.edge_cent(make_edge(face[i],
                     face[(i+1)%fsz])).unit());
      for(int i=0; i<fsz; i++) {
         s0.v_num = i;
         int vsz = dome.verts().size();
         dome.add_vert(line_point(pts, i, twist0). unit());
         dome.add_vert(line_point(pts, i+2, twist1). unit()*radius);
         dome.add_col_edge(vsz, vsz+1, 0);

         strut s1 = maps.get_neighbour_strut(f_idx, i);
         strut s2 = maps.get_neighbour_strut(f_idx, i+2);

         if(twist0<0) {
            dome.add_col_edge(s0.get_con(0), s1.get_con(0), 1);
            dome.add_col_edge(s0.get_con(1), s1.get_con(0), 1);
         }
         else {
            dome.add_col_edge(s0.get_con(0), s0.get_con(1), 1);
            dome.add_col_edge(s0.get_con(0), s1.get_con(0), 1);
         }
         if(twist1<0) {
            dome.add_col_edge(s0.get_con(0)+1, s2.get_con(-2)+1, 2);
            dome.add_col_edge(s0.get_con(1)+1, s2.get_con(-2)+1, 2);
         }
         else {
            dome.add_col_edge(s0.get_con(0)+1, s0.get_con(1)+1, 2);
            dome.add_col_edge(s0.get_con(0)+1, s2.get_con(-2)+1, 2);
         }
         dome.add_col_edge(s0.get_con(0), s0.get_con(-1)+1, 3);
      }
   }
}

void set_color_values(col_geom_v &geom)
{
   color_map_map *cmap = new color_map_map;
   cmap->set_col(0,  col_val(1.0, 0.0, 0.0));
   cmap->set_col(1,  col_val(0.0, 0.0, 1.0));
   cmap->set_col(2,  col_val(0.0, 1.0, 0.0));
   cmap->set_col(3,  col_val(0.9, 0.9, 0.0));
   cmap->set_col(10, col_val(1.0, 1.0, 1.0, 0.3));
   coloring clrng(&geom);
   clrng.add_cmap(cmap);
   clrng.e_apply_cmap();
   clrng.f_apply_cmap();
}

class tens_opts : public prog_opts {
   public:
      double radius;
      bool use_col_values;
      double twist0;
      double twist1;
      string ifile;
      string ofile;

      tens_opts(): prog_opts("prism_tens"),
                   radius(0.85),
                   use_col_values(true),
                   twist0(NAN),
                   twist1(NAN)
                   {}
      void process_command_line(int argc, char **argv);
      void usage();
};


void tens_opts::usage()
{
   fprintf(stdout,
"\n"
"Usage: %s [options] [input_file]\n"
"\n"
"Read a dome polyhedron in OFF format, project onto unit sphere and add\n"
"additional layering. If input_file is not given the program reads from\n"
"standard input.\n"
"\n"
"Options\n"
"%s"
"  -r <rad>  radius of sphere for second layer vertices (default: 0.85)\n"
"  -w <val>  inner twist (at radius of -r)\n"
"  -W <val>  outer twist (at radius of 1.0)\n"
"  -W <val>  layer type (default: dual)\n"
"  -i        write colours as index numbers (struts 0-4, faces 10)\n"
"  -o <file> write output to file (default: write to standard output)\n"
"\n"
"\n", prog_name(), help_ver_text);
}


void tens_opts::process_command_line(int argc, char **argv)
{
   char errmsg[MSG_SZ];
   opterr = 0;
   int c;
   char name[MSG_SZ];
   
   handle_long_opts(argc, argv);

   while( (c = getopt(argc, argv, ":hr:W:w:io:")) != -1 ) {
      if(common_opts(c, optopt))
         continue;

      switch(c) {
         case 'o':
            ofile = optarg;
            break;

         case 'r':
            if(!read_double(optarg, &radius, errmsg))
               error(errmsg, c);
            break;

         case 'W':
            if(!read_double(optarg, &twist0, errmsg))
               error(errmsg, c);
            break;

         case 'w':
            if(!read_double(optarg, &twist1, errmsg))
               error(errmsg, c);
            break;

         case 'i':
            use_col_values = false;
            break;

         default:
            error("unknown command line error");
      }
   }

   // If one of W/w set, default is the other, otherwise 0.
   if(isnan(twist0) && isnan(twist1))
      twist0 = twist1 = 0;
   else if(!isnan(twist0) && isnan(twist1))
      twist1 = twist0;
   else if(isnan(twist1) && isnan(twist1))
      twist1 = twist0;


   if(argc-optind > 1)
      error("too many arguments");

   if(argc-optind == 1)
      ifile=argv[optind];

}


int main(int argc, char *argv[])
{
   tens_opts opts;
   opts.process_command_line(argc, argv);
   col_geom_v geom;
   geom_read_or_error(geom, opts.ifile, opts);

   col_geom_v tens;
   make_dome_prism(geom, tens, opts.radius, opts.twist0, opts.twist1);

   if(opts.use_col_values)
      set_color_values(tens);

   geom_write_or_error(tens, opts.ofile, opts);

   return 0;
}


